Presets

Ready-made sockguard configs for drydock, drydock with self-update, drydock with compose, drydock with build, drydock with mediated build, Portwing, Portwing with exec, Portwing with compose, Portwing with build, Portwing with mediated build, Traefik, Portainer, Watchtower, Homepage, Homarr, Diun, Autoheal, GitHub Actions and GitLab runners, the CIS Docker Benchmark, read-only dashboards, and Podman read-only monitoring.

Sockguard ships with ready-made config presets for common Docker consumers. All presets are bundled in the container image at /etc/sockguard/.

Drydock (drydock.yaml)

Optimized for the drydock container update manager.

Use for: drydock, or any container updater that needs lifecycle control + image pull.

# Allows: container read
# Allows: container lifecycle (start, stop, restart, kill, rename, update, wait, create, remove)
# Allows: image read, pull, remove
# Allows: network read + connect, volume read, distribution, Swarm service reads
# Denies: exec, build, swarm writes, secrets, plugins, raw archive/export/logs/attach streams
# container-create bodies are inspected by default — add host bind-mount sources to
# request_body.container_create.allowed_bind_mounts if your updates require them.
# image pulls are inspected too; this preset explicitly allows arbitrary registries
# while still denying image imports via fromSrc
# container updates are inspected; restart-policy/resource/device/capability changes
# stay denied unless you explicitly allow them under request_body.container_update
# container removal permits drydock's force-removal recovery path but keeps
# anonymous-volume and legacy-link deletion denied
# the stock "runc" runtime is allowlisted (request_body.container_create.allowed_runtimes)
# because drydock recreates containers from their inspect spec, which usually carries an
# explicit HostConfig.Runtime — without it every update would 403 at /containers/create
# Three response redactions are off: redact_mount_paths, redact_container_env
# and redact_network_topology. redact_sensitive_data stays on, at its default.
# Drydock recreates a container from its own
# inspect payload, so a "<redacted>" placeholder would be forwarded straight into
# POST /containers/create and dockerd would reject it. The four drydock-with-*
# presets inherit this; re-enable them only if the consumer never replays inspect
# output back into a create body.

Drydock with Self-Update (drydock-with-selfupdate.yaml)

Extends the drydock preset with the exec paths required for drydock's self-update finalize flow. The finalize step runs a short-lived docker exec inside the new container to POST a completion callback to drydock's internal API.

Use for: drydock deployments where the sockguard proxy is in the self-update path and you need the finalize callback to complete cleanly.

# Extends drydock.yaml with:
# Allows: POST /containers/{id}/exec, POST /exec/{id}/start, GET /exec/{id}/json
# Allows: container-create bodies that bind-mount /var/run/docker.sock
#         (request_body.container_create.allowed_bind_mounts, which drydock.yaml
#         leaves empty). The socket-mode recreate needs it; set it back to [] if
#         drydock reaches the daemon over TCP. This is the largest security
#         difference between the two presets.
# Exec body inspection: allow_privileged=false, allow_root_user=true
# (drydock's exec create sets no User field, and sockguard treats an empty
# User as root, so allow_root_user must be true or the finalize exec is
# denied before allowed_commands is ever checked)
# allowed_commands pins exec to the single finalize entrypoint argv, so the
# root-user allowance is safe: only that exact command can run
# allowed_env_vars pins exec Env to the six DD_SELF_UPDATE_* names. Without it
# the argv pin is not enough on its own: NODE_OPTIONS could --require arbitrary
# code under the pinned node command
# allowed_env_values pins the finalize callback to Drydock's loopback URL at
# port 3000; update the exact value if you configure a different server port

Without this preset (using drydock.yaml), the self-update still completes — the old container is renamed, the new one starts — but the finalize callback exec is denied and drydock logs the failure. Use this preset to get a clean finalize.

Drydock with Compose (drydock-with-compose.yaml)

Extends the drydock preset with the network and volume lifecycle a docker compose stack needs — the same gap portwing-with-compose.yaml closes for Portwing.

Use for: drydock deployments that recreate a docker-compose.yml stack through this proxy, not just a single standalone container.

