Skip to content

dockguard scans Docker containers and reports health and lifecycle problems in a machine-friendly way

License

Notifications You must be signed in to change notification settings

hu553in/dockguard

dockguard

CI go-test-coverage Go Report Card GitHub go.mod Go version GitHub release (latest by date)

dockguard logo

⚖️ 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

Why dockguard (and why not "just use X")

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.

Philosophy

  • 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.

Quick start

Build or download the binary and run:

./dockguard

Installation

From GitHub Releases

Download the standalone binary for your OS/arch from the Releases page and put it on your PATH.

Container image (GHCR)

docker pull ghcr.io/hu553in/dockguard:latest

Run 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:latest

Versioned tag example:

docker pull ghcr.io/hu553in/dockguard:v1.2.3

Build from source

go build ./cmd/dockguard

Repo workflows / local dev

make check        # fmt + lint + verify-test-coverage + build-multi
make build        # build via GoReleaser into ./dist
make build-multi  # cross-build via GoReleaser into ./dist

Builds require GoReleaser v2+ to be installed locally.


How it connects to Docker

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 docker group, or
  • a rootless Docker setup with the correct socket/host exported.

Findings

dockguard can detect the following finding types:

  • restart_loop — container is running/restarting, its RestartCount is high enough, and its uptime is within restart-window
  • unhealthy — health status is unhealthy
  • health_starting — health status is starting
  • no_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

Restart loop heuristic (⚠️ important)

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)

Scoping and filtering containers

Scope

  • --scope=running (default) — only running containers
  • --scope=all — include non-running containers too

Labels

Filter containers by Docker label semantics (key or key=value), comma-separated:

./dockguard --labels=com.example.app,com.example.env=prod

Names (include/exclude)

Filter 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-names substring
  • a container is excluded if any of its names contains any exclude-names substring (takes precedence over include-names)

Controlling behavior: "disable a feature" vs "fail on it"

There are two separate knobs:

1) include-findings (feature toggle)

--include-findings controls what dockguard will detect and emit.

Disabled findings:

  • are not detected
  • will not appear in output
  • will not trigger failures

2) fail-on-findings (exit code policy)

--fail-on-findings controls which findings flip exit code to 1.

⚠️ Note: only findings enabled via --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,dead

Turn off no_healthcheck entirely (no output, no failing, no detection intent):

./dockguard --include-findings=restart_loop,unhealthy,dead,exit_non_zero,health_starting

Output

--output-format:

  • plain — human-friendly summary
  • plain_quiet — compact summary (no names list)
  • json — structured report (single JSON object)
  • json_pretty — structured report with indentation

Plain format

  • If no findings: OK: checked=<n>
  • If findings exist:
    • prefix is WARN or ERROR (depends on fail-on-findings)
    • then checked=<n>
    • then per-finding counters (and up to a few container names, unless quiet)

Example:

ERROR: checked=12; restart_loop=1(api); unhealthy=2(worker-1,worker-2)

JSON format (schema)

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 type
  • container_id: short container ID (12 chars)
  • names: container names (in order of appearance in Docker inspect)
  • image: image reference
  • status: best-effort status string
  • exit_code: container exit code (if applicable)
  • health: health status or unknown
  • restarting: whether Docker reports it as restarting
  • restart_count: restart counter
  • started_at, finished_at: RFC3339 timestamps as returned by Docker inspect

Exit codes

  • 0 — no findings (clean)
  • 1 — findings exist and at least one matches fail-on-findings
  • 2 — fatal error (Docker client/list/output); plus: container inspect failures when --strict-inspect=true

Logging

Logs go to stderr.

  • --log-level=debug|info|warn|error (default: info)
  • --log-format=plain|json|none (default: plain)

⚠️ Note: config parsing errors cannot be suppressed via --log-format=none.


Integration snippets

Below are minimal, copy-paste-friendly patterns.

GitHub Actions (gate job)

  • 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=json

If you run against a remote Docker host in CI, export DOCKER_HOST (and TLS vars if needed) or pass --docker-host.

GitLab CI

dockguard:
  image: golang:latest
  script:
    - go build -o dockguard ./cmd/dockguard
    - ./dockguard --output-format=json

Docker Compose health gate (example pattern)

Run dockguard from a "tooling" container that can talk to the Docker socket:

See Container image (GHCR) for the base invocation pattern.

Cron / systemd timer (simple)

Run every minute; if it fails, rely on your scheduler to alert/log:

* * * * * /usr/local/bin/dockguard --output-format=plain_quiet --scope=all

Monit / Nagios style checks

Use plain output (single line) and exit code:

Use the same invocation pattern as in the cron example above.

JSON processing (jq)

Show only failing findings and the affected container names:

dockguard --output-format=json | jq '.findings[] | {type, names, image, status, exit_code, health}'

"Fail only on selected findings"

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_pretty

"Disable a check completely"

Do not detect or output no_healthcheck:

dockguard --include-findings=restart_loop,unhealthy,dead,exit_non_zero,health_starting

Practical presets

Strict inspection (treat partial visibility as fatal)

dockguard --strict-inspect=true

Ignore expected non-zero exits (e.g. SIGTERM)

dockguard --ignore-non-zero-exit-codes=137,143

Scan only a "stack"

dockguard --labels=com.docker.compose.project=myapp

Scan everything, including exited containers

dockguard --scope=all

About

dockguard scans Docker containers and reports health and lifecycle problems in a machine-friendly way

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages