Dockhand - Docker Management Dashboard

I’ve been running Portainer for a long time and it’s served me well, but I came across Dockhand the other day when I was researching a new project for security scanning docker images. It positions itself as a modern alternative with a heavier focus on security — built-in CVE scanning, a safe-pull strategy for auto-updates, and OIDC/SSO support out of the box. It’s free and source-available for homelab use, which checks all the boxes for me.

Dockhand gives you a clean web interface to manage containers, Compose stacks, images, volumes, and networks from one place. It also ships with browser-based terminals, real-time log streaming with color support, visual Compose file editing, and live CPU and memory metrics per container. For anyone already using Trivy for vulnerability scanning, it’s worth knowing Dockhand integrates both Grype and Trivy natively as part of its update workflow — it pulls new images to a temporary tag and only deploys if the vulnerability count doesn’t increase.

This post walks through deploying Dockhand with Docker Compose, getting through the initial setup, and covering a few of the features that make it stand out.


Prerequisites


Deployment

Dockhand uses SQLite by default with no database setup required, so the compose file is straightforward. Create a directory for it, add the compose file and a .env file, and you’re ready to go.

services:
  app:
    image: fnsys/dockhand:latest
    container_name: dockhand
    ports:
      - "${DOCKHAND_PORT}:3000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - dockhand_data:/app/data
    environment:
      - PUID=${DOCKHAND_PUID}
      - PGID=${DOCKHAND_PGID}
      - TZ=${DOCKHAND_TZ}
    deploy:
      resources:
        limits:
          memory: 512m
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:${PORT:-3000}/ || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s
    restart: unless-stopped

volumes:
  dockhand_data:

Create a .env file in the same directory with the following values:

# Main interface port for accessing the application
DOCKHAND_PORT="3000"

# User ID the container process runs as (run `id -u` on the host to get this)
DOCKHAND_PUID="1000"

# Group ID the container process runs as (run `id -g` on the host to get this)
DOCKHAND_PGID="1000"

# Time Zone where the application is operating
DOCKHAND_TZ="America/Los_Angeles"

A few things to note:

  • /var/run/docker.sock — mounts the Docker socket so Dockhand can manage containers on the host
  • dockhand_data — a named volume that persists Dockhand’s SQLite database, settings, and credentials across restarts
  • DOCKHAND_PORT — the host port Dockhand will be available on; change this if 3000 is already in use
  • DOCKHAND_PUID / DOCKHAND_PGID — set to the UID and GID of the user running Docker on your host; run id to check
  • DOCKHAND_TZ — set this to your local timezone so timestamps display correctly

If you’re on a NAS device and the dashboard feels slow, add SKIP_DF_COLLECTION: "true" to the environment block — it disables disk usage collection which can be sluggish on certain storage setups.

Bring it up with:

docker compose up -d

Initial Setup

Once the container is running, open a browser and navigate to:

http://{your server IP}:{DOCKHAND_PORT}

The first time you load the page, you’ll get a what’s new screen summarizing recent changes. Close it to continue.

You’ll land on the main dashboard showing No environments. Dockhand doesn’t auto-connect to the local Docker host — you add it manually. Click Go to Settings in the middle of the page.

Dockhand empty environments view

In the Add environment modal, fill in:

  • Name — whatever you want to call this host
  • Connection type — leave as Unix socket for the local host
  • Socket path — defaults to /var/run/docker.sock; click the auto-detect button to confirm
  • Public IP — optional, used for clickable port links on the containers page

Click Test connection to verify, then Add. Dockhand will connect and confirm with your Docker version and container count.

Dockhand add environment modal

There’s no admin account creation on first launch — authentication is disabled by default and the dashboard is open access. To lock it down, go to Settings → Authentication, click the Users tab, then Add user.

Fill in your username and password (both required), plus an optional email and display name, then click Create user. Once your user is created, go back to the Authentication General tab and click the OFF toggle at the top of the Authentication section to enable it. After that, you just need to refresh the page and it should prompt you for username and password.

Dockhand add user modal

SSO/OIDC and LDAP/AD are also available as auth options, but those require an Enterprise license.


Managing Containers and Stacks

From the Containers view you can start, stop, restart, and remove containers. Clicking into a container gives you access to real-time logs with color support, a live stats view showing CPU and memory usage, and a browser-based terminal — no SSH needed for quick inspections or one-off commands.

Dockhand container detail view

