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 portWithout 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 bothDrydock 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.buildkitPortwing (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 execPortwing 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.buildkitTraefik (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/attachIn 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 truePortainer'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 pluginsContainer 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/*/getEvery 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 routesHomarr 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 itImage 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 presetGitHub 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/attachSee 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, DELETEThis 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.
Remote Upstreams & Failover
Connect sockguard to a remote Docker daemon over TCP+mTLS, or configure two endpoints for active/passive HA failover with automatic health probing.
Podman
Running sockguard in front of a Podman socket — the Docker-compat and native libpod API surfaces, which request_body.* keys govern which endpoint, the uninspected play/kube blind-write surface, ownership/visibility coverage, and current limitations.