|
1 | | -# Webmin Docker |
| 1 | +<div align="center"> |
2 | 2 |
|
3 | | -A clean, security-first [Webmin](https://webmin.com) module for managing Docker |
4 | | -from your browser — containers, images, volumes, networks, Compose projects, |
5 | | -and image scanning — with a status panel on the Webmin home dashboard. |
| 3 | +# 🐳 Virtualmin Docker |
6 | 4 |
|
7 | | -It is written from scratch with a single guiding rule: **no user input ever |
8 | | -reaches a shell unescaped.** Webmin runs as root, so a Docker management module |
9 | | -is a high-value target; this one is built to be safe by construction. |
| 5 | +### A clean, security-first Webmin & Virtualmin module for managing Docker from your browser |
10 | 6 |
|
11 | | -> If you want a full standalone Docker UI, look at |
12 | | -> [Portainer](https://www.portainer.io). This module is for people who already |
13 | | -> run Webmin and want Docker management that fits naturally inside it. |
| 7 | +Containers · images · volumes · networks · Compose · backups · vulnerability scanning — |
| 8 | +with a live dashboard widget and **Virtualmin reverse-proxy awareness**. |
14 | 9 |
|
15 | | -## Features |
| 10 | +[](LICENSE) |
| 11 | +[](https://webmin.com) |
| 12 | +[](https://virtualmin.com) |
| 13 | +[](CHANGELOG.md) |
| 14 | +[](#-security-model) |
| 15 | + |
| 16 | +</div> |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +> **Why another Docker module?** Because a panel that runs **as root** has no business |
| 21 | +> pasting your input into a shell. Every Docker command in this module is built from |
| 22 | +> constant flags plus individually escaped values — proven, not promised |
| 23 | +> (see [Security model](#-security-model)). On top of that it's fast, responsive, and |
| 24 | +> it knows which of your Virtualmin sites proxy to which container. |
| 25 | +
|
| 26 | +If you want a full standalone Docker UI, look at [Portainer](https://www.portainer.io). |
| 27 | +This module is for people who already live in **Webmin / Virtualmin** and want Docker |
| 28 | +management that fits right in — under **Servers → Docker**. |
| 29 | + |
| 30 | +## ✨ Highlights |
| 31 | + |
| 32 | +- 🔒 **Injection-proof by construction** — no user input ever reaches `/bin/sh` unescaped. |
| 33 | +- 🧭 **One-glance dashboard** — running/paused/stopped counts, disk usage, and a clickable home-screen widget. |
| 34 | +- 🌐 **Virtualmin-aware** — see the domains/subdomains reverse-proxied to each container, right in the list. |
| 35 | +- 🧰 **Everything you actually do** — full lifecycle, bulk actions, images, Compose, storage, backups, scanning. |
| 36 | +- 👥 **Granular access control** — grant or deny each capability per Webmin user. |
| 37 | +- 📝 **Audited** — every change is written to the Webmin Actions Log. |
| 38 | + |
| 39 | +## 📸 Screenshots |
| 40 | + |
| 41 | +> Drop your own PNGs into `docs/screenshots/` — they'll render here. |
| 42 | +
|
| 43 | +| Dashboard & containers | Home-screen widget | |
| 44 | +| --- | --- | |
| 45 | +|  |  | |
| 46 | + |
| 47 | +## 🚀 Features |
| 48 | + |
| 49 | +<table> |
| 50 | +<tr><td valign="top" width="50%"> |
16 | 51 |
|
17 | 52 | **Dashboard** |
18 | | -- Running / paused / stopped container counts and image count |
| 53 | +- Running / paused / stopped / image counts |
19 | 54 | - `docker system df` disk-usage breakdown |
20 | | -- A **home-screen widget** that shows Docker status in the Webmin dashboard |
21 | | - (the same area as "Servers Status") |
| 55 | +- Clickable **home-screen widget** on the Webmin dashboard |
22 | 56 |
|
23 | 57 | **Containers** |
24 | | -- List with live CPU / memory, start, stop, restart, pause, unpause, kill, |
25 | | - remove, rename, update resources (memory / cpus / pids / restart policy) |
26 | | -- **Bulk select-and-act** on many containers at once |
27 | | -- Create a container (env / ports / volumes / network / limits, optional |
28 | | - hardened defaults) and clone an existing one |
29 | | -- Per-container **logs** (timestamps, since, text filter, auto-refresh, |
30 | | - download), **inspect**, non-interactive **exec** with quick-command buttons, |
31 | | - live **stats**, and host ↔ container file copy |
| 58 | +- List with live CPU / memory, **ports**, and **proxied domains** |
| 59 | +- Start · stop · restart · pause · unpause · kill |
| 60 | +- Remove · rename · update resources · clone |
| 61 | +- **Bulk select-and-act** on many at once |
| 62 | +- Per-container **logs** (timestamps, since, filter, auto-refresh, download), |
| 63 | + **inspect**, non-interactive **exec** with quick-command buttons, live **stats** |
32 | 64 |
|
33 | 65 | **Images** |
34 | | -- List, inspect, history, remove, pull, push, tag |
35 | | -- Build from an inline Dockerfile, run a new container from an image |
36 | | -- **Docker Hub search** and prune (dangling or all unused) |
| 66 | +- List · inspect · history · remove · pull · push · tag |
| 67 | +- Build from an inline Dockerfile · run a new container |
| 68 | +- **Docker Hub search** · prune |
| 69 | + |
| 70 | +</td><td valign="top" width="50%"> |
37 | 71 |
|
38 | 72 | **Compose** |
39 | | -- List projects (`docker compose ls`) and run up / down / status / logs / |
40 | | - validate against any Compose file (v2 plugin, with legacy `docker-compose` |
41 | | - fallback) |
| 73 | +- List projects + up / down / status / logs / validate (v2, with v1 fallback) |
42 | 74 |
|
43 | 75 | **Storage** |
44 | | -- Volumes and networks: list, inspect, create, remove, prune |
| 76 | +- Volumes & networks: list · inspect · create · remove · prune |
45 | 77 |
|
46 | 78 | **Backup & restore** |
47 | | -- Images: save to / load from a host tar (`docker save` / `docker load`) |
48 | | -- Containers: commit to a new image, or export the filesystem to a tar |
49 | | -- Volumes: back up and restore a local volume's data as a `.tar.gz` |
| 79 | +- Images: `save` / `load` to a host tar |
| 80 | +- Containers: commit to an image · export the filesystem |
| 81 | +- Volumes: back up & restore a local volume as `.tar.gz` |
50 | 82 |
|
51 | 83 | **Maintenance** |
52 | | -- `system prune` and `builder prune` (build cache), each with a confirmation |
| 84 | +- `system prune` & `builder prune`, each with a confirmation |
53 | 85 |
|
54 | 86 | **Security** |
55 | | -- Image vulnerability scanning via **Docker Scout** or **Trivy** |
56 | | - (the removed `docker scan` is not used) |
| 87 | +- Image scanning via **Docker Scout** or **Trivy** |
57 | 88 |
|
58 | 89 | **Registry & contexts** |
59 | | -- Log in to a registry for private images, and switch the Docker context the |
60 | | - module talks to (rootless Docker is supported via contexts) |
61 | | - |
62 | | -**Monitors** |
63 | | -- "Docker Up" and "Docker Container Up" monitor types for the |
64 | | - [System and Server Status](https://webmin.com/docs/modules/system-and-server-status/) |
65 | | - module |
66 | | - |
67 | | -## Security model |
68 | | - |
69 | | -This is the reason the module exists, so it is worth stating plainly: |
70 | | - |
71 | | -- **No shell injection.** Webmin's command runners all execute through |
72 | | - `/bin/sh -c`. Every value originating from a form, a config file, or Docker |
73 | | - output is wrapped with a single-quote shell escaper (`sq()`) before it is |
74 | | - placed in a command. Constant flags are literals; user values are quoted |
75 | | - tokens — they can never be read as extra flags or shell metacharacters. |
76 | | -- **Allowlist validation.** Container / image / volume / network identifiers |
77 | | - are validated against anchored regexes before use, rejecting control |
78 | | - characters, leading dashes, and path traversal. |
79 | | -- **POST-only, referer-checked, audited mutations.** Every state change goes |
80 | | - through one dispatcher (`act.cgi`) that is reached only by POST (so Webmin's |
81 | | - trusted-referer check applies), is gated by a per-action ACL, and is recorded |
82 | | - with `webmin_log` for the Actions Log. |
83 | | -- **Secrets via stdin.** Registry passwords are passed to |
84 | | - `docker login --password-stdin` through the child process's standard input — |
85 | | - never on the command line, never via `echo`, and they are not stored by the |
86 | | - module. |
87 | | -- **Granular ACL.** Webmin users can be granted or denied each capability |
88 | | - independently (view / manage / create / delete / exec / prune / backup / |
89 | | - registry / context) under **Webmin Users → user → Docker**. The default |
90 | | - grant is full access (you are the admin); tighten it per-user as needed. |
91 | | -- **Output escaping.** All Docker output (names, statuses, error text) is |
92 | | - HTML-escaped before display to prevent stored XSS from container labels. |
93 | | -- **Destructive-action confirmations** for remove and prune (configurable). |
94 | | - |
95 | | -## Requirements |
96 | | - |
97 | | -- A host with Webmin installed |
98 | | -- The Docker engine and CLI (`docker`) available to the Webmin user |
99 | | -- Perl `JSON::PP` (ships with Perl 5.14+) |
100 | | -- Optional: `docker compose` (v2) for Compose, and `docker scout` or `trivy` |
101 | | - for scanning |
| 90 | +- Private-registry login · switch Docker context (rootless-friendly) |
102 | 91 |
|
103 | | -## Install |
| 92 | +**Monitoring** |
| 93 | +- "Docker Up" & "Container Up" monitor types for **System and Server Status** |
104 | 94 |
|
105 | | -1. Download `docker.wbm.gz` from the |
106 | | - [latest release](../../releases/latest). |
107 | | -2. In Webmin go to **Webmin → Webmin Configuration → Webmin Modules**, choose |
108 | | - **From local file** (or **From uploaded file**), select the package, and |
109 | | - install. |
110 | | -3. The module appears under **Servers → Docker**. The dashboard widget appears |
111 | | - on the Webmin home page once the **System and Server Status** module is |
112 | | - installed and enabled in your dashboard settings. |
| 95 | +</td></tr> |
| 96 | +</table> |
113 | 97 |
|
114 | | -To build the package yourself from a checkout: |
| 98 | +## 🌐 Virtualmin integration |
115 | 99 |
|
116 | | -```sh |
117 | | -tar -czf docker.wbm.gz docker |
118 | | -``` |
| 100 | +Running Docker behind Virtualmin virtual servers? When a site has **Website Proxy |
| 101 | +Settings → Proxying enabled** pointing at a local port (e.g. `http://localhost:3000`), |
| 102 | +this module matches that port to the container publishing it and shows the |
| 103 | +domain right in the container list: |
| 104 | + |
| 105 | +| Name | Status | Image | Ports | Proxied to | |
| 106 | +| --- | --- | --- | --- | --- | |
| 107 | +| `twenty-server-1` | 🟢 Up (healthy) | `twentycrm/twenty` | `3000->3000/tcp` | [crm.example.com](#) | |
| 108 | + |
| 109 | +It reads Virtualmin's domain definitions read-only, links each domain through to |
| 110 | +the live site, and **degrades silently to nothing on non-Virtualmin hosts**. Turn it |
| 111 | +off any time in **Module Config**. |
119 | 112 |
|
120 | | -(The top-level directory in the archive must be `docker`.) |
| 113 | +## 🔒 Security model |
121 | 114 |
|
122 | | -## Rootless Docker |
| 115 | +This is the whole reason the module exists. |
123 | 116 |
|
124 | | -Rootless Docker is supported through |
125 | | -[Docker contexts](https://docs.docker.com/engine/manage-resources/contexts/). |
126 | | -Create a context for the rootless socket, then either run |
127 | | -`docker context use <name>` or set the context override under |
128 | | -**Contexts** in the module (or in **Module Config**). |
| 117 | +| Guarantee | How | |
| 118 | +| --- | --- | |
| 119 | +| **No shell injection** | Every value from a form, config, or Docker output is wrapped with a single-quote escaper (`sq()`); constant flags stay literal. Verified by round-tripping `$()`, backticks, `;`, `\|`, `&&`, newlines and `-v /:/host` through a real `/bin/sh`. | |
| 120 | +| **Allowlist validation** | Container/image/volume/network identifiers are checked against anchored regexes before use — rejecting control characters, leading dashes and path traversal. | |
| 121 | +| **POST-only, referer-checked, audited** | All mutations go through one dispatcher (`act.cgi`) reached only by POST, gated by ACL, and logged with `webmin_log`. | |
| 122 | +| **Secrets via stdin** | Registry passwords are fed to `docker login --password-stdin` over the child's STDIN — never on argv, never via `echo`, never stored. | |
| 123 | +| **XSS-safe output** | All Docker output (names, statuses, error text) is HTML-escaped before display. | |
| 124 | +| **Confirmations** | Destructive actions (remove / prune / restore) ask first. | |
129 | 125 |
|
130 | | -## Configuration |
| 126 | +## 📦 Requirements |
| 127 | + |
| 128 | +- A host running **Webmin** (or **Virtualmin**) |
| 129 | +- The Docker engine + CLI (`docker`) available to the Webmin user |
| 130 | +- Perl `JSON::PP` (ships with Perl 5.14+) |
| 131 | +- Optional: `docker compose` (v2), and `docker scout` or `trivy` for scanning |
| 132 | + |
| 133 | +## ⚙️ Installation |
| 134 | + |
| 135 | +1. Download **`docker.wbm.gz`** from the [latest release](../../releases/latest). |
| 136 | +2. In Webmin go to **Webmin Configuration → Webmin Modules → From uploaded file**, |
| 137 | + choose the package, and click **Install Module**. |
| 138 | +3. Open it under **Servers → Docker**. The dashboard widget appears once the |
| 139 | + **System and Server Status** module is installed and enabled. |
| 140 | + |
| 141 | +Build it yourself from a checkout: |
| 142 | + |
| 143 | +```sh |
| 144 | +tar -czf docker.wbm.gz docker # top-level dir in the archive must be "docker" |
| 145 | +``` |
| 146 | + |
| 147 | +## 🛠️ Configuration |
131 | 148 |
|
132 | 149 | **Module Config** (top-left cog) exposes: |
133 | 150 |
|
134 | | -- Show live CPU / memory stats in the list |
135 | | -- Default number of log lines |
136 | | -- Show the dashboard widget |
| 151 | +- Show live CPU/memory stats in the list |
| 152 | +- Show Virtualmin domains proxied to each container |
| 153 | +- Default number of log lines · show the dashboard widget |
137 | 154 | - Confirm before destructive actions |
138 | 155 | - Preferred image scanner (auto / Docker Scout / Trivy) |
139 | | -- Default Compose file path |
140 | | -- Default backup directory |
141 | | -- Docker context override |
| 156 | +- Default Compose file path · default backup directory · Docker context override |
| 157 | + |
| 158 | +## 👥 Access control |
| 159 | + |
| 160 | +Under **Webmin Users → _user_ → Docker**, grant or deny each capability |
| 161 | +independently: **view · manage · create · delete · exec · prune · backup · |
| 162 | +registry · context**. The default grant is **full access** (you're the admin) — |
| 163 | +tighten it per-user as needed. |
| 164 | + |
| 165 | +## 🧩 Rootless Docker |
142 | 166 |
|
143 | | -## Module layout |
| 167 | +Supported via [Docker contexts](https://docs.docker.com/engine/manage-resources/contexts/). |
| 168 | +Create a context for the rootless socket, then `docker context use <name>` or set |
| 169 | +the context override in the module's **Contexts** page. |
| 170 | + |
| 171 | +## 🗂️ Module layout |
144 | 172 |
|
145 | 173 | | File | Purpose | |
146 | | -|------|---------| |
147 | | -| `docker-lib.pl` | Secure core: shell quoting, validation, and all Docker operations | |
| 174 | +| --- | --- | |
| 175 | +| `docker-lib.pl` | Secure core: shell quoting, validation, every Docker operation, Virtualmin proxy lookup | |
148 | 176 | | `index.cgi` | Overview dashboard + container management | |
149 | | -| `container.cgi` | Per-container logs / inspect / exec / stats / manage | |
150 | | -| `images.cgi`, `compose.cgi`, `storage.cgi`, `maintenance.cgi`, `security.cgi`, `registry.cgi`, `contexts.cgi` | Section pages | |
| 177 | +| `container.cgi` | Per-container logs / inspect / exec / stats / manage / backup | |
| 178 | +| `images · compose · storage · maintenance · security · registry · contexts .cgi` | Section pages | |
151 | 179 | | `act.cgi` | The single POST action dispatcher (ACL-gated, audited) | |
152 | 180 | | `system_info.pl` | Home-screen dashboard widget | |
153 | 181 | | `status_monitor.pl` | Monitor types for System and Server Status | |
154 | | -| `acl_security.pl`, `defaultacl` | Per-user access control | |
155 | | -| `log_parser.pl` | Renders audit-log entries | |
156 | | -| `install_check.pl` | Auto-detection of Docker | |
| 182 | +| `acl_security.pl` · `defaultacl` | Per-user access control | |
| 183 | +| `log_parser.pl` · `install_check.pl` | Audit-log rendering · Docker auto-detection | |
| 184 | + |
| 185 | +## 🤝 Contributing |
| 186 | + |
| 187 | +Issues and pull requests are welcome. Every `.cgi`/`.pl` file is validated with |
| 188 | +`perl -c` in CI, and changes to command construction should keep the `sq()` + |
| 189 | +allowlist discipline described above. |
| 190 | + |
| 191 | +## 📜 License |
157 | 192 |
|
158 | | -## License |
| 193 | +Copyright © 2026 MrZaKaRiA. |
159 | 194 |
|
160 | | -Copyright (C) 2026 MrZaKaRiA |
| 195 | +This program is free software; you can redistribute it and/or modify it under the |
| 196 | +terms of the [GNU General Public License version 2](LICENSE) as published by the |
| 197 | +Free Software Foundation. It is distributed **without any warranty** — see the |
| 198 | +license for details. |
161 | 199 |
|
162 | | -This program is free software; you can redistribute it and/or modify it under |
163 | | -the terms of the [GNU General Public License version 2](LICENSE) as published |
164 | | -by the Free Software Foundation. |
| 200 | +--- |
165 | 201 |
|
166 | | -This program is distributed in the hope that it will be useful, but WITHOUT ANY |
167 | | -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
168 | | -PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 202 | +<div align="center"> |
| 203 | +<sub>Built for the Webmin & Virtualmin community. Not affiliated with Docker, Inc.</sub> |
| 204 | +</div> |
0 commit comments