jbergner 56170ccde9
release-tag / release-image (push) Failing after 1m15s
v9.3.1
2026-08-31 22:17:18 +02:00
2026-08-31 17:09:21 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 17:09:21 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 22:17:18 +02:00
2026-08-31 17:09:21 +02:00

Dockwatch v9.3.1

Go module: git.send.nrw/sendnrw/dockwatch

Dockwatch is a single-binary Go control plane for Docker Compose, Docker resources and uptime monitoring. It combines Dockge/Dockhand-style operational workflows with Uptime-Kuma-style monitoring while keeping its own implementation and UI.

The same binary runs as standalone, master or agent. SQLite uses modernc.org/sqlite, so the application itself builds with CGO_ENABLED=0.

What is included

Compose / stack management

  • multiple Compose stacks per environment
  • local Docker plus remote agents
  • staged docker compose config --quiet validation before a save
  • atomic managed-file writes and rollback on save failure
  • full bidirectional Compose designer backed by a YAML AST
  • recursive editing of all YAML maps/arrays/scalars, including unknown/future Compose fields
  • compose.yaml, root .env, envs/, secrets/ and configs/
  • Up / Start / Stop / Restart / Down / Pull / Update / Force recreate
  • service/container status
  • live SSE logs with pause, follow, filter and download
  • interactive PTY/WebSocket docker compose exec terminal
  • normalized service/dependency/network/volume graph from docker compose config --format json
  • registry digest based image-update checks
  • safe stack deletion: only Dockwatch-managed definition files are removed by default
  • explicit, typed-name confirmation for full stack-folder purge

The editor keeps unsaved drafts in browser storage. Background refresh never overwrites a dirty editor.

Git-backed stacks

Git sources support:

  • local or remote-agent environment
  • repository, branch, workdir and Compose filename
  • manual sync
  • optional deploy after sync
  • signed webhooks (X-Hub-Signature-256, X-Gitlab-Token or X-Webhook-Token)
  • encrypted webhook secret at rest
  • last commit / last sync / last error

A Git sync is now managed-file based. Dockwatch stores a small .dockwatch/git-manifest.json in the stack directory and only replaces/removes files that belong to the Git checkout. Unrelated runtime or bind-mount data beside the Compose project is preserved. Syncs of the same source are serialized and a failed apply rolls back the touched file set.

Symlink stack destinations and symlink paths inside Git-managed writes are rejected.

Docker resources

Containers

  • list/filter
  • start / stop / restart / force-remove
  • operator-only inspect
  • one-shot CPU/memory/network/block stats

Container identity / host UID-GID checks

Container rows include an Identity action. Dockwatch inspects the selected container and reports:

  • configured Compose/image user and the effective runtime UID/GID where resolvable
  • whether the process currently runs as UID 0
  • a conservative root assessment based on privileged, Docker socket mounts, passed-through devices and added Linux capabilities
  • bind-mount sources and their host UID/GID ownership when host access is configured
  • single-container detail checks plus a throttled Identity audit across all containers in the selected environment
  • whether the container UID/GID already maps to a local host account/group

Dockwatch deliberately does not claim that a root container can always be converted to non-root. Application-internal filesystem permissions, entrypoints and image-specific PUID/PGID conventions cannot be proven from Docker metadata alone. It also never rewrites Compose user: automatically.

A matching local host username is not required by Docker. Linux file ownership is numeric; creating a host account can nevertheless make bind-mount ownership, backups and administration easier.

Host inspection is opt-in. For read-only inspection, set:

HOST_ROOT=/host
ALLOW_HOST_USER_MANAGEMENT=false
ALLOW_HOST_PERMISSION_MANAGEMENT=false

and mount the host root read-only at /host. examples/compose-host-identity.override.yml shows this setup.

Admins may optionally create a locked/non-login local host account using the container's server-side re-resolved UID/GID. This requires both a writable host-root mount and:

ALLOW_HOST_USER_MANAGEMENT=true

