Skip to content

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.

On your own machine, with no proxy, domain or certificate. From core/dockhand/:

Terminal window
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 -d

Then open http://localhost:3000. Stop it again with the same command and down.

From core/dockhand/:

  1. Copy the environment file and set APP_TRAEFIK_HOST, TZ and APP_PUID/APP_PGID:

    Terminal window
    cp .env.example .env
  2. Generate the two secrets:

    Terminal window
    mkdir -p .secrets
    openssl rand -base64 32 | tr -d '\n' > .secrets/db_pwd.txt
    openssl rand -base64 32 | tr -d '\n' > .secrets/encryption_key.txt

    Copy the encryption key off the host now — see Backup.

  3. Start it:

    Terminal window
    docker compose up -d
  4. 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.

  5. Add the Docker environment when Dockhand asks for one. For the daemon on this host:

    Field Value
    Connection type Direct connection
    Host socket-proxy
    Port 2375
    Protocol HTTP
    Public 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.

  6. 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
Terminal window
docker compose ps # three services, healthy

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

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.

core/hawser is the agent for a second machine. It is not covered by a guide here.

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 psql

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

  1. Read the release notes for the version you are moving to.

  2. Back up the database and the encryption key together.

  3. Raise APP_TAG in .env, then:

    Terminal window
    docker compose pull && docker compose up -d
  4. Open the environment and confirm the container list still loads. A change in the proxy’s permission set surfaces there.

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.