Dockhand — managing stacks from Git
Dockhand deploys and updates Compose stacks by watching a Git repository: you push, it applies. Three containers — the interface, PostgreSQL, and a proxy that stands between it and the Docker daemon.
Last run on a host 8 Nov 2026 · data restored from a backup
Pinned at v1.0.57 (released 4 Nov 2026) · the newest upstream release
In the upstream images: 1 CRITICAL · 33 HIGH with a published fix — per image
To try it: Docker, nothing else. See Try it locally.
To deploy it behind the proxy: Traefik running and a domain.
Try it locally
Section titled “Try it locally”On your own machine, with no proxy, domain or certificate. From core/dockhand/:
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needs{ echo "DB_PASSWORD=$(openssl rand -hex 32)" echo "ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -d '\n')"} >> .env.local
docker compose -f docker-compose.local.yml --env-file .env.local up -dThen open http://localhost:3000. Stop it again with the same command and down.
Installation
Section titled “Installation”From core/dockhand/:
-
Copy the environment file and set
APP_TRAEFIK_HOST,TZandAPP_PUID/APP_PGID:Terminal window cp .env.example .env -
Generate the two secrets:
Terminal window mkdir -p .secretsopenssl rand -base64 32 | tr -d '\n' > .secrets/db_pwd.txtopenssl rand -base64 32 | tr -d '\n' > .secrets/encryption_key.txtCopy the encryption key off the host now — see Backup.
-
Start it:
Terminal window docker compose up -d -
Over the VPN, open
https://<APP_TRAEFIK_HOST>. A new instance starts without authentication: whoever reaches the route manages every connected Docker host, so the VPN-only route is the only gate until step 6. -
Add the Docker environment when Dockhand asks for one. For the daemon on this host:
Field Value Connection type Direct connectionHost socket-proxyPort 2375Protocol HTTPPublic IP leave empty Plain HTTP is correct here: the proxy is reachable only from containers on the stack’s internal network, never from the host or the internet.
-
Turn authentication on in Dockhand’s settings and create your account. Afterwards the session endpoint reports it:
Terminal window curl -s https://<APP_TRAEFIK_HOST>/api/auth/session # "authEnabled":true
Verify
Section titled “Verify”docker compose ps # three services, healthyThen use Test connection in the environment dialog — it turns green — and confirm the container list appears afterwards. A running interface says nothing about whether the proxy is answering.
Going further
Section titled “Going further”The proxy is the hardening
Section titled “The proxy is the hardening”Dockhand never sees /var/run/docker.sock. It talks to
tcp://socket-proxy:2375, and the proxy decides which API calls are allowed.
The current set covers container management, networks, volumes, and the POST,
EXEC and DELETE calls that deploying a stack needs.
Each permission is an environment variable on the socket-proxy service. If you
do not manage images from the interface, set that variable to "0" and restart —
the surface shrinks with it.
Managing more than one host
Section titled “Managing more than one host”core/hawser is the agent for a second machine. It is not covered by a guide
here.
Backup
Section titled “Backup”| Database | PostgreSQL, container dockhand-db, database dockhand, user dockhand |
| Password | .secrets/db_pwd.txt |
| State | volumes/postgres · volumes/data |
| Also needed | .secrets/encryption_key.txt, stored somewhere other than the database |
| Quiescing | Not needed — the dump is consistent on its own. |
For borgmatic:
postgresql_databases: - name: dockhand username: dockhand # The server's own client, inside its container pg_dump_command: docker exec dockhand-db pg_dump pg_restore_command: docker exec -i dockhand-db pg_restore psql_command: docker exec -i dockhand-db psqlencryption_key.txt decrypts what the database holds. A database restored
without the matching key contains rows that cannot be read back. Keep the key and
the database in different places — storing them together removes the reason for
encrypting at rest.
Restore order: database, then the application.
Updates
Section titled “Updates”-
Read the release notes for the version you are moving to.
-
Back up the database and the encryption key together.
-
Raise
APP_TAGin.env, then:Terminal window docker compose pull && docker compose up -d -
Open the environment and confirm the container list still loads. A change in the proxy’s permission set surfaces there.
Troubleshooting
Section titled “Troubleshooting”Test connection fails. The environment fields do not match the proxy: host
socket-proxy, port 2375, protocol HTTP. The proxy is not reachable by any
other address.
An action in the interface reports a permission error. The socket proxy
denies that API call. Find the corresponding variable on the socket-proxy
service and decide whether the capability should be granted.
A restored instance shows rows it cannot read. The encryption key does not match the database.
Repository files
Section titled “Repository files”core/dockhand/README.md— setup, security model, the environment dialogcore/dockhand/UPSTREAM.md— source and upgrade checklistcore/hawser/— the agent for additional hosts
Official sources
Section titled “Official sources”© 2026 SecDockBlueCode Apache-2.0Site content CC BY-NC 4.0