⚖️ License • 🤝 How to contribute • 🫶 Code of conduct
dockguard scans Docker containers and reports health and lifecycle problems in a machine-friendly way.
It's designed for automation:
- stdout → a stable report (plain or JSON)
- exit code → a deterministic machine-friendly contract
dockguard exists because most "ready" solutions tend to be one of these:
- A full monitoring system (Prometheus + exporters + alert rules + dashboards) — great, but heavy when you only need a simple gate.
- A local convenience script — fast, but ad-hoc, inconsistent output, no stable contract, and hard to reuse in CI.
- A healthcheck-only approach — misses dead/exited containers and restart-loop conditions.
- A Compose-only view — doesn't help if you have plain Docker containers, mixed stacks, or need daemon-wide scanning.
dockguard is intentionally small and opinionated:
- Single binary, no daemon, no dependencies.
- Deterministic output for machines.
- Independent control over detection vs failure behavior.
- KISS over cleverness: small surface area, predictable behavior.
- Output is a contract: stable keys, stable exit codes, stable sorting.
- Separate "detect" from "fail": you can disable checks entirely, or detect but not fail.
- Heuristic-based findings are explicitly documented.
- Follows the Docker CLI connection model.
Build or download the binary and run:
./dockguardDownload the standalone binary for your OS/arch from the
Releases page and put it on your PATH.
docker pull ghcr.io/hu553in/dockguard:latestRun it against a local daemon (mount the Docker socket):
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/hu553in/dockguard:latestVersioned tag example:
docker pull ghcr.io/hu553in/dockguard:v1.2.3go build ./cmd/dockguardmake check # fmt + lint + verify-test-coverage + build-multi
make build # build via GoReleaser into ./dist
make build-multi # cross-build via GoReleaser into ./distBuilds require GoReleaser v2+ to be installed locally.
dockguard uses the official Docker Go SDK and connects the same way the Docker CLI does:
- default: Docker environment variables (e.g.
DOCKER_HOST) - override:
--docker-host
To run against the local daemon you usually need either:
- root (or
sudo), or - membership in the
dockergroup, or - a rootless Docker setup with the correct socket/host exported.
dockguard can detect the following finding types:
restart_loop— container is running/restarting, itsRestartCountis high enough, and its uptime is withinrestart-windowunhealthy— health status is unhealthyhealth_starting— health status is startingno_healthcheck— container has no healthcheck configured (no health section in inspect)exit_non_zero— container is exited with a non-zero exit code (unless ignored)dead— container is dead
Docker doesn't expose historical restart timestamps in the inspect API. So restart_loop is a heuristic based on:
RestartCount>=restart-threshold- uptime <=
restart-window
Flags:
--restart-window(default:10m)--restart-threshold(default:3)
--scope=running(default) — only running containers--scope=all— include non-running containers too
Filter containers by Docker label semantics (key or key=value), comma-separated:
./dockguard --labels=com.example.app,com.example.env=prodFilter by container names using case-insensitive substring match, comma-separated.
--include-names=api,worker--exclude-names=postgres,redis
Rules:
- a container is included if any of its names contains any
include-namessubstring - a container is excluded if any of its names contains any
exclude-namessubstring (takes precedence overinclude-names)
There are two separate knobs:
--include-findings controls what dockguard will detect and emit.
Disabled findings:
- are not detected
- will not appear in output
- will not trigger failures
--fail-on-findings controls which findings flip exit code to 1.
--include-findings can actually trigger failure.
Examples:
Detect everything but only fail on unhealthy/restart_loop/dead:
./dockguard \
--include-findings=restart_loop,unhealthy,dead,exit_non_zero,no_healthcheck,health_starting \
--fail-on-findings=restart_loop,unhealthy,deadTurn off no_healthcheck entirely (no output, no failing, no detection intent):
./dockguard --include-findings=restart_loop,unhealthy,dead,exit_non_zero,health_starting--output-format:
plain— human-friendly summaryplain_quiet— compact summary (no names list)json— structured report (single JSON object)json_pretty— structured report with indentation
- If no findings:
OK: checked=<n> - If findings exist:
- prefix is
WARNorERROR(depends onfail-on-findings) - then
checked=<n> - then per-finding counters (and up to a few container names, unless quiet)
- prefix is
Example:
ERROR: checked=12; restart_loop=1(api); unhealthy=2(worker-1,worker-2)dockguard prints a single JSON object:
{
"checked": 12,
"failed": true,
"timestamp": "2026-01-07T12:34:56.789012345Z",
"scope": "running",
"docker_host": "unix:///var/run/docker.sock",
"findings": [ ... ]
}Each finding is:
type: finding typecontainer_id: short container ID (12 chars)names: container names (in order of appearance in Docker inspect)image: image referencestatus: best-effort status stringexit_code: container exit code (if applicable)health: health status orunknownrestarting: whether Docker reports it as restartingrestart_count: restart counterstarted_at,finished_at: RFC3339 timestamps as returned by Docker inspect
0— no findings (clean)1— findings exist and at least one matchesfail-on-findings2— fatal error (Docker client/list/output); plus: container inspect failures when--strict-inspect=true
Logs go to stderr.
--log-level=debug|info|warn|error(default:info)--log-format=plain|json|none(default:plain)
--log-format=none.
Below are minimal, copy-paste-friendly patterns.
- Fail the workflow on findings (exit code
1) - Print JSON report to logs
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- run: go build -o dockguard ./cmd/dockguard
- run: ./dockguard --output-format=jsonIf you run against a remote Docker host in CI, export DOCKER_HOST (and TLS vars if needed) or pass --docker-host.
dockguard:
image: golang:latest
script:
- go build -o dockguard ./cmd/dockguard
- ./dockguard --output-format=jsonRun dockguard from a "tooling" container that can talk to the Docker socket:
See Container image (GHCR) for the base invocation pattern.
Run every minute; if it fails, rely on your scheduler to alert/log:
* * * * * /usr/local/bin/dockguard --output-format=plain_quiet --scope=all
Use plain output (single line) and exit code:
Use the same invocation pattern as in the cron example above.
Show only failing findings and the affected container names:
dockguard --output-format=json | jq '.findings[] | {type, names, image, status, exit_code, health}'Only fail on unhealthy and restart_loop, but still include everything in output:
dockguard \
--include-findings=restart_loop,unhealthy,dead,exit_non_zero,no_healthcheck,health_starting \
--fail-on-findings=restart_loop,unhealthy \
--output-format=json_prettyDo not detect or output no_healthcheck:
dockguard --include-findings=restart_loop,unhealthy,dead,exit_non_zero,health_startingdockguard --strict-inspect=truedockguard --ignore-non-zero-exit-codes=137,143dockguard --labels=com.docker.compose.project=myappdockguard --scope=all