A clean, self-hostable manager for a Docker Compose cluster. One Go binary (sov backend) embeds a loom frontend, reads Docker Compose labels to group containers into stacks, and drives full stack lifecycle — all over the Docker API, so it works against a local socket or a remote daemon with no compose files on disk.
Docs: https://mininote.ink/s/dzW9c7aSbkHOGeD5-jH1qA
Screenshots below use a demo dataset — not a real cluster.
| Stack control + replica grouping | Streaming logs |
|---|---|
![]() |
![]() |
- Mission-control overview — every stack at a glance; trouble lights up, the rest stays quiet. A fleet ribbon + synthesized health verdict.
- Stack control — start / stop / restart / pull / redeploy per stack, and per
container, driven entirely through the Docker API (no
docker composeshell-out, no mounted compose files). Redeploy pulls images and recreates containers in place, preserving config, networks, and compose labels. - Replica grouping — services with multiple containers collapse into one row with bulk actions; expand for per-replica control.
- Live logs & stats — per container, per service (all replicas multiplexed and source-tagged), or the whole stack. Streamed as NDJSON; auto-scroll + wrap toggle.
- Update detection — a background crawler compares each container's image against its registry (a manifest lookup — never pulls layers) and flags what's out of date: a dashboard "updates" section, per-row chips on the stack page, and an "update available" button on the container page that redeploys to the latest.
- Host insight — the dashboard shows the Docker daemon's identity and capacity
(version, OS/arch, CPUs, memory, container/image counts) plus cached disk usage
(images / volumes / build cache) with an on-demand
dfrefresh. - Images view — every local image at a glance (repo:tag, id, size, age) with in-use / unused / dangling tags, searchable, sorted largest-first.
- Private registry pulls — reads a Docker
config.jsonsopull/redeployauthenticate to private registries (e.g. ghcr). - Guarded destructive actions — stop / kill / redeploy require confirmation.
- Single login — username/password from config; stateless HMAC bearer tokens.
- Optional socket proxy — expose the Docker API to a trusted LAN behind a method/path allowlist.
- Container plugins — any container can ship its own hope panel (tables, detail
pages, images, charts, live streams, actions) by exposing a small JSON-RPC
endpoint and a few labels. hope discovers it across the fleet, and — once enabled
— renders it and proxies every call; the browser never touches the plugin. Extend
hope in your own container, language, and release cadence — no hope recompile. See
the plugin protocol, the Go SDK on
pkg.go.dev, and the
example plugins (
hello-world,hope-postgres,kitchen-sink).
cp config.example.toml config.toml # then edit: username, password, token_secret
cp docker-compose.example.yml docker-compose.yml
docker compose up -d --buildOpen http://<host>:8080 and sign in. hope reaches the daemon through the mounted
/var/run/docker.sock.
See config.example.toml. Every key can be overridden with a
HOPE_* env var (e.g. HOPE_AUTH_PASSWORD). Key sections:
[auth]—username,password(plaintext or a$2…bcrypt hash), and a long randomtoken_secret.[docker] host—unix:///var/run/docker.sockor a remotetcp://host:2375.configoptionally points at a Dockerconfig.jsonfor registry credentials (defaults to~/.docker/config.json). hope reads only inlineauthentries —docker loginwrites those on Linux. Credential helpers / credsStore keep secrets outside the file and can't run in hope's minimal container. Podman works too — it serves the same API: runpodman system serviceand pointhostat its socket (unix:///run/podman/podman.sock, orunix://$XDG_RUNTIME_DIR/podman/podman.sockrootless). hope groups stacks by the Docker or podman compose labels (io.podman.compose.*), sopodman compose/podman-composeprojects show up grouped.[auth] api_keys— optional static keys for headless RPC (pass one as the bearer token). Enabling them also turns on the in-app API explorer at/apiand sov's schema at/rpc/_introspect. Off by default. See the in-app API page for the calling convention.[[registry]]— explicit registry credentials (server/username/password), the reliable way to authenticate pulls without mounting a config.json or running a helper. Use a Docker Hub account + access token so pulls aren't anonymous and rate-limited. Repeat per registry. An authenticated pull that the registry rejects (rate limit, bad creds) now fails the redeploy loudly instead of silently keeping the old image.[updates]— the image-freshness crawler.enabled(default true),interval(default6h; mind Docker Hub anonymous rate limits), and an optionalcache_paththat persists the freshness cache to disk so it survives restarts — mount that path to keep it across container recreates, e.g.cache_path = "/data/updates.json"with a/datavolume.[socketproxy]— opt-in LAN proxy; read-only by default.[log]—color/json.
- Mounting the Docker socket grants root-equivalent control of the host to the hope container. Run it only on a trusted network (LAN / overlay like ZeroTier), behind its login — not on a public interface.
- The socket proxy is the same exposure to whoever reaches its port. It defaults to
read-only (
GET/HEAD); only allowlist writes deliberately, and never route it through a public tunnel.
Backend (live reload with air):
air # builds ./cmd/hope, restarts on change (uses ./config.toml)Frontend (Vite dev server, proxies /rpc to the backend on :8080):
cd frontend && npm install && npm run devcd frontend && npm run build # outputs frontend/dist (embedded by the binary)
go build -o hope ./cmd/hope # single self-contained binaryOr build the image: docker build -t hope .. If github.com/Toyz/sov is private,
pass build credentials (see the Dockerfile header).
- Backend (
cmd/hope,internal/*): sov gateway.internal/dockerwraps the Docker SDK and groups containers by compose label;internal/stacks,internal/containers,internal/systemare RPC routers;internal/authis the sovAuthService;internal/plugins/logstreamstreams NDJSON logs/stats;internal/plugins/loggeris the unified request logger;internal/socketproxyis the optional LAN proxy. The built SPA is embedded viago:embed. - Frontend (
frontend/): loom + loom-rpc. A customRpcTransportadds the bearer token and implementsstream()over the NDJSON routes. Pages: login, dashboard (mission control), stack detail, container detail.
MIT — see LICENSE.