# Extends drydock.yaml with:
# Allows: POST /networks/create, DELETE /networks/*, POST /networks/*/disconnect
#         POST /volumes/create, DELETE /volumes/*
# (POST /networks/*/connect is already allowed by drydock.yaml — untouched)
# Body inspection for the new endpoints reuses drydock.yaml's existing
# network/volume create guardrails (deny custom drivers/driver opts by default)
# Container removal additionally permits Compose's force and anonymous-volume
# cleanup controls; legacy link deletion stays denied
# Still denied: /build, /session, /grpc. `docker compose build` / `up --build`
# with default settings never calls POST /build at all — it drives dockerd's
# embedded BuildKit over the hijacked, unversioned /session and /grpc streams
# instead, both denied here. Use drydock-with-mediated-build.yaml if you want
# `--build` support with BuildKit's default (no env var) transport, fully
# mediated (issue #185) rather than opened wholesale. Use drydock-with-build.yaml
# instead if you specifically want the CLASSIC builder (POST /build only,
# requires DOCKER_BUILDKIT=0 in the client). See the Compose/BuildKit
# Transport section of the security model docs for the full breakdown.
# Compose-Spec post_start hooks are also still denied: they run through exec
# (POST /containers/{id}/exec, POST /exec/{id}/start), which the catch-all
# blocks, so drydock's PostStartExecutor fails the deploy loudly rather than
# skipping the hook. Add a pinned allowed_commands entry (modeled on
# drydock-with-selfupdate.yaml) rather than a blanket exec allow if you need it.
# Self-update finalize exec is still denied, same caveat as drydock.yaml;
# combine with drydock-with-selfupdate.yaml by hand if you need both

Drydock with Build (drydock-with-build.yaml)

Extends drydock-with-compose.yaml with classic builder only support for docker compose build / up --build, for deployments that need Sockguard to inspect build requests rather than blanket-deny them.

Use for: drydock-managed compose stacks that build images locally instead of only pulling pre-built ones — with the caveat that the client must run with DOCKER_BUILDKIT=0 set (see below).

# Extends drydock-with-compose.yaml with:
# Allows: POST /build — classic builder only, request_body.build left at its
#         secure defaults (allow_remote_context/allow_host_network/
#         allow_run_instructions all false)
# Still denies: /session, /grpc, and any moby.buildkit.v1.Control method path
# — this preset does not open either endpoint, no insecure_accept_opaque_
# buildkit_tunnels acknowledgment (now deprecated) and no request_body.buildkit
# mediation policy. THIS FAILURE IS INTENTIONAL: modern Buildx defaults to
# BuildKit, so a default `docker compose build` against this preset still
# fails with a /session or /grpc denial. Set DOCKER_BUILDKIT=0 in the client
# environment to fall back to the classic builder this preset actually
# supports, or use drydock-with-mediated-build.yaml for the fully-mediated
# BuildKit alternative.

Drydock with Mediated Build (drydock-with-mediated-build.yaml)

Extends drydock-with-compose.yaml with mediated BuildKit support — docker compose build / up --build run with BuildKit enabled (the Docker CLI default, no DOCKER_BUILDKIT=0 needed). Sockguard terminates the POST /session/POST /grpc tunnel as h2c and inspects every gRPC message crossing it (issue #185) instead of admitting the tunnel wholesale.

Use for: drydock-managed compose stacks that build images locally with BuildKit's default transport, where you want the same per-message inspection this preset's classic-builder sibling gets for /build.

# Extends drydock-with-compose.yaml with:
# Allows: POST /session, POST /grpc — mediated only; request_body.buildkit
#         admits Control.Solve (an "image"-typed exporter, no push registry
#         allowlisted), Control.Info/ListWorkers/Status (worker metadata and
#         ref-owned build progress), and Session.FileSync (REQUIRED — this is
#         how the Dockerfile and context reach buildkitd; still subject to
#         the same RUN-instruction hold-and-inspect scan classic /build gets,
#         since allow_run_instructions stays false)
# request_body.build's three flags stay at their secure defaults
# (allow_remote_context/allow_host_network/allow_run_instructions all false),
# reused verbatim for Solve's own entitlement/frontend checks
# Left denied by default (widen only if you need it): session.auth (base-image
# pulls needing registry auth — exact realm/scope match, no generic default
# covers arbitrary Dockerfiles), session.secrets/ssh (RUN --mount=type=secret/
# ssh), session.file_send/upload (local/tar export, remote/stdin context)
# Still denies: classic POST /build (use drydock-with-build.yaml instead) and
# the literal /moby.buildkit.v1.Control/<Method> probe path (no h2c upgrade
# for any mediator to terminate)
# insecure_accept_opaque_buildkit_tunnels is NOT set — it is deprecated and
# mutually exclusive with request_body.buildkit

Portwing (portwing.yaml)

Optimized for the Portwing Docker agent.

Use for: Portwing in the tri-tool topology (sockguard → Portwing → drydock), or any remote Docker agent that needs lifecycle control, image pull, and event streaming.

# Allows: container read (list, inspect, stats, top, changes, logs)
# Allows: container lifecycle (start, stop, restart, kill, rename, update, wait, create, remove)
# Container removal permits Portwing's force option; anonymous-volume and
# legacy-link deletion stay denied
# Allows: image read, pull, remove
# Allows: network read, volume read, distribution, Swarm service reads
# Denies: exec, build, swarm writes, secrets, plugins, raw archive/export/attach streams
# insecure_allow_read_exfiltration=true — required because GET /containers/*/logs and
# GET /containers/*/top are allowed (Portwing's GetContainerLogs() and process view); logs can
# carry secrets and top returns process command lines. Drop those rules and this
# flag to harden. /containers/*/archive, /export, /attach stay denied (bulk-exfil paths).
# Response redactions disabled (redact_mount_paths=false, redact_container_env=false,
# redact_network_topology=false) — required for drydock passthrough topology so
# inspect data forwarded to drydock is not corrupted by "<redacted>" placeholders
# Standalone operators not paired with drydock can re-enable all three redactions
# deny_verbosity: verbose returns detailed denial reasons to the caller.
# That is safe here because the consumer is the trusted Portwing agent or drydock
# controller. The runner and CIS presets use deny_verbosity: minimal instead.

Portwing with Exec (portwing-with-exec.yaml)

Extends the Portwing preset with exec support for interactive terminal access through the Portwing agent, e.g. terminal-over-websocket or drydock-driven exec in edge mode.

Use for: Portwing deployments where exec sessions are needed (interactive terminals, drydock edge mode exec calls).

# Extends portwing.yaml with:
# Allows: POST /containers/{id}/exec, POST /exec/{id}/start,
#         POST /exec/{id}/resize, GET /exec/{id}/json
# Exec body inspection: allow_privileged=false, allow_root_user=true
# (most container workloads run as root; set false + use allowed_commands for
# non-root or command-pinned deployments)
# insecure_allow_body_blind_writes=true — interactive exec can't be pinned to a fixed argv,
# so the body-blind-write guard is bypassed; the allow_privileged/allow_root_user layer still
# applies. insecure_allow_read_exfiltration=true is inherited from portwing.yaml for the
# GET /containers/*/logs rule (see the Portwing section above).

Portwing with Compose (portwing-with-compose.yaml)

Extends the Portwing preset with the network and volume lifecycle a docker compose stack needs, so Portwing can deploy compose stacks through this proxy, not just standalone containers.

Use for: Portwing deployments that create/remove compose-managed networks and named volumes (docker compose up/down), in addition to plain container orchestration.

# Extends portwing.yaml with:
# Allows: POST /networks/create, POST /networks/*/connect,
#         POST /networks/*/disconnect, DELETE /networks/*
#         POST /volumes/create, DELETE /volumes/*
# Body inspection for the new endpoints reuses portwing.yaml's existing
# network/volume create guardrails (deny custom drivers/driver opts by default)
# Container removal additionally permits Compose's force and anonymous-volume
# cleanup controls; legacy link deletion stays denied
# Still denied: /build, /session, /grpc. `docker compose build` / `up --build`
# with default settings never calls POST /build at all — it drives dockerd's
# embedded BuildKit over the hijacked, unversioned /session and /grpc streams
# instead, both denied here. Use portwing-with-mediated-build.yaml if you want
# `--build` support with BuildKit's default (no env var) transport, fully
# mediated (issue #185) rather than opened wholesale. Use portwing-with-build.yaml
# instead if you specifically want the CLASSIC builder (POST /build only,
# requires DOCKER_BUILDKIT=0 in the client). See the Compose/BuildKit
# Transport section of the security model docs for the full breakdown.
# insecure_allow_read_exfiltration=true is inherited from portwing.yaml for the
# GET /containers/*/logs rule (see the Portwing section above)
# Exec is denied, same as portwing.yaml — combine with portwing-with-exec.yaml's
# request_body.exec block by hand if you need both compose deploys and exec

Portwing with Build (portwing-with-build.yaml)

Extends portwing-with-compose.yaml with classic builder only support for docker compose build / up --build.

Use for: Portwing-managed compose stacks that build images locally instead of only pulling pre-built ones — with the caveat that the client must run with DOCKER_BUILDKIT=0 set (see below).

# Extends portwing-with-compose.yaml with:
# Allows: POST /build — classic builder only, request_body.build left at its
#         secure defaults (allow_remote_context/allow_host_network/
#         allow_run_instructions all false)
# Still denies: /session, /grpc, and any moby.buildkit.v1.Control method path
# — this preset does not open either endpoint, no insecure_accept_opaque_
# buildkit_tunnels acknowledgment (now deprecated) and no request_body.buildkit
# mediation policy. THIS FAILURE IS INTENTIONAL: modern Buildx defaults to
# BuildKit, so a default `docker compose build` against this preset still
# fails with a /session or /grpc denial. Set DOCKER_BUILDKIT=0 in the client
# environment to fall back to the classic builder this preset actually
# supports, or use portwing-with-mediated-build.yaml for the fully-mediated
# BuildKit alternative.
# insecure_allow_read_exfiltration=true is inherited from portwing.yaml for the
# GET /containers/*/logs rule (see the Portwing section above)

Portwing with Mediated Build (portwing-with-mediated-build.yaml)

Extends portwing-with-compose.yaml with mediated BuildKit support — docker compose build / up --build run with BuildKit enabled (the Docker CLI default, no DOCKER_BUILDKIT=0 needed). Sockguard terminates the POST /session/POST /grpc tunnel as h2c and inspects every gRPC message crossing it (issue #185) instead of admitting the tunnel wholesale.

Use for: Portwing-managed compose stacks that build images locally with BuildKit's default transport, where you want the same per-message inspection this preset's classic-builder sibling gets for /build.

# Extends portwing-with-compose.yaml with:
# Allows: POST /session, POST /grpc — mediated only; request_body.buildkit
#         admits Control.Solve (an "image"-typed exporter, no push registry
#         allowlisted), Control.Info/ListWorkers/Status (worker metadata and
#         ref-owned build progress), and Session.FileSync (REQUIRED — this is
#         how the Dockerfile and context reach buildkitd; still subject to
#         the same RUN-instruction hold-and-inspect scan classic /build gets,
#         since allow_run_instructions stays false)
# request_body.build's three flags stay at their secure defaults
# (allow_remote_context/allow_host_network/allow_run_instructions all false),
# reused verbatim for Solve's own entitlement/frontend checks
# Left denied by default (widen only if you need it): session.auth (base-image
# pulls needing registry auth — exact realm/scope match, no generic default
# covers arbitrary Dockerfiles), session.secrets/ssh (RUN --mount=type=secret/
# ssh), session.file_send/upload (local/tar export, remote/stdin context)
# Still denies: classic POST /build (use portwing-with-build.yaml instead) and
# the literal /moby.buildkit.v1.Control/<Method> probe path (no h2c upgrade
# for any mediator to terminate)
# insecure_allow_read_exfiltration=true is inherited from portwing.yaml for the
# GET /containers/*/logs rule (see the Portwing section above)
# insecure_accept_opaque_buildkit_tunnels is NOT set — it is deprecated and
# mutually exclusive with request_body.buildkit

Traefik (traefik.yaml)

Minimal read-only access for Traefik reverse proxy.

Use for: Traefik's Docker provider. The allowlist is source-verified against Traefik v3.7.12; other reverse proxies need their own call-surface review.

# Allows: GET container list + inspect, network/service/task lists, node inspect
#         (Swarm), events, ping, version
# Denies: everything else, including container archive/export/logs/attach

In Swarm mode, task discovery includes node inspection at GET /nodes/*. Every allowed path is a named list or required inspect endpoint rather than a /containers/** glob, so this preset does not set insecure_allow_read_exfiltration: true. A client that genuinely needs GET /containers/*/logs or GET /containers/*/archive has to add that rule and the acknowledgment itself.

Portainer (portainer.yaml)

Full Docker API access for Portainer management UI.

Use for: Portainer with full management capabilities.

# Allows: containers, images, networks, volumes, exec, build, swarm, plugins (full R/W)
# WARNING: This grants near-full Docker socket access
# Container, image, build, network, swarm, node, and plugin writes are inspected, but
# the preset sets insecure_allow_body_blind_writes: true and
# insecure_allow_read_exfiltration: true unconditionally, because Portainer runs
# arbitrary exec and proxies every read endpoint
# request_body.build is fully widened as well (allow_remote_context,
# allow_host_network and allow_run_instructions are all true), so POST /build is
# admitted without the RUN-instruction or remote-context checks the drydock and
# portwing build presets keep, and image_pull.allow_all_registries is true

Portainer's cleanup path uses force removal and anonymous-volume removal, so both controls are enabled. Docker's legacy link-removal mode stays denied.

Watchtower (watchtower.yaml)

Optimized for nicholas-fedor/watchtower, the maintained continuation of the archived containrrr/watchtower. The preset was checked against both upstreams' container client code: the core recreate flow (list/inspect, stop, force-remove, create with copied network config, start, rename, exec lifecycle hooks, image pull/remove) is identical between them, so this preset works as a drop-in for containrrr/watchtower too.

Use for: Watchtower v1.21.2 with lifecycle hooks and network-aware updates.

# Allows: ping/info; container list, inspect, start, stop, rename, update,
#         remove and create; image inspect, pull and remove; lifecycle-hook
#         exec; network connect
# Denies: every unused container/image/network route, all volume and
#         distribution routes, read-exfiltration routes, build, swarm,
#         secrets, configs and plugins

Container create allows the stock runc runtime and rejects other explicit runtimes. It also admits the copied endpoint addressing and optional user-configured MAC that Watchtower preserves during recreation, without allowing endpoint links, driver options, link-local IPs, or gateway priority. Watchtower copies those four fields verbatim from the old container, so a container that relies on legacy links, per-endpoint driver options, link-local IPs or a custom gateway priority cannot be recreated through this preset. Open the matching endpoint_config switch for that field alone rather than allow_endpoint_config, which would also admit links and driver options. Container update allows the restart-only request Watchtower uses to set Name: "no". This is a nicholas-fedor addition: containrrr/watchtower never called POST /containers/{id}/update at all, so the rule is simply unused against the original image rather than unsafe. Moby serializes the embedded Resources fields at their zero values in that request, which the daemon treats as no-ops; nonzero resource, privilege, device and capability updates remain denied, matching the fact that no code path in either upstream submits real resource limits through this route. Image list is explicitly denied before the registry-qualified image-inspect rule. The preset permits Watchtower's force-removal path and optional anonymous-volume cleanup, while legacy link removal stays denied. It keeps insecure_allow_body_blind_writes: true for arbitrary lifecycle hooks and allows an empty exec user because Watchtower uses the container's configured user, but it no longer needs insecure_allow_read_exfiltration: true. Like the drydock presets it also turns off the mount-path, container-env and network-topology redactions, because Watchtower recreates containers from the inspect payload it just read; redact_sensitive_data stays on.

Homepage (homepage.yaml)

Read-only access for the Homepage dashboard.

Use for: Homepage, or any dashboard that only reads container state and stats.

# Allows: GET container list + inspect + stats, service list + inspect and task
#         list (Swarm)
# Denies: all write operations, plus container archive/export/logs/attach and
#         the image-tarball paths GET /images/get and GET /images/*/get

Every allowed path is a named list, inspect or stats endpoint rather than a /containers/** or /images/** glob, so this preset does not set insecure_allow_read_exfiltration: true. A client that genuinely needs GET /containers/*/logs or GET /containers/*/archive has to add that rule and the acknowledgment itself.

Homarr (homarr.yaml)

Optimized for Homarr dashboard with container management.

Use for: Homarr with start/stop/restart/remove controls enabled.

# Allows: GET container list + inspect + stats + logs
# Allows: POST start/stop/restart and DELETE container
# Denies: every other Docker API route, including all image routes

Homarr v1.76.2 uses container logs, so this preset still sets insecure_allow_read_exfiltration: true. Logs are the only exfiltration route it allows; container archive, export and attach routes and image tarball routes stay denied. Its dockerode call is container.remove() with no options, so the preset allows only bare container removal; force, anonymous-volume deletion, and legacy link deletion stay denied.

Diun (diun.yaml)

Minimal read-only access for Diun (Docker Image Update Notifier).

Use for: Diun, or any tool that only needs to discover running containers and inspect images.

# Allows: GET container list, image inspect, ping, version
# Denies: all other routes. Diun checks registries directly. Container
#         inspect is denied too; Diun's Docker provider never calls it

Image inspect is written GET /images/**/json rather than /images/*/json because Docker registers the route as /images/{name:.*}/json and {name:.*} spans slashes, so a registry-qualified image arrives as GET /images/ghcr.io/org/app:tag/json. The literal /json suffix keeps the pattern narrow: GET /images/get, GET /images/{name}/get and GET /images/{name}/history do not end in /json and stay denied. Because /** can match zero segments, an earlier GET /images/json deny also keeps the unused image-list call out of the surface. That is why this preset does not set insecure_allow_read_exfiltration: true.

Autoheal (autoheal.yaml)

Minimal access for docker-autoheal.

Use for: Autoheal, or any health-check-based restart tool.

# Allows: GET /containers/json and GET /containers/*/json (autoheal reads health
#         status from inspect, not from the list), POST /containers/*/restart
# Allows: /_ping (GET and HEAD), GET /version, GET /events (a live stream)
# Denies: everything else — the most restrictive write preset

GitHub Actions Self-Hosted Runner (github-actions-runner.yaml)

For the actions/runner process on a self-hosted runner that needs Docker socket access to spawn jobs.*.container + jobs.*.services containers.

Use for: self-hosted GitHub Actions runners that handle untrusted workflows (i.e. anything from a fork PR or a low-trust org member).

# Allows: container lifecycle, exec, attach, log streaming (GET /containers/*/logs),
#         image pull, per-job networks, named volumes for cache
# Denies: privileged containers, privileged exec, host namespace sharing, bind mounts,
#         capability additions, build, swarm, secrets, plugins,
#         raw tarball export (/containers/*/export, /images/*/get)
# insecure_allow_body_blind_writes=true — exec argv is not command-pinned (workflow steps are
# arbitrary); only privileged exec is denied. insecure_allow_read_exfiltration=true — the runner
# streams job output via the logs and attach APIs. Gate the proxy socket to the runner process
# via clients.unix_peer_profiles or clients.allowed_cidrs to bound both.

The preset enforces what a workflow can do once admitted. Pair it with clients.allowed_cidrs (TCP listener) or clients.unix_peer_profiles (unix listener) to authenticate which process is connecting — sockguard on its own does not know which runner identity is on the other end. The runner cleans up with docker rm --force, so force removal is enabled; anonymous-volume and legacy-link deletion stay denied.

GitLab Runner (Docker Executor) (gitlab-runner.yaml)

For gitlab-runner with executor = "docker" in config.toml. Spawns build + service + helper containers per CI job and uses docker exec to inject job steps.

Use for: GitLab Runner deployments handling untrusted CI jobs.

# Allows: container lifecycle, exec, attach, log streaming (GET /containers/*/logs),
#         image pull and image remove (DELETE /images/**, for the runner's
#         post-pipeline cleanup, so a job can delete any image on the host daemon),
#         per-job networks, named volumes
# Denies: privileged containers (even if config.toml asks for them), privileged exec,
#         host namespace sharing, bind mounts, capability additions, build, swarm,
#         secrets, plugins, raw tarball export (/containers/*/export, /images/*/get)
# insecure_allow_body_blind_writes=true — exec argv is not command-pinned; the runner injects
# arbitrary job-step commands. insecure_allow_read_exfiltration=true — job trace output streams
# via the logs and attach APIs. Gate the proxy socket to the runner via clients.unix_peer_profiles
# or clients.allowed_cidrs to bound both.

GitLab Runner force-removes job containers and removes their anonymous volumes unless volume_keep is enabled, so this preset enables both controls. Legacy link deletion stays denied.

Notable: the preset deliberately rejects privileged containers even if the runner's config.toml has privileged = true. DinD-style jobs that require a privileged build container will fail through the proxy until the operator either removes the flag or moves the affected project to a different runner. This is the secure default; widening it is an audit-trail-bearing edit.

CIS Docker Benchmark (cis-docker-benchmark.yaml)

Turns sockguard into an admission gate for the inspectable subset of the CIS Docker Benchmark v1.6.0 Section 5 (Container Runtime) controls.

Use for: any deployment that has to evidence CIS posture — every non-compliant docker run is rejected at the API boundary with 403 before dockerd executes it.

# Enforces CIS 5.3, 5.4, 5.5, 5.9, 5.10, 5.11, 5.12, 5.15, 5.16, 5.17,
#          5.21, 5.22, 5.23, 5.25, 5.28, 5.30, 5.31 (structural)
# Allows:  container/image/network/volume reads, container lifecycle
# Denies:  exec, build, swarm, secrets, plugins, process-list top,
#          raw archive/log/attach

See the dedicated CIS Docker Benchmark guide for the full control-by-control mapping, the negative-test recipe, and notes on companion tools for the host/daemon/image controls sockguard cannot inspect.

Read-Only (readonly.yaml)

Allows GET access to all Docker API endpoints. Denies all write operations.

Use for: generic dashboards, monitoring tools, read-only Portainer.

# Allows: GET on containers, images, networks, volumes, system, events, info, ping, version
# Denies: all POST, PUT, DELETE

This preset intentionally includes raw archive/export and log/attach stream reads, so it sets insecure_allow_read_exfiltration: true. Tighten it if your client only needs list/inspect endpoints.

Podman Read-Only (podman-readonly.yaml)

Read-only monitoring posture covering both of Podman's API surfaces — the Docker-compat /vN.NN/... paths and the native /libpod/... paths — in one file. See the Podman guide for the full routing model.

Use for: dashboards and monitoring tools watching a Podman host.

# Allows: GET list/inspect/stats/top/changes on containers; list/inspect
#         (+ history on the Docker-compat side) on images; list/inspect
#         only on networks, volumes, secrets — plus pod list/inspect/stats
#         (libpod only) and health/version/info/events, on both surfaces
# Denies: all writes on both surfaces, including libpod-only writes
#         (pod create, play/kube); archive/export/logs/attach/get/push
#         reads on both surfaces; and libpod-only exfiltration reads with
#         no Docker-compat counterpart (mounted-container inventory,
#         generate/kube, manifest push)

This preset intentionally sets insecure_allow_read_exfiltration: true for container process monitoring on both API surfaces. Docker-compatible GET /containers/{id}/top forwards caller-selected ps_args to the daemon host's ps, and native GET /libpod/containers/{id}/top exposes the same process-list class; both return command lines without response redaction. Every other exfiltration-gated route remains denied by the narrow rules, including native GET /libpod/pods/{id}/top, which carries the same global guard but is not part of this preset's pod read surface. The flag is global rather than per-rule, so anything you add to this preset later that the exfiltration catalog covers, container logs being the obvious one, is admitted without a fresh startup refusal to warn you. Point upstream.socket at either a rootful (/run/podman/podman.sock) or rootless ($XDG_RUNTIME_DIR/podman/podman.sock) Podman socket; the rule set itself doesn't change.

Using a Preset

Mount the preset as your config file:

services:
  sockguard:
    image: codeswhat/sockguard:latest
    group_add:
      - "${DOCKER_SOCK_GID:?set to the GID of /var/run/docker.sock}"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - sockguard-socket:/var/run/sockguard
    environment:
      - SOCKGUARD_LISTEN_SOCKET=/var/run/sockguard/sockguard.sock
    command: ["serve", "-c", "/etc/sockguard/drydock.yaml"]

Neither addition is optional. No preset sets listen.socket and the default is listen.address: "127.0.0.1:2375", so without SOCKGUARD_LISTEN_SOCKET sockguard binds container-local TCP and never writes into the volume the example mounts. Sockguard also runs as UID 65532, so it can't open a stock 0660 root:docker socket without group_add.

All preset configs are bundled in the image at /etc/sockguard/. Three of the 25 files there aren't workload presets: sockguard.yaml is the default config the container loads when no -c is given, and discovery.yaml and multi-listener.yaml are covered in Configuration.

Presets that keep broad container/image read compatibility also carry explicit insecure_allow_read_exfiltration: true acknowledgements so they continue to validate honestly under the read-side export guardrail.