| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
English | Français
A minimal, security-focused HTTP proxy in front of the Docker socket. Client containers are assigned a profile from their Docker labels and shared network IP addresses; access is denied unless it is explicitly granted.
Instead of mounting /var/run/docker.sock directly into an application, grant the Docker API families it needs and, when necessary, restrict those permissions to a precise set of target containers.
cerede2000/docker-socket-proxy:latest
ghcr.io/cerede2000/docker-socket-proxy:latest
Docker Hub is the primary registry; GitHub Container Registry is also available. Both images support linux/amd64 and linux/arm64, are built with Go, and run unprivileged on distroless/static-debian13:nonroot.
latest follows main. A Git release vX.Y.Z additionally publishes immutable X.Y.Z and X.Y tags to both registries.
The integration branch publishes only the mutable integration tag. It never replaces latest or a release tag.
Release 1.2.0 tightens several permissions and may require profile changes. Container inspection needs allow_inspect: true; creating exec sessions needs both exec: true and post: true; and scoped profiles cannot perform global image, volume, or network writes. Unknown CLI profile options are rejected so that a typo cannot silently produce an unintended policy.
Review the complete migration checklist in CHANGELOG.md before moving an existing deployment from 1.1.2 or an older image, then pin the immutable 1.2.0 tag rather than latest.
The published image is continuously analysed by Docker Scout. The live report is linked rather than hard-coded here, so its result always reflects current image and vulnerability data.
Most Docker socket proxies only filter Docker API endpoint families. This project adds two complementary layers:
This lets an operator such as Portainer retain broad access where it is genuinely needed, while Traefik Manager can inspect and restart only traefik. A cached container-name / ID mapping keeps normal scoped checks fast and is applied consistently to lists, events, and direct requests.
Create profiles.yml, then start the proxy. The :ro socket mount only prevents replacement of the Unix socket file; it does not make Docker API calls read-only. The proxy policy is the security boundary.
services:
docker-socket-proxy:
image: cerede2000/docker-socket-proxy:latest
container_name: docker-socket-proxy
user: "1000:998" # adapt to an UID:GID allowed to read the host socket
read_only: true
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
tmpfs:
- /tmp
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./profiles.yml:/config/profiles.yml:ro
networks:
- socketproxy
restart: unless-stopped
networks:
socketproxy:
internal: trueThe configured account must be able to access the host Docker socket. On Linux, inspect it with stat -c '%u:%g' /var/run/docker.sock, then adapt user. On some hosts, a derived image that creates a group with the socket's real GID is preferable.
Add either label to the client container:
labels:
socketproxy.role: my-profile
# or: socketproxy.service: my-profileProfiles are reloaded automatically after a profiles.yml change. Client IP discovery occurs at startup, after relevant Docker events, and periodically.
Traefik receives only the read access required by its Docker provider. Traefik Manager can inspect and restart only the traefik container; it cannot affect other containers or create an exec session.
compose.yml:
services:
docker-socket-proxy:
image: cerede2000/docker-socket-proxy:latest
container_name: docker-socket-proxy
user: "1000:998" # adapt to the host
read_only: true
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
tmpfs: [/tmp]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./profiles.yml:/config/profiles.yml:ro
networks: [socketproxy]
restart: unless-stopped
traefik:
image: traefik:v3
container_name: traefik
command:
- --providers.docker=true
- --providers.docker.endpoint=tcp://docker-socket-proxy:2375
- --providers.docker.exposedbydefault=false
labels:
socketproxy.role: traefik
networks: [socketproxy, frontend]
restart: unless-stopped
traefik-manager:
image: ghcr.io/chr0nzz/traefik-manager:latest
container_name: traefik-manager
environment:
DOCKER_HOST: tcp://docker-socket-proxy:2375
RESTART_METHOD: proxy
TRAEFIK_CONTAINER: traefik
labels:
socketproxy.role: traefik-manager
networks: [socketproxy, frontend]
restart: unless-stopped
networks:
socketproxy:
internal: true
frontend:
external: trueprofiles.yml:
traefik:
ping: true
version: true
containers: true
allow_inspect: true
networks: true
events: true
session: true
traefik-manager:
ping: true
version: true
containers: true
allow_inspect: true
post: true
allow_restart: true
container_scope: allowlist
allowed_containers:
- traefikcontainer_name: traefik and allowed_containers: [traefik] must match exactly. The traefik-manager profile grants neither start nor stop: only POST /containers/traefik/restart is allowed.
| Variable | Default | Description |
|---|---|---|
| DOCKER_SOCKET_PATH | /var/run/docker.sock | Docker Unix socket path |
| PROXY_PORT | 2375 | Listen port and built-in healthcheck port |
| PROXY_LISTEN | — | Full listen address; takes precedence over PROXY_PORT |
| SOCKETPROXY_PROFILE_FILE | /config/profiles.yml | YAML profile file |
| DISCOVER_INTERVAL | 30s | Container rediscovery interval; Go duration (15s) or seconds (15) |
| EVENT_DEBOUNCE_DELAY | 100ms | Docker event debounce delay; Go duration or milliseconds |
The equivalent flags take precedence over environment variables: --listen, --socket, --profiles, --discover-interval, and --debounce-delay.
The healthcheck calls http://127.0.0.1:$PROXY_PORT/version. If --listen or PROXY_LISTEN uses another port, set PROXY_PORT to that same port.
Profiles can also be defined in the container command. Use --<profile>.<option>=<value> or --proxy-<profile>.<option>=<value>.
command:
- --traefik.ping=1
- --traefik.containers=1
- --traefik-manager.container_scope=allowlist
- --traefik-manager.allowed_containers=traefikCLI lists use comma-separated names. container_rule accepts name:deny or name:readonly. YAML is generally easier to maintain for long-lived configurations.
Every API family is disabled by default. YAML booleans (true / false) are recommended.
| Option | Docker API family |
|---|---|
| ping | /_ping |
| version | /version |
| info | /info |
| events or event | /events |
| auth | /auth |
| build | /build |
| commit | /commit |
| configs | /configs |
| containers | /containers (general family; sensitive sub-routes remain separately gated) |
| distribution | /distribution |
| exec | /exec |
| images | /images |
| networks | /networks |
| nodes | /nodes |
| plugins | /plugins |
| secrets | /secrets |
| services | /services |
| session | /session |
| swarm | /swarm |
| system | /system |
| tasks | /tasks |
| volumes | /volumes |
Generic write methods (POST, PUT, PATCH, DELETE) remain forbidden even if a family is enabled, unless post: true is set. Narrow container lifecycle permissions are independent from that broad switch and can be granted while post: false.
| Container option | Route | Requires post |
|---|---|---|
| allow_archive | /containers/{id}/archive | GET/HEAD: no; PUT: yes |
| allow_changes | /containers/{id}/changes | no |
| allow_export | /containers/{id}/export | no |
| allow_inspect | /containers/{id}/json | no |
| allow_logs | /containers/{id}/logs | no |
| allow_top | /containers/{id}/top | no |
| allow_start | /containers/{id}/start | no |
| allow_stop | /containers/{id}/stop | no |
| allow_restart | /containers/{id}/restart | no |
| allow_pause | /containers/{id}/pause | no |
| allow_unpause | /containers/{id}/unpause | no |
| allow_kill | /containers/{id}/kill | no |
All these options default to false. allow_restarts remains an alias for allow_restart; unlike LinuxServer's grouped switch, it deliberately does not silently grant stop or kill. Grant those operations explicitly when required.
Creating an exec session with POST /containers/{id}/exec requires all three explicit grants: containers: true, exec: true, and post: true. allow_all never enables exec.
GET /containers/{id}/stats deliberately remains part of the general containers read permission. It exposes runtime telemetry, follows the configured container scope, and has no independent allow_stats switch.
allow_all: true is a grouped but scoped convenience shortcut for every allow_* option in the table. It is deliberately not a global Docker permission: it does not enable containers, exec, post, any other API family, or bypass container scopes. Archive upload and other generic writes therefore still require post: true. Treat it as a high-impact permission: export can read the complete container filesystem and archive reads can disclose arbitrary files inside the selected container.
Minimal lifecycle-only example:
container-operator:
ping: true
version: true
containers: true
post: false
allow_start: true
allow_stop: true
allow_restart: true
allow_pause: true
allow_unpause: trueComplete targeted container controls, without enabling unrelated Docker API families:
container-manager:
containers: true
post: true
allow_all: trueapirewrite forces a Docker API version for a profile, for example apirewrite: "1.53".
Names are Docker container names without the / prefix. Scope rules apply to lists, events, inspect, logs, stats, exec, network operations, and targeted actions.
Container scope applies only where a Docker request can be tied to a container. For a scoped profile, global container operations (create, prune) and destructive image, volume, or non-targeted network writes are denied. Read access to the images, volumes, and global networks families is not filtered per container. Avoid granting these families together with post: true unless the client genuinely administers the whole host.
all is the default. The profile keeps its rights over every container; add a deny rule to remove a critical target.
portainer:
ping: true
version: true
containers: true
allow_inspect: true
images: true
networks: true
post: true
allow_start: true
allow_stop: true
allow_restart: true
container_scope: all
container_rules:
- name: docker-socket-proxy
access: denyContainers absent from allowed_containers are hidden and inaccessible.
traefik-manager:
containers: true
allow_inspect: true
post: true
allow_restart: true
container_scope: allowlist
allowed_containers: [traefik]Containers in blocked_containers are hidden and every operation targeting them is rejected.
dockhand:
ping: true
containers: true
allow_inspect: true
events: true
post: true
allow_start: true
allow_stop: true
allow_restart: true
container_scope: blacklist
blocked_containers: [docker-socket-proxy]container_rules takes precedence over the scope. deny fully hides the target. readonly keeps it visible in lists and events, and permits only inspect, logs, stats, top, and changes; actions, exec, archives, and attach remain forbidden.
dockhand:
containers: true
allow_inspect: true
events: true
post: true
allow_start: true
allow_stop: true
allow_restart: true
container_scope: blacklist
blocked_containers: [docker-socket-proxy]
container_rules:
- name: dockman
access: readonlyA target cannot appear in both blocked_containers and container_rules. Valid access values are deny and readonly. container_scope: all cannot contain an allowlist or blacklist; allowlist cannot contain blocked_containers; blacklist cannot contain allowed_containers.
When any scope is active (allowlist, blacklist, or a named rule), global create and prune operations are rejected so they cannot bypass the target restriction.
The proxy is designed for local Docker Engine communication:
Docker client -- private HTTP --> docker-socket-proxy -- Unix socket --> dockerd
This is the same model used by the Tecnativa and LinuxServer socket proxies: network isolation and API filtering replace TLS termination for a port that must never be published. If you need cross-host access, deploy a local proxy per host rather than extending this port; client/profile association relies on local Docker networks.
distroless/static-debian13:nonroot fits this design. The Go binary is built with CGO_ENABLED=0, with no dependency on glibc, OpenSSL, or a CA store. Adding CA certificates would not strengthen this configuration; they would only become useful if a future feature introduced outgoing HTTPS or mTLS.
The proxy logs profile discovery and denials. A client with no role, an unknown role, or no shared network with the proxy receives 403 Forbidden.
go test -race ./...
go vet ./...Licensed under the MIT License.
| Back | FazBrowse Home | New Git URL |