See examples/compose-host-user-management.override.yml. The browser cannot supply an arbitrary UID/GID: Dockwatch re-inspects the container immediately before the change and derives the IDs itself. Existing numeric users/groups are reused and UID/name collisions are refused. Docker rootless/user-namespace remapping is detected where possible; automatic same-numbered host-account creation is refused when IDs are remapped. This operation is intentionally admin-only and disabled by default.

Identity & Bind Mount Permissions

Dockwatch can now diagnose the actual bind-mount permission problem instead of stopping at "container runs as UID X". The container Identity dialog and each stack's Permissions tab show, per bind mount:

  • effective PID 1 UID/GID and a separate expected bind UID/GID
  • PUID/PGID or USER_ID/GROUP_ID when the image exposes those paired conventions
  • host owner UID/GID and POSIX mode bits
  • static writeability (w+x for directories, w for files)
  • extended POSIX ACL detection when getfacl is available
  • an optional non-mutating runtime test -w using the expected numeric identity
  • read-only mounts, rootless/userns remapping and unsafe symlinked host paths as hard repair blockers

The repair flow is Analyze → Preview → Repair → Verify. For recursive ownership repair Dockwatch scans the tree first and shows how many files/directories differ. Automatic recursive repair is refused above 200,000 entries. Symlinks are never followed or chowned. :ro mounts are never repaired automatically.

Permission repair is a separate high-trust opt-in from host-user creation:

HOST_ROOT=/host
ALLOW_HOST_PERMISSION_MANAGEMENT=true

and / must intentionally be mounted read-write at /host. See examples/compose-host-permissions.override.yml. The browser supplies only the container and its mount destination (for example /config); the backend re-reads docker inspect and resolves the real host source itself. Arbitrary host paths and arbitrary UID/GID values cannot be submitted for repair.

Ownership repair can operate on only the bind root or recursively. chmod is separate, optional, explicit, and only applies to the bind root; Dockwatch never automatically applies chmod 777 and never recursively rewrites modes. After a repair Dockwatch re-runs the ownership/writeability analysis and reports the result.

Images

  • list/filter
  • pull / remove / prune
  • registry login/logout
  • persistent Docker auth via DOCKER_CONFIG=/data/docker-config

Volumes

  • list/filter
  • create / remove / prune
  • driver and labels

Networks

  • list/filter
  • create / remove / prune
  • driver, labels, internal, attachable

Docker positional arguments and Compose service names are validated before invoking the CLI so option-like values cannot be interpreted as Docker CLI flags.

Monitoring

Probe types:

  • HTTP(S)
  • TCP
  • DNS
  • Docker container running state
  • Docker container running + optional healthy requirement

Monitor lifecycle:

  • create / edit / delete
  • pause / resume
  • manual or timed maintenance
  • maintenance notes
  • manual Check now
  • interval and timeout
  • HTTP method and accepted status range
  • request headers/body
  • keyword / inverted keyword assertion
  • optional HTTP TLS verification bypass
  • local or remote-agent execution
  • heartbeat history
  • last latency/status message
  • 24-hour uptime
  • retention cleanup

The scheduler uses a lightweight monitor query and does not recompute 24-hour uptime statistics every two seconds. Manual checks are serialized with scheduled checks and do not accidentally resume paused/maintenance monitors. Shutdown-cancelled checks are not persisted as false outages.

Services and public status pages

Probes can be grouped into user-facing Services. Aggregation is strict:

  • any probe down -> Service down
  • otherwise maintenance -> Service maintenance
  • pending/paused states are retained
  • otherwise Service up

Service dialogs can assign/unassign probes directly.

Admins can publish selected Services on public pages:

/status/<slug>
/public/api/status/<slug>

The public JSON API uses a dedicated DTO and exposes only public status information (name, status, uptime, latency and last-check timestamp), not targets, node IDs, request headers, bodies or credentials. HTML/JSON status responses are not cached.

Notifications

