Language: English | 中文
Lightweight Docker container management Web UI. Zero external dependencies (Go standard library only), final image ~10MB. Supports single-host and multi-node federated deployment — manage Docker on many machines from one UI.
- Features
- Architecture
- Quick Start
- Deployment Scenarios
- Configuration
- API Reference
- UI Guide
- Operations
- FAQ
- Security
- Development
- Project Structure
- License
| Category | Capability |
|---|---|
| Container management | List running containers (name, image, status, port mappings) |
| Resource monitoring | Inline CPU% + memory; detail modal with network I/O, disk I/O, process count; 2s / 3s auto-refresh |
| Log viewer | Pull by line count (50 / 100 / 200 / 500), SSE live streaming, ANSI escape codes stripped |
| Remote restart | Two-click confirmation to prevent misclicks |
| Port suggestions | Docker API + TCP bind dual-check, click to copy, "refresh" for next batch |
| Multi-node | Hub + Agent architecture, one UI for many machines |
| Agent discovery | Agents register themselves; 30s heartbeat, 90s timeout → marked offline |
| Security | Bearer token auth, Hub ↔ Agent communication (recommend HTTPS reverse proxy) |
┌──────────────────────────────────────┐
│ Dockpit (MODE=both) │
│ ┌──────┐ ┌───────────┐ ┌────────┐ │
│ │ UI │ │ Registry │ │ Local │ │
│ │ │→ │ + Proxy │→ │ Agent │ │ → /var/run/docker.sock
│ └──────┘ └───────────┘ └────────┘ │
└──────────────────────────────────────┘
↑
[Browser]
┌─────────────────┐
│ Browser (UI) │
└────────┬────────┘
▼
┌────────────────────────┐
│ Hub (MODE=hub/both) │
│ ┌──────────────────┐ │
│ │ Agent Registry │ │
│ │ + Reverse Proxy │ │
│ └──────────────────┘ │
└──────┬─────────────────┘
│ Bearer Token
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Agent A │ │ Agent B │ │ Agent C │
│ Docker │ │ Docker │ │ Docker │
└─────────┘ └─────────┘ └─────────┘
Communication model:
- On startup, agents call
POST /api/agents/registeron the hub - Every 30s, agents send
POST /api/agents/heartbeat - If the hub doesn't hear from an agent for 90s → marked offline (kept in registry, can reconnect)
- The hub routes browser requests by
?agent=<name>:- Local agent → in-process call (no HTTP overhead)
- Remote agent → reverse proxy + inject bearer token
- SSE log streams use
FlushInterval: -1for realtime delivery
- Docker Engine 20.10+ / Docker Desktop
- One exposed port (default 8080)
- Access to
/var/run/docker.sock
git clone <repo-url>
cd Dockpitdocker compose up -d --buildOpen http://localhost:8080. No extra configuration required.
docker compose up -d --buildOpen http://localhost:8080.
Manage Docker on multiple machines from one central UI.
Step 1 — generate a shared token
openssl rand -hex 32All agents must use the same token. Leaking it grants control over every managed host.
Step 2 — hub machine
cd Dockpit
cat > .env <<EOF
TOKEN=<the-hex-token-you-generated>
AGENT_URL=http://hub.example.com:8080
EOF
docker compose up -d --buildStep 3 — on each remote host
cd Dockpit
cp .env.example .env
vi .env # fill in valuesExample .env:
TOKEN=<same-token-as-hub>
HUB_URL=http://hub.example.com:8080
AGENT_NAME=prod-server-1
AGENT_URL=http://prod-server-1.example.com:8080Start:
docker compose -f docker-compose.agent.yml up -d --buildStep 4 — verify
Open the hub UI. The agent switcher (top right) should show central plus each remote agent. Within 30s the first heartbeat lands and the green dot turns on.
go build -o /tmp/pc .
# Terminal 1: hub on :8080
MODE=both TOKEN=test AGENT_NAME=central \
AGENT_URL=http://localhost:8080 /tmp/pc
# Terminal 2: simulated remote agent on :8099
MODE=agent TOKEN=test HUB_URL=http://localhost:8080 \
AGENT_NAME=remote1 AGENT_URL=http://localhost:8099 \
LISTEN=:8099 /tmp/pcOpen http://localhost:8080 — the agent switcher shows both nodes.
All config is via environment variables. docker-compose.yml reads from .env.
| Variable | Default | Description |
|---|---|---|
MODE |
both |
hub / agent / both |
LISTEN |
:8080 |
Listen address |
TZ |
Asia/Shanghai |
Container timezone |
| Variable | Required | Description |
|---|---|---|
TOKEN |
hub/agent ✅; both optional | Shared secret (Bearer token). Must match across all nodes. In MODE=both a random value is generated if unset (internal use only). |
| Variable | Required | Default | Description |
|---|---|---|---|
AGENT_NAME |
agent ✅ | container hostname | Display name in the UI; unique within the cluster |
AGENT_URL |
agent ✅ | http://localhost:LISTEN (both) |
URL the hub uses to pull back from this agent — must be reachable from the hub |
HUB_URL |
agent ✅ | http://localhost:LISTEN (both) |
Hub base URL for registration and heartbeats |
mem_limit: 512mrestart: unless-stoppedlogging: json-filewith 10MB × 3 rotation
When running with MODE=both and no config:
TOKEN→ random 32-char hex (internal only)AGENT_NAME→ container hostnameHUB_URL→http://localhost:8080AGENT_URL→http://localhost:8080
Lists all registered agents. Public (used by the UI).
curl http://hub:8080/api/agents | jqCalled by agents on startup. Requires bearer token.
curl -X POST http://hub:8080/api/agents/register \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"prod-1","url":"http://prod-1:8080","version":"2.0.0"}'Called by agents every 30s. Requires bearer token.
Manually remove an agent. Requires bearer token.
All business endpoints on the hub accept ?agent=<name>. On an agent, endpoints require bearer token.
| Method | Path | Description |
|---|---|---|
| GET | /api/containers |
List running containers |
| GET | /api/containers/stats |
Stats snapshot for all containers (concurrent) |
| GET | /api/containers/stats/one?id=<cid> |
Single container detail |
| GET | /api/containers/logs?id=<cid>&tail=100 |
Recent logs |
| GET | /api/containers/logs/stream?id=<cid>&tail=100 |
SSE log stream |
| POST | /api/containers/restart {id:<cid>} |
Restart container |
| GET | /api/ports/suggest?start=8000&count=8 |
Suggest available ports |
| GET | /api/health |
Health check (always open) |
Example: fetch containers on remote1 via the hub
curl http://hub:8080/api/containers?agent=remote1- Click
🖥 central ▼at the top - Green dot = online, grey dot = offline (no heartbeat for 90s)
- Click any agent to switch views — all data reloads
- Online status refreshes every 15s
- 8 available ports starting from 8000 by default
- Click a port → copied to clipboard, chip flashes green for 1.2s
- "Refresh" continues scanning from the last end
Each row shows status / name + image / CPU% / memory / port mappings / actions.
- CPU / memory: mini progress bar + percentage. < 50% green, 50–80% orange, > 80% red. 3s refresh.
- Port mappings: badges in
host:container/protoformat
| Button | Behavior |
|---|---|
| 📊 Monitor | Opens resource modal, 2s refresh |
| 📋 Logs | Opens log modal, toggle line count, ▶ live stream |
| ⟳ Restart | First click turns red "confirm?"; second click within 3s executes |
- Select last 50 / 100 / 200 / 500 lines at the top
- ▶ Live switches to SSE stream (auto-scroll unless user scrolls up)
- ⏸ Stop closes the stream, keeps current output
- ANSI escape codes stripped automatically
- Close with Esc or click outside
Four metric cards:
- CPU percentage + online CPU count + progress bar
- Memory percentage + used/limit bytes + progress bar
- Network I/O cumulative ↓ rx / ↑ tx bytes
- Disk I/O cumulative read / write bytes
- Processes current PID count
Refreshes every 2s; interval cleared on close.
# Hub
docker compose logs -f dockpit
# Remote agent
docker compose -f docker-compose.agent.yml logs -f dockpit-agentdocker compose restart
# or
docker compose down && docker compose up -d
# Upgrade
git pull
docker compose up -d --build- Stop hub:
docker compose down - Stop every agent
- Edit
TOKENin all.envfiles (must stay in sync) - Bring hub up first, then agents
curl -s http://hub:8080/api/agents | jq '.[] | {name, online, last_seen}'curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"http://hub:8080/api/agents/remove?name=<agent-name>"Edit .env:
LISTEN=:9090Also update the compose ports mapping to 9090:9090, and each remote agent's AGENT_URL=http://host:9090.
Known Docker Desktop limitation. Dockpit runs inside the Docker Desktop Linux VM; net.Listen checks ports inside the VM, not on the macOS host. Ports published by Docker containers are detected correctly via the API. Not an issue on Linux hosts.
The hub can receive heartbeats ≠ the hub can reach the agent. Common causes:
- Firewall on the agent blocks inbound 8080
AGENT_URLpoints to the agent's own localhost, unreachable from the hub- Networking misconfigured inside the agent container
Debug: run curl -v http://<AGENT_URL>/api/health from the hub; should return {"status":"ok"}.
Expected. The hub uses heartbeats — offline takes 90s. To remove immediately, call DELETE /api/agents/remove?name=X.
The registry is in-memory. On the agents' next heartbeat (within 30s) they get a 404 and re-register automatically. Recovery: at most 30–60s.
Agent mode mandates a token. Correct steps:
cp .env.example .env
vi .env # fill in TOKEN etc.
docker compose -f docker-compose.agent.yml up -d.envis in.gitignore— never commit itchmod 600 .envto restrict permissions- In production, integrate with Vault / secret managers
Current version assumes a trusted network:
- Browser → hub: no auth
- Hub ↔ agent: bearer token
- Transport not encrypted
For production:
- Terminate TLS with nginx / Caddy in front of the hub
- If hub ↔ agent crosses the public internet, use WireGuard / Tailscale
- Or add mTLS (not implemented today)
Each agent mounts /var/run/docker.sock read-only (:ro). Note: read-only Docker socket still allows dangerous operations (read sensitive images, inspect all container env vars). Read-only is limited protection, not a full sandbox.
Deploy agents only on trusted hosts. Do not run in shared multi-tenant environments.
The UI has no per-user permission model — anyone who can reach the hub UI can restart any container on any agent. If you need access control:
- Front the hub with nginx basic auth / OAuth2 proxy
- Or extend Dockpit with per-agent authorization (not implemented)
Go standard library only, no external dependencies:
go build -o dockpit .
./dockpit # default MODE=both on :8080go build -o /tmp/pc .
MODE=both TOKEN=test AGENT_NAME=central AGENT_URL=http://localhost:8080 \
/tmp/pc > /tmp/hub.log 2>&1 &
MODE=agent TOKEN=test HUB_URL=http://localhost:8080 \
AGENT_NAME=remote1 AGENT_URL=http://localhost:8099 LISTEN=:8099 \
/tmp/pc > /tmp/agent.log 2>&1 &
curl -s http://localhost:8080/api/agents | jq
curl -s "http://localhost:8080/api/containers?agent=remote1" | jqdocker build -t dockpit:latest .
# or
docker compose buildMulti-stage build:
- builder:
golang:1.22-alpine+GOPROXY=https://goproxy.cn,direct - runtime:
alpine:3.19+ tzdata (dropped after Asia/Shanghai is set) - Final size ~10MB
Dockpit/
├── main.go # entry point, mode dispatch
├── config.go # env-var parsing
├── docker.go # Docker Engine API calls
├── agent.go # agent handlers + bearer middleware + register/heartbeat
├── hub.go # hub registry, heartbeat handlers, dispatcher
├── registry.go # thread-safe agent registry (RWMutex + TTL)
├── proxy.go # SSE-friendly reverse proxy
├── html.go # embedded Web UI (SVG sprite + CSS + JS)
├── go.mod # Go module (no external deps)
│
├── Dockerfile # multi-stage build
├── docker-compose.yml # hub / both mode (default, single host)
├── docker-compose.agent.yml # agent mode (remote nodes)
├── .env.example # env var template
├── LICENSE # Apache 2.0
├── NOTICE # copyright notice
└── README.md # this file
$ cat go.mod
module dockpit
go 1.22
Everything implemented on the standard library:
- HTTP / SSE →
net/http+net/http/httputil - Docker API →
net/http+net.Dialer(Unix socket) - JSON →
encoding/json - Concurrency →
sync+context - Regex (ANSI stripping) →
regexp
This project is licensed under the Apache License 2.0.
Copyright 2026 Beijing Aiyu Intelligent Technology Co., Ltd.
(北京艾语智能科技有限公司)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
- Website: https://www.aiyu-ai.com/
- Maintainer: wangxiaojie@aiyu-ai.com
- Issues / PRs are welcome via the repository