Role: hawser¶
Dockhand's remote-host agent — runs as a Docker container on each managed Docker host, initiates an outbound WebSocket to the Dockhand controller, and proxies Docker API requests against the local daemon. The agent is what lets one Dockhand controller manage compose stacks on multiple hosts (Edge mode).
Hosts: n8n VM (Phase 5C). nginx VM has a manual install pre-dating this role; codification is a deferred follow-up.
Phase 5C executed — 2026-04-28
Role landed alongside the n8n VM bring-up. Container running on n8n VM, connected to the Dockhand controller on arrstack, serving Docker API requests. Two execution-time bugs captured in the n8n service page lessons — one of them (Hawser's 30s default REQUEST_TIMEOUT) is now codified as a role default.
Architecture¶
flowchart LR
subgraph arrstack["arrstack VM · 192.168.1.252"]
Dockhand[Dockhand controller<br/>:3000]
end
subgraph n8nvm["n8n VM · 192.168.1.248"]
Hawser[Hawser agent<br/>edge mode]
DockerSock[/var/run/docker.sock<br/>RW]
Stacks[/data/stacks<br/>named volume]
Hawser --> DockerSock
Hawser --> Stacks
end
Dockhand <-. ws://...:3000/api/hawser/connect .-> Hawser
The agent dials out to the controller (no inbound port needed on the Docker host, works behind NAT). A per-environment TOKEN generated in the Dockhand UI authenticates the WebSocket. Compose files Dockhand pushes land in the hawser_stacks named volume; Hawser executes docker compose up against the local daemon via the bound socket.
Tasks¶
| Task | Tag |
|---|---|
Pull ghcr.io/finsys/hawser:latest |
hawser, install |
Run Hawser agent (declarative via community.docker.docker_container) |
hawser, install |
Key variables¶
| Variable | Source | Value |
|---|---|---|
hawser_image |
defaults | ghcr.io/finsys/hawser:latest |
hawser_container_name |
defaults | hawser |
hawser_dockhand_url |
defaults | ws://192.168.1.252:3000/api/hawser/connect |
hawser_token_var |
defaults | vault_hawser_token_{{ inventory_hostname }} (per-host) |
hawser_stacks_volume |
defaults | hawser_stacks (Docker named volume) |
hawser_socket_mode |
defaults | rw (override to ro for monitor-only) |
hawser_request_timeout |
defaults | 600 seconds — see Gotchas |
hawser_restart_policy |
defaults | unless-stopped |
Per-host token vault entries follow vault_hawser_token_<hostname> and live in inventory/group_vars/all/vault.yml.
Gotchas captured during execution¶
REQUEST_TIMEOUT default of 30s is too short for any real image pull¶
Hawser's upstream default for per-request timeout is 30 seconds. The first Phase 5C deploy attempt failed at exactly 30s every time — dockerd logged Not continuing with pull after error / context canceled and Dockhand returned Success: false with the bare Image n8nio/n8n:latest Pulling error.
The role now sets REQUEST_TIMEOUT=600 (10 min) by default. Override per-host via hawser_request_timeout in host_vars if larger images warrant it.
Edge mode vs Standard mode¶
The role hardcodes Edge mode (outbound WebSocket from agent to controller). Hawser also supports Standard mode (inbound listen on port 2376) but Edge fits a homelab better — no inbound port to firewall, agent works behind NAT, no port to publish. Standard mode is the right choice if you ever need an air-gapped controller pulling from agents in a DMZ; not the case here.
docker.sock RW vs RO¶
Default is RW because Hawser actually issues writes (docker compose up creates containers, networks, volumes). RO works for "monitor-only" environments where Dockhand should see the host in its UI but never push deploys — useful if you want a single pane of glass over a host you're not yet trusting Dockhand to manage. nginx VM's existing manual install is RO precisely because nginx hasn't moved to git-managed yet.
TLS / wss://¶
The agent connects over plain ws:// on the LAN. Hawser logs a WARNING: Using unencrypted WebSocket. Token and all traffic are sent in cleartext. Use wss:// in production. on every start. Acceptable on a trusted LAN behind the UDM. Future cleanup: terminate TLS at the nginx VM and route Hawser → Dockhand via wss:// once the internal-traffic TLS story is in place.
Related¶
- Service: n8n — first stack deployed via this agent
- Service: arrstack — controller-side (Dockhand runs on the arrstack VM)
- Roadmap — Phase 5C