Providers:

  • generic JSON webhook
  • ntfy
  • Gotify
  • SMTP

SMTP supports:

  • STARTTLS
  • implicit SSL/TLS
  • None
  • authentication on/off independently of TLS
  • optional certificate-verification bypass for private/self-signed infrastructure

SMTP connections inherit the notification deadline after connect as well, so a stalled server cannot leave a delivery goroutine hanging indefinitely. SMTP/password/token/secret values are AES-GCM encrypted at rest. Notification configuration is admin-only in both API and UI.

PocketID / OIDC and roles

Roles are mapped from OIDC/PocketID groups:

  • viewer – dashboards/status/inventory, sanitized monitor configuration
  • operator – stack configuration/logs/terminal, Docker actions, monitor/service operations, Git stacks
  • admin – environments, notifications, status-page administration and audit log

Viewer API responses do not expose monitor request headers/body/keywords. Stack definition detail and live logs require Operator access because .env, secrets and runtime logs may contain sensitive data.

Required OIDC setup:

AUTH_DISABLED=false
BASE_URL=https://dockwatch.example.com
OIDC_ISSUER=https://id.example.com
OIDC_CLIENT_ID=...
OIDC_CLIENT_SECRET=...
APP_SECRET=replace-with-at-least-32-random-characters
OIDC_ADMIN_GROUP=dockwatch-admins
OIDC_OPERATOR_GROUP=dockwatch-operators

Redirect URI:

https://dockwatch.example.com/auth/callback

OIDC discovery, token exchange and verification are bounded by HTTP_TIMEOUT_SECONDS. Session/state cookies are HttpOnly, SameSite=Lax and become Secure when BASE_URL uses HTTPS.

Master / agent

Modes:

APP_MODE=standalone
APP_MODE=master
APP_MODE=agent

