Skip to content

Commit f1fb4f8

Browse files
mrzakariacomclaude
andcommitted
Virtualmin proxy column + ports column; fix rename redirect bug (v1.2.0)
- Fix 'Invalid or unknown container reference' after rename/copy/commit/export: redir() appended '?' to URLs that already had a query string, corrupting id. - Container list now shows published Ports and the Virtualmin domains proxied to each container (matched by host port), linked to the site. Read-only, config-toggled, silently empty off Virtualmin. - Rewrite README for the Virtualmin-Docker project. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 048e2eb commit f1fb4f8

10 files changed

Lines changed: 250 additions & 123 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ All notable changes to this project are documented here. The format follows
44
[Keep a Changelog](https://keepachangelog.com/) and the project uses
55
[Semantic Versioning](https://semver.org/).
66

7+
## [1.2.0] - 2026-06-23
8+
9+
### Added
10+
- **Virtualmin integration**: the container list now shows the Virtualmin
11+
domains/subdomains whose Website Proxy Settings point at each container
12+
(matched by published host port), linked through to the site. Toggle in
13+
Module Config; silently empty on non-Virtualmin hosts.
14+
- **Ports column** in the container list (de-duplicated host→container mapping).
15+
16+
### Fixed
17+
- Rename (and copy / commit / export) reported "Invalid or unknown container
18+
reference" after succeeding: the redirect builder appended `?` to a URL that
19+
already had a query string, corrupting the `id` parameter. It now appends `&`
20+
when needed.
21+
722
## [1.1.0] - 2026-06-23
823

924
### Added

README.md

Lines changed: 157 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,168 +1,204 @@
1-
# Webmin Docker
1+
<div align="center">
22

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
64

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
106

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**.
149

15-
## Features
10+
[![License: GPL v2](https://img.shields.io/badge/License-GPLv2-blue.svg)](LICENSE)
11+
[![Webmin module](https://img.shields.io/badge/Webmin-module-FF6600.svg)](https://webmin.com)
12+
[![Virtualmin](https://img.shields.io/badge/Virtualmin-integrated-2E7D32.svg)](https://virtualmin.com)
13+
[![Version](https://img.shields.io/badge/version-1.2.0-success.svg)](CHANGELOG.md)
14+
[![Hardened](https://img.shields.io/badge/shell--injection-hardened-brightgreen.svg)](#-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+
| ![Dashboard](docs/screenshots/dashboard.png) | ![Widget](docs/screenshots/widget.png) |
46+
47+
## 🚀 Features
48+
49+
<table>
50+
<tr><td valign="top" width="50%">
1651

1752
**Dashboard**
18-
- Running / paused / stopped container counts and image count
53+
- Running / paused / stopped / image counts
1954
- `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
2256

2357
**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**
3264

3365
**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%">
3771

3872
**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)
4274

4375
**Storage**
44-
- Volumes and networks: list, inspect, create, remove, prune
76+
- Volumes & networks: list · inspect · create · remove · prune
4577

4678
**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`
5082

5183
**Maintenance**
52-
- `system prune` and `builder prune` (build cache), each with a confirmation
84+
- `system prune` & `builder prune`, each with a confirmation
5385

5486
**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**
5788

5889
**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)
10291

103-
## Install
92+
**Monitoring**
93+
- "Docker Up" & "Container Up" monitor types for **System and Server Status**
10494

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>
11397

114-
To build the package yourself from a checkout:
98+
## 🌐 Virtualmin integration
11599

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**.
119112

120-
(The top-level directory in the archive must be `docker`.)
113+
## 🔒 Security model
121114

122-
## Rootless Docker
115+
This is the whole reason the module exists.
123116

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. |
129125

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
131148

132149
**Module Config** (top-left cog) exposes:
133150

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
137154
- Confirm before destructive actions
138155
- 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
142166

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
144172

145173
| 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 |
148176
| `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 |
151179
| `act.cgi` | The single POST action dispatcher (ACL-gated, audited) |
152180
| `system_info.pl` | Home-screen dashboard widget |
153181
| `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
157192

158-
## License
193+
Copyright © 2026 MrZaKaRiA.
159194

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.
161199

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+
---
165201

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>

docker/act.cgi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ my ($dest, $msg, $err) = @_;
2929
my @p;
3030
push(@p, "msg=".&urlize($msg)) if (defined($msg) && $msg ne '');
3131
push(@p, "err=".&urlize($err)) if (defined($err) && $err ne '');
32-
$dest .= "?".join("&", @p) if (@p);
32+
# Use '&' when the destination already carries a query string, else '?'.
33+
$dest .= ($dest =~ /\?/ ? "&" : "?").join("&", @p) if (@p);
3334
&redirect($dest);
3435
exit;
3536
}

docker/config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
docker_context=
22
show_stats=1
3+
show_proxy=1
34
confirm_destructive=1
45
max_log_lines=100
56
scanner=auto

docker/config.info

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Display options,11
22
show_stats=Show live CPU/memory stats in the container list?,1,1-Yes,0-No
3+
show_proxy=Show Virtualmin domains proxied to each container?,1,1-Yes,0-No
34
max_log_lines=Default number of log lines to show,3,100
45
widget_enabled=Show the Docker panel on the Webmin dashboard?,1,1-Yes,0-No
56
Behaviour,11

0 commit comments

Comments
 (0)