English | Français
Proxy HTTP minimaliste et sécurisé devant le socket Docker. Les clients sont associés à un profil par leur adresse IP, découverte depuis leurs labels Docker ; tout accès qui n'est pas explicitement accordé est refusé.
Le projet permet de limiter les familles d'API Docker, puis de restreindre les opérations à certains conteneurs. Il évite ainsi de monter directement /var/run/docker.sock dans une application.
cerede2000/docker-socket-proxy:latest
ghcr.io/cerede2000/docker-socket-proxy:latest
La première référence est publiée sur Docker Hub ; la seconde sur GitHub Container Registry. Les deux images sont multi-architecture (linux/amd64 et linux/arm64), construites avec Go et exécutées sans privilèges dans distroless/static-debian13:nonroot.
latest suit main. Chaque release Git vX.Y.Z publie également les tags Docker immuables X.Y.Z et X.Y sur les deux registres.
La branche integration publie uniquement le tag mutable integration. Elle ne remplace jamais latest ni un tag de release.
La release 1.2.0 renforce plusieurs permissions et peut nécessiter une adaptation des profils. L'inspection d'un conteneur demande allow_inspect: true ; la création d'une session exec demande à la fois exec: true et post: true ; enfin, les profils à portée limitée ne peuvent plus effectuer d'écritures globales sur les images, volumes ou réseaux. Les options de profil CLI inconnues sont rejetées afin qu'une faute de frappe ne produise pas silencieusement une politique inattendue.
Consultez la procédure de migration complète dans CHANGELOG.md avant de remplacer une image 1.1.2 ou antérieure, puis épinglez le tag immuable 1.2.0 plutôt que latest.
L'image publiée est analysée en continu par Docker Scout. Le rapport est lié ici plutôt que figé dans le README : son résultat suit les mises à jour des vulnérabilités et de l'image.
Les socket proxies classiques limitent principalement les familles d'endpoints Docker. Celui-ci ajoute deux niveaux de contrôle complémentaires :
- Le droit est attribué au client par profil, automatiquement via son label Docker (
socketproxy.role). - Le droit est ensuite limité à la cible : tous les conteneurs, allowlist, blacklist, refus explicite, ou accès strictement en lecture seule par conteneur.
Un outil peut donc disposer d'un accès Docker étendu lorsque c'est nécessaire (Portainer, un opérateur), tandis que Traefik Manager peut uniquement consulter et redémarrer traefik. Le contrôle nom/ID est maintenu en cache et s'applique aux listes, événements et appels directs.
- Aucun droit n'est accordé implicitement.
- Seuls les conteneurs portant
socketproxy.role(ou l'aliassocketproxy.service) et correspondant à un profil sont autorisés. - Le proxy ne retient que les IP partagées avec ses propres réseaux Docker.
- Les listes, les événements et les opérations ciblant un conteneur respectent la même portée.
- Le cache interne nom / ID de conteneur évite une requête Docker supplémentaire pour les vérifications usuelles.
- Le contrôle local
/versionest accepté sans profil uniquement depuis l'interface loopback. N'utilisez pas le réseau hôte et ne publiez pas le port2375. - Pour les profils à portée limitée, les événements Docker non liés à un conteneur sont volontairement omis car ils ne peuvent pas être rattachés sûrement à une cible autorisée.
Créez un fichier profiles.yml, puis lancez le proxy. Le montage :ro empêche seulement de remplacer le fichier socket Unix ; il ne rend pas les appels à l'API Docker accessibles en lecture seule. La politique du proxy constitue la barrière de sécurité.
services:
docker-socket-proxy:
image: cerede2000/docker-socket-proxy:latest
container_name: docker-socket-proxy
user: "1000:998" # adapter à l'UID:GID pouvant lire le socket sur l'hôte
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: trueLe compte configuré avec user doit avoir accès au socket Docker de l'hôte. Vérifiez son UID et son GID avec stat -c '%u:%g' /var/run/docker.sock sous Linux, puis adaptez la valeur. Sur certaines installations, il est préférable de construire une image dérivée qui crée un groupe portant le GID réel du socket.
Ajoutez l'un des deux labels suivants au conteneur client :
labels:
socketproxy.role: mon-profil
# ou : socketproxy.service: mon-profilLe profil est rechargé automatiquement lorsque profiles.yml est modifié. La découverte des IP intervient au démarrage, lors des événements Docker pertinents, et périodiquement.
Cet exemple sépare les rôles : Traefik peut lire les informations nécessaires au provider Docker ; Traefik Manager peut seulement consulter et redémarrer le conteneur traefik. Il ne peut ni agir sur les autres conteneurs ni lancer d'exec.
compose.yml :
services:
docker-socket-proxy:
image: cerede2000/docker-socket-proxy:latest
container_name: docker-socket-proxy
user: "1000:998" # à adapter à l'hôte
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 et allowed_containers: [traefik] doivent correspondre exactement. Le profil traefik-manager ne permet pas start ou stop : seul POST /containers/traefik/restart est admis.
| Variable | Défaut | Description |
|---|---|---|
DOCKER_SOCKET_PATH |
/var/run/docker.sock |
Chemin du socket Docker à joindre |
PROXY_PORT |
2375 |
Port d'écoute et port du healthcheck intégré |
PROXY_LISTEN |
— | Adresse d'écoute complète ; prioritaire sur PROXY_PORT |
SOCKETPROXY_PROFILE_FILE |
/config/profiles.yml |
Fichier YAML des profils |
DISCOVER_INTERVAL |
30s |
Période de redécouverte des conteneurs ; durée Go (15s) ou nombre de secondes (15) |
EVENT_DEBOUNCE_DELAY |
100ms |
Délai de regroupement des événements Docker ; durée Go ou nombre de millisecondes |
Les arguments suivants sont disponibles et prioritaires sur les variables correspondantes : --listen, --socket, --profiles, --discover-interval et --debounce-delay.
Le healthcheck appelle http://127.0.0.1:$PROXY_PORT/version. Si --listen ou PROXY_LISTEN utilise un autre port, renseignez PROXY_PORT avec ce même port.
Les profils peuvent aussi être définis dans la commande du conteneur. Le format est --<profil>.<option>=<valeur> ou --proxy-<profil>.<option>=<valeur>.
command:
- --traefik.ping=1
- --traefik.containers=1
- --traefik-manager.container_scope=allowlist
- --traefik-manager.allowed_containers=traefikLes listes CLI acceptent des noms séparés par des virgules. container_rule accepte nom:deny ou nom:readonly. Le YAML reste préférable pour les configurations maintenues dans le temps.
Chaque famille est désactivée par défaut. Une valeur YAML booléenne (true/false) est recommandée.
| Option | Famille d'API Docker autorisée |
|---|---|
ping |
/_ping |
version |
/version |
info |
/info |
events ou event |
/events |
auth |
/auth |
build |
/build |
commit |
/commit |
configs |
/configs |
containers |
/containers (famille générale ; les sous-routes sensibles restent contrôlées séparément) |
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 |
Les écritures génériques (POST, PUT, PATCH, DELETE) restent interdites même lorsqu'une famille est activée, sauf si post: true est ajouté. Les actions ciblées de cycle de vie sont indépendantes de ce droit large et peuvent être accordées avec post: false.
| Option conteneur | Route | Nécessite post |
|---|---|---|
allow_archive |
/containers/{id}/archive |
GET/HEAD : non ; PUT : oui |
allow_changes |
/containers/{id}/changes |
non |
allow_export |
/containers/{id}/export |
non |
allow_inspect |
/containers/{id}/json |
non |
allow_logs |
/containers/{id}/logs |
non |
allow_top |
/containers/{id}/top |
non |
allow_start |
/containers/{id}/start |
non |
allow_stop |
/containers/{id}/stop |
non |
allow_restart |
/containers/{id}/restart |
non |
allow_pause |
/containers/{id}/pause |
non |
allow_unpause |
/containers/{id}/unpause |
non |
allow_kill |
/containers/{id}/kill |
non |
Toutes ces options valent false par défaut. allow_restarts reste un alias de allow_restart ; contrairement au commutateur groupé de LinuxServer, il n'accorde pas implicitement stop ou kill. Ces droits doivent être explicitement ajoutés.
La création d'une session exec via POST /containers/{id}/exec exige les trois droits explicites containers: true, exec: true et post: true. allow_all n'active jamais exec.
GET /containers/{id}/stats reste volontairement inclus dans le droit général de lecture containers. Cette route expose la télémétrie d'exécution, respecte la portée de conteneurs configurée et ne possède pas de commutateur allow_stats distinct.
allow_all: true est un raccourci groupé mais limité à la portée pour toutes les options allow_* du tableau. Ce n'est volontairement pas un droit Docker global : il n'active ni containers, ni exec, ni post, ni une autre famille d'API et ne contourne pas les portées de conteneurs. L'envoi d'une archive et les autres écritures génériques nécessitent donc toujours post: true. Traitez-le comme un droit à fort impact : export peut lire tout le système de fichiers du conteneur et la lecture d'archive peut exposer n'importe quel fichier de la cible.
Exemple minimal limité au cycle de vie :
container-operator:
ping: true
version: true
containers: true
post: false
allow_start: true
allow_stop: true
allow_restart: true
allow_pause: true
allow_unpause: trueTous les contrôles ciblés des conteneurs, sans activer les autres familles Docker :
container-manager:
containers: true
post: true
allow_all: trueapirewrite force une version d'API Docker pour un profil, par exemple apirewrite: "1.53".
Les noms sont les noms Docker sans le préfixe /. Les règles s'appliquent aux listes, événements, inspections, logs, statistiques, exec, opérations réseau et actions ciblées.
La portée conteneur s'applique uniquement lorsqu'une requête Docker peut être rattachée à un conteneur. Pour un profil limité, les opérations globales sur les conteneurs (create, prune) et les écritures destructrices sur les images, volumes ou réseaux non ciblés sont refusées. Les lectures des familles globales images, volumes et networks ne sont pas filtrées par conteneur. Évitez d'accorder ces familles avec post: true sauf si le client administre réellement tout l'hôte.
all est la valeur par défaut. Le profil conserve ses droits sur tous les conteneurs ; utilisez une règle deny pour retirer une cible critique.
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: denyLes conteneurs absents de allowed_containers sont invisibles et inaccessibles.
traefik-manager:
containers: true
allow_inspect: true
post: true
allow_restart: true
container_scope: allowlist
allowed_containers:
- traefikLes conteneurs de blocked_containers sont invisibles et toute opération les visant est refusée.
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-proxycontainer_rules est prioritaire sur la portée. deny masque totalement la cible. readonly laisse visibles les listes et événements et autorise uniquement inspect, logs, stats, top et changes ; les actions, exec, les archives et attach sont refusés.
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: readonlyUne cible ne peut pas figurer à la fois dans blocked_containers et container_rules. Les valeurs d'accès admises sont deny et readonly. container_scope: all ne peut pas contenir de liste blanche ou noire ; allowlist ne peut pas contenir blocked_containers et blacklist ne peut pas contenir allowed_containers.
Lorsqu'une portée est active (allowlist, blacklist ou une règle nominative), les opérations globales create et prune sont refusées afin de ne pas contourner la restriction par conteneur.
Le proxy écrit dans ses journaux la découverte des rôles et les refus. Un client sans rôle, avec un rôle inconnu, ou ne partageant aucun réseau avec le proxy reçoit 403 Forbidden.
Le proxy est conçu pour une communication locale au moteur Docker :
client Docker -- HTTP privé --> docker-socket-proxy -- socket Unix --> dockerd
- La liaison entre le proxy et Docker utilise
DOCKER_SOCKET_PATH(par défaut/var/run/docker.sock). Elle ne traverse pas le réseau et n'utilise donc pas TLS, certificat ou autorité de certification (CA). - Le proxy n'ouvre pas de connexion HTTPS sortante et ne prend pas en charge un moteur Docker distant configuré par
DOCKER_HOST=tcp://…. - Le port
2375est volontairement en HTTP clair : il doit rester accessible uniquement depuis un réseau Docker interne, partagé avec les clients autorisés. N'ajoutez pas deports:et ne l'exposez jamais par Traefik, un load balancer ou Internet. internal: trueréduit l'exposition du réseau, mais tout conteneur qui y est attaché reste un client potentiel : n'y raccordez que le proxy et les services qui ont réellement besoin de l'API Docker.
Cette approche est la même que celle des proxies de référence Tecnativa et LinuxServer : le contrôle d'accès réseau et le filtrage d'API remplacent une terminaison TLS sur un port qui ne doit pas être publié. Si un besoin inter-hôtes apparaît, déployez un proxy local par hôte plutôt que d'étendre ce port : l'association client/profil de ce projet repose sur les réseaux Docker locaux.
La runtime distroless/static-debian13:nonroot est adaptée à ce modèle : le binaire Go est compilé avec CGO_ENABLED=0, sans dépendance à glibc, OpenSSL ni magasin de CA. Ajouter des CA ne renforcerait pas cette configuration ; elles ne deviendraient nécessaires qu'avec une future fonctionnalité HTTPS sortante ou mTLS.
go test -race ./...
go vet ./...Distribué sous licence MIT.