An agent exposes only /agent/v1/* and requires an AGENT_TOKEN of at least 24 characters. Agent tokens are AES-GCM encrypted in the master's SQLite database.

Remote-capable features include:

  • stacks and actions
  • logs and PTY terminal
  • Compose graph
  • image update checks
  • containers/images/networks/volumes
  • container identity analysis, bind-mount permission repair and optional host-account creation on the agent host
  • monitoring probes
  • Git clone/sync/deploy

See examples/compose-master.yml and examples/compose-agent.yml.

Full Compose designer

The Compose tab uses two synchronized representations:

  1. the raw YAML source of truth
  2. a recursive visual designer

YAML changes are parsed after a short debounce. Visual changes patch the selected YAML AST path instead of reserializing a simplified Compose model. Unknown keys, x-* extensions and comments are therefore preserved where possible. Invalid/incomplete YAML pauses visual synchronization and never overwrites the source editor.

The designer is not limited to a hard-coded subset: arbitrary maps, arrays and scalar types remain editable, including current/future Compose fields and long syntax objects.

Server-side save validation still uses Docker Compose itself after all related .env, secret, env-file and config files have been staged.

Reliability and security work in v9 / v9.2

The v9.2 identity extension is opt-in, admin-gated and preserves the existing least-surprise rule: diagnostics are read-only by default and no container user or host account is changed automatically.

The v9 review includes, among other changes:

  • fixed fresh-database migration ordering before monitor indexes are created
  • SQLite WAL/busy-timeout/foreign-key configuration with a small connection pool
  • strict environment parsing instead of silently accepting invalid numeric/boolean values
  • graceful HTTP shutdown and sensible server timeouts
  • security headers and same-origin checks for browser mutations
  • request JSON size limit and single-value decoding
  • admin-only audit activity and notification configuration
  • audit retention cleanup
  • public-status DTO that does not leak internal monitor configuration
  • safe IP parsing for audit entries
  • sensitive viewer responses reduced
  • Compose save snapshot/rollback
  • safe stack delete vs. explicit purge
  • symlink protection on stack/Git file paths
  • atomic managed-file writes
  • Git sync serialization and managed-file manifest
  • Git copy file-descriptor leak fixed
  • Node URL validation rejects credentials/query/fragment
  • remote terminal WebSocket URL/query handling fixed
  • registry/Docker CLI positional-argument validation
  • scheduler query and shutdown behaviour improved
  • defensive frontend collection handling (null cannot break list pages)
  • admin-only navigation entries hidden for non-admin users
  • persisted dark/light theme and collapsible/mobile sidebar
  • API connectivity indicator and manual refresh
  • denser Docker/monitor/service tables and better empty/error states

Configuration

.env.example contains the defaults. Important values:

APP_MODE=standalone
LISTEN_ADDR=:8080
BASE_URL=http://localhost:8080
DATA_DIR=/data
STACKS_DIR=/stacks
AUTH_DISABLED=true
APP_SECRET=change-me-change-me-change-me-32bytes
CHECK_CONCURRENCY=8
CHECK_RETENTION_DAYS=30
HTTP_TIMEOUT_SECONDS=10
AUDIT_RETENTION_DAYS=180
HOST_ROOT=
ALLOW_HOST_USER_MANAGEMENT=false
ALLOW_HOST_PERMISSION_MANAGEMENT=false

AUTH_DISABLED=true is for local development only. Do not expose that configuration publicly.

Run with Docker Compose

cp .env.example .env
docker compose up -d --build

Runtime mounts normally include:

/data                       SQLite, Docker registry config
/stacks                     Compose projects
/var/run/docker.sock        Docker Engine access

Giving Dockwatch access to the Docker socket grants highly privileged control of that Docker host. Protect the UI and agent endpoint accordingly.

v9.3.1 build-context fix

v9.3 accidentally used the broad ignore pattern dockwatch in both .gitignore and .dockerignore. Because patterns without a slash match path components recursively, that could hide cmd/dockwatch/ from Git and from the Docker build context. v9.3.1 removes that pattern, writes local Makefile builds to bin/dockwatch, ignores only bin//dist/, and makes the Dockerfile fail early with a clear message if cmd/dockwatch/main.go is ever missing from the build context.

Build from source

The pinned OIDC/OAuth2 releases require Go 1.25. The Docker build uses golang:1.25-alpine.

go mod tidy
go test ./...
CGO_ENABLED=0 go build ./cmd/dockwatch

Build metadata can be injected through the supplied Makefile/Dockerfile (VERSION, COMMIT, BUILD_DATE) and is visible in /healthz and the UI.

Database

SQLite database:

/data/dockwatch.db

modernc.org/sqlite is used, so no SQLite CGO binding is required. Migrations are additive and include users/sessions, nodes, monitors/checks/services, public status pages, audit events, notification channels and Git sources.

Verification for this archive

The artifact-building environment cannot reach proxy.golang.org, so it cannot download the real external modules or generate a trustworthy go.sum here. The repository intentionally does not ship fake checksums or test stubs.

For quality control, the project is copied into a temporary test workspace where API-compatible local stubs replace only the external dependencies. Those stubs are not included in the ZIP. The checks used for v9 include:

# all packages type/compile checked with temporary external-module stubs
go test -run='^$' ./...

go test ./internal/config ./internal/gitops ./internal/httpapi \
        ./internal/monitor ./internal/nodes ./internal/notify ./internal/stacks

go vet ./...
go test -race ./internal/config ./internal/gitops ./internal/httpapi ./internal/monitor ./internal/nodes ./internal/notify ./internal/stacks
node --check web/app.js

The SQLite migration SQL is additionally smoke-tested against Python's SQLite engine because the temporary modernc.org/sqlite stub does not implement a real SQL driver.

On a normal networked development machine or during docker build, run go mod tidy && go test ./... once against the real pinned dependencies.

S
Description
No description provided
Readme
801 KiB
Languages
Go 98.4%
HTML 0.7%
Dockerfile 0.4%
PowerShell 0.3%
Makefile 0.2%