The Stacks view handles Compose-based deployments. You can create new stacks by pasting in a Compose file directly, edit existing ones using the visual editor, and deploy or redeploy with a single click. The visual dependency graph editor is a nice touch if you have stacks with multiple services and want to see how they relate to each other.

Dockhand stacks view

Vulnerability Scanning

One of the features that separates Dockhand from most other Docker dashboards is the built-in CVE scanning. When auto-updates are enabled, Dockhand uses a safe-pull strategy — it pulls the new image to a temporary tag, scans it with Grype and Trivy against current vulnerability databases, and only promotes it to replace the running container if the vulnerability count hasn’t increased.

Enabling Scanning

Scanning is configured per environment. Go to Settings → Environments, click the edit icon on your environment, and open the Security tab.

Toggle Enable scanning on, then choose your scanner — Grype, Trivy, or Both. If the tools aren’t installed yet, they’ll appear without a version. Install each one using the button next to it; Dockhand handles the download and shows the installed version once complete.

Dockhand environment security tab with Grype and Trivy installed

Save the environment when done.

Running Scans

You can also trigger a manual scan on any image from the Images view. Dockhand will show you a breakdown of vulnerabilities by severity alongside the CVE identifiers so you can look them up if needed.

Dockhand vulnerability scan results

Remote Host Management

If you’re running multiple Docker hosts, Dockhand can manage them all from a single dashboard using Hawser — a lightweight open-source agent you deploy on each remote host. Hawser supports two operating modes depending on your network setup:

  • Standard mode — the agent listens for incoming connections from Dockhand. Use this when both hosts are on the same LAN or can reach each other directly.
  • Edge mode — the agent initiates an outbound WebSocket connection to Dockhand. Use this when the remote host is behind NAT, on a VPS, or has a dynamic IP.

Standard Mode

Deploy the Hawser agent on the remote host. Create a directory for it and use this compose file:

services:
  hawser:
    image: ghcr.io/finsys/hawser:latest
    container_name: hawser
    ports:
      - "${HAWSER_PORT}:2376"
    volumes:
      - /home/${HAWSER_USERNAME}/hawser/stacks:/data/stacks
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - TOKEN=${HAWSER_Token}
      - PORT=2376
    healthcheck:
      test: ["CMD-SHELL", "if [ -n \"$$TLS_CERT\" ]; then wget -q --spider --no-check-certificate https://localhost:$$PORT/_hawser/health; else wget -q --spider http://localhost:$$PORT/_hawser/health; fi || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 5s
    deploy:
      resources:
        limits:
          memory: 512m
    restart: unless-stopped

Create a .env file in the same directory with the following values:

# Host port to expose the Hawser API on
HAWSER_PORT=2376

# Linux username whose home directory holds the stacks volume
HAWSER_USERNAME=

# Authentication token for the Hawser API
HAWSER_Token=

A few things to note:

  • HAWSER_PORT — the host port Hawser will listen on; 2376 is the default, change it if already in use
  • HAWSER_USERNAME — the Linux username whose home directory holds the stacks volume (e.g. dockersa)
  • HAWSER_Token — a strong random string used to authenticate Dockhand to the agent; generate one with openssl rand -hex 32

Bring it up:

docker compose up -d

Back in Dockhand, go to Settings → Environments → Add Environment. Set the connection type to Agent, enter the remote host’s IP and port 2376, and paste in your token. Click Test connection, then Add.

Edge Mode

Edge mode reverses the connection direction — the agent calls out to Dockhand rather than waiting for Dockhand to call in. This works through NAT without any port forwarding on the remote side.

First, in Dockhand go to Settings → Environments → Add Environment and select Edge Agent as the connection type. Dockhand will generate a token and display the WebSocket URL for the agent to connect to — copy both.

On the remote host, create the compose file using the values Dockhand gave you:

services:
  hawser:
    image: ghcr.io/finsys/hawser:latest
    container_name: hawser
    volumes:
      - /home/${HAWSER_USERNAME}/hawser/stacks:/data/stacks
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - TOKEN=${HAWSER_Token}
      - DOCKHAND_SERVER_URL=wss://${DOCKHAND_ADDRESS}:${DOCKHAND_PORT}
      - PORT=2376
    healthcheck:
      test: ["CMD-SHELL", "if [ -n \"$$TLS_CERT\" ]; then wget -q --spider --no-check-certificate https://localhost:$$PORT/_hawser/health; else wget -q --spider http://localhost:$$PORT/_hawser/health; fi || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 5s
    deploy:
      resources:
        limits:
          memory: 512m
    restart: unless-stopped

Create a .env file in the same directory with the following values:

# Hawser Edge

# Linux username whose home directory holds the stacks volume
HAWSER_USERNAME=

# Authentication token for the Hawser API
HAWSER_Token=

# Dockhand server address (hostname or IP)
DOCKHAND_ADDRESS=

# Dockhand server WebSocket port
DOCKHAND_PORT=3000

A few things to note:

  • HAWSER_USERNAME — the Linux username whose home directory holds the stacks volume (e.g. dockersa)
  • HAWSER_Token — paste in the token Dockhand generated when you created the Edge Agent environment
  • DOCKHAND_ADDRESS — the hostname or IP of your Dockhand host
  • DOCKHAND_PORT — the port Dockhand is running on; 3000 is the default

Setting DOCKHAND_ADDRESS is what switches Hawser into Edge mode — the agent detects it automatically and connects outbound, so no ports need to be exposed on the remote host.

Bring it up:

docker compose up -d

The environment in Dockhand will flip from pending to connected once the agent establishes the WebSocket session. If it doesn’t connect within a minute, check that the agent can reach your Dockhand host on port 3000 (or whichever port you’re running it on).

Managing Remote Hosts

Once connected, switching between hosts is a single click in the environment selector at the top of the Dockhand UI — the same containers, stacks, images, and terminal access you get on the local host. Hawser automatically reconnects with exponential backoff if the connection drops, so remote hosts stay managed without manual intervention.

Dockhand environment management

GitOps Integration

For anyone managing Compose files in a Git repository, Dockhand supports automated syncing via webhooks. Under Settings → Git Integration, you can point Dockhand at a repository and it will watch for changes and redeploy affected stacks automatically. This is the same workflow tools like Watchtower handle for image updates, but at the stack level and driven by your Git history rather than image tags.

Setup requires a webhook URL that you configure in your Git provider — Dockhand generates this for you once you connect the repo. If you’re looking for a starting point, I keep all of my Docker Compose files in my CHNS/Docker-Compose repo on Gitea — feel free to use them as a reference or fork them for your own setup.

Dockhand Git integration settings

Notifications

Dockhand can send alerts for container events, auto-update results, and system activity. There are two notification methods: SMTP for email, and Apprise for everything else — Apprise is a notification abstraction layer that supports 80+ services including ntfy, Discord, Slack, Telegram, Gotify, and Pushover, all configured via a single URL format.

To set up notifications, go to Settings → Notifications.

Dockhand notifications settings

Events

Dockhand can notify you on any combination of the following events:

Category Events
Container started, stopped, restarted, created, removed, healthy, unhealthy
Auto-update success, failed, blocked
Git sync success, failed, skipped
System error, warning, backup completed, cleanup completed

You can configure notifications globally or override them per environment — useful if you want different alert channels for different hosts.

ntfy Example

ntfy is a self-hosted push notification service that works well here. Dockhand uses Apprise’s ntfy URL format, so no extra setup is needed beyond entering the URL.

In the Apprise URL field, enter:

ntfys://your-ntfy-host.com/topic
  • Use ntfy:// for plain HTTP (local network), ntfys:// for HTTPS
  • Replace your-ntfy-host.com with your ntfy server’s hostname
  • Replace topic with the ntfy topic you want notifications sent to

If your ntfy server requires authentication, use username and password:

ntfys://username:[email protected]/topic

Or use an access token (preferred — generated under your ntfy account settings):

ntfys://[email protected]/topic

Apprise detects the token automatically when no password is present. You can also be explicit by appending ?auth=token to the URL.

Save the settings and use the Test button to confirm a notification comes through before relying on it.


Wrapping Up

Dockhand is a genuinely impressive project, especially given that it’s free for homelab use. The built-in CVE scanning and safe-pull update strategy are features I’d normally need separate tools for, and having them integrated directly into the container management dashboard makes the whole workflow cleaner. The GitOps integration and outbound-only remote agent are also well thought out compared to how other tools handle the same problems.

If you’re coming from Portainer, the transition is smooth — the concepts map over cleanly and the UI is intuitive. Whether it fully replaces Portainer in my setup long-term remains to be seen, but it’s worth deploying alongside it to evaluate.


References