Getting Started
Install sockguard with Docker Compose, Docker Run, Homebrew, or a release binary, and point your apps at the proxy socket.
Installation
Docker Compose (recommended)
services:
sockguard:
image: codeswhat/sockguard:latest
restart: unless-stopped
read_only: true
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
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.yaml:/etc/sockguard/sockguard.yaml:ro
- sockguard-socket:/var/run/sockguard
environment:
- SOCKGUARD_LISTEN_SOCKET=/var/run/sockguard/sockguard.sock
volumes:
sockguard-socket:Docker Run
docker run -d \
--name sockguard \
--read-only \
--cap-drop=ALL \
--security-opt no-new-privileges:true \
--group-add <docker-socket-gid> \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v ./sockguard.yaml:/etc/sockguard/sockguard.yaml:ro \
-v sockguard-socket:/var/run/sockguard \
-e SOCKGUARD_LISTEN_SOCKET=/var/run/sockguard/sockguard.sock \
codeswhat/sockguard:latestSockguard runs as UID 65532 (Chainguard nonroot) inside the container.
Set DOCKER_SOCK_GID before starting Compose (export DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock) on Linux; use stat -f '%g' on macOS), or replace <docker-socket-gid> in the docker run command with that numeric value. The hardening knobs that matter in practice are
--read-only, --cap-drop=ALL, --security-opt no-new-privileges:true, and
your runtime's seccomp/AppArmor/SELinux defaults.
The filtered socket and its named-volume directory are owner-only and owned by UID/GID 65532. A non-root consumer of that socket must also use UID 65532; root consumers can connect as well. If an application must keep another UID, run Sockguard with that matching UID against a pre-owned bind-mounted directory, set listen.socket_mode: "0660" with an explicit listen.socket_gid so a named group can reach the socket, or use an authenticated TCP listener. 0600 and 0660 are the only modes Sockguard accepts, and 0660 without socket_gid is rejected, so a group-shared socket is always a deliberate choice rather than a default.
Homebrew
Install the latest stable native binary on macOS or Linux from the CodesWhat tap:
brew install --cask codeswhat/tap/sockguard
sockguard versionStable releases update the cask automatically. Release candidates and other prereleases remain available from GitHub Releases without moving the stable Homebrew channel.
The cask installs only the sockguard command. It does not create a service, grant Docker socket access, or generate a policy file. Run it under your own service manager after configuring the upstream Docker socket and an explicit policy; Docker Compose remains the recommended production deployment.
The macOS binary is not yet signed and notarized with an Apple Developer ID. After Homebrew verifies the release archive's SHA-256 checksum, the cask removes the quarantine attribute from its staged sockguard binary so Gatekeeper does not block it. This bypass applies only to that installed file. That checksum proves the archive wasn't corrupted or tampered with in transit, it does not prove who published it, so it is not a substitute for Apple Developer ID signing/notarization. If your policy requires Apple notarization, or you don't want the quarantine bypass at all, use the container image or verify the GitHub Releases binary with cosign instead.
Upgrade or remove the cask with:
brew upgrade --cask sockguard
brew uninstall --cask sockguardBinary
Download from GitHub Releases:
sockguard serve -c /etc/sockguard/sockguard.yamlThis guide uses a unix socket because it is the simplest secure deployment. If you expose Sockguard on non-loopback TCP, configure listen.tls for mutual TLS. Plaintext remote TCP requires the two-flag legacy opt-in — both SOCKGUARD_LISTEN_INSECURE_ALLOW_PLAIN_TCP=true and SOCKGUARD_LISTEN_INSECURE_ALLOW_UNAUTHENTICATED_CLIENTS=true.
Connecting Your Apps
Point your Docker consumers at the proxy socket instead of docker.sock:
services:
traefik:
volumes:
- sockguard-socket:/var/run/sockguard:ro
environment:
- DOCKER_HOST=unix:///var/run/sockguard/sockguard.sock
drydock:
volumes:
- sockguard-socket:/var/run/sockguard:ro
environment:
- DD_WATCHER_LOCAL_SOCKET=/var/run/sockguard/sockguard.sockCLI Commands
# Start the proxy (default command)
sockguard serve
# Start with a signed policy and separate pinned trust
sockguard serve --config /etc/sockguard/sockguard.yaml --policy-bundle-trust-config /etc/sockguard/policy-bundle-trust.yaml
# Validate configuration and print the compiled rule table
sockguard validate -c /etc/sockguard/sockguard.yaml
# Offline dry-run: evaluate a single request against the rules,
# show which rule fires and why, without starting the proxy.
sockguard match -c /etc/sockguard/sockguard.yaml \
-X GET --path /v1.45/containers/json
# Runtime self-check: is the upstream daemon answering, is this
# sockguard's own health endpoint up, does the TLS material load
sockguard verify -c /etc/sockguard/sockguard.yaml
# Print version
sockguard versionsockguard match reads the same config, applies the same path
normalization as the running proxy, and reports the decision in
text (default) or JSON (-o json). Use it to sanity-check a
ruleset before any traffic hits the proxy.
sockguard verify is the runtime counterpart to sockguard validate. Where validate is an offline check on a config file,
verify loads the config the way serve does (flags, environment,
file) and then checks that the things it names are reachable right
now. It runs five checks and prints one line each:
| Check | What it does |
|---|---|
config | Loads and validates the effective config, without touching the filesystem paths it names. |
upstream | Builds the upstream endpoints (loading their TLS material), runs the same reachability probe serve runs at startup, asks the Docker API through the same readiness probe /health uses, and resolves the engine flavor. |
listener | Issues GET <health.path> against each configured listener. |
tls | Opens the cert, key, and client CA that each mutual-TLS listener names — the filesystem half of validation that POST /admin/validate deliberately skips. |
image-trust | Reports whether image trust is configured and, when it uses keyless identities, whether the Sigstore trust root loads. |
Each check reports ok, fail, or skip. A skip is a check
that does not apply to this deployment — an opt-in feature that is
off, a listener that is not running — and never changes the exit
code; any fail exits non-zero, so verify works as a container
healthcheck or a deploy gate. Add --json for a machine-readable
report with the same five checks in the same order. --listen-socket
and --upstream-socket override the config the same way they do on
serve.
Verify does not need the proxy to be running: with no listener up,
the listener check skips and everything else still runs, so it is
also a pre-flight check on a host before the first start.
Signed-policy mode requires the candidate and bootstrap trust config to be
different files, including after symlink or hardlink resolution. The trust
file uses policy_bundle.enabled, signing identities, Rekor posture, and the
verification timeout. The signed candidate carries
policy_bundle.signature_path. Rule-generating Tecnativa variables cannot be
combined with this mode; migrate those grants to YAML before signing. See
Signed Policy Bundles for the complete
layout.
Next Steps
- Configuration — YAML config and environment variables
- Presets — Ready-made configs for drydock, Traefik, and more
- Migration — Migrate from Tecnativa or LinuxServer