Docker Socket Proxy - CI/CD Security
1.2K
Secure Docker proxy for CI/CD, DevOps and multi-tenant environments with advanced and granular filtering system.
A professional Docker socket proxy with advanced regex filtering, specifically designed to secure CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI, etc.) and cloud-native environments. Inspired by Tecnativa/docker-socket-proxyโ , implemented in high-performance Go with Gin and Resty.
๐ณ Docker Hub: hypolas/proxy-dockerโ ๐ฆ GitHub: hypolas/docker-proxyโ
Ideal for securing your CI/CD pipelines by exposing only the necessary Docker functionalities:
This Docker proxy offers advanced security for your Docker environments through:
Ideal for cloud-native security, Kubernetes, Docker Swarm, PaaS, and CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI, Azure DevOps).
# Block mounting sensitive directories
export DKRPRX__VOLUMES__DENIED_PATHS="^/etc/.*,^/root/.*,^/home/.*"
# Allow only images from a private registry
export DKRPRX__CONTAINERS__ALLOWED_IMAGES="^registry.company.com/.*"
# Block :latest tag
export DKRPRX__IMAGES__DENIED_TAGS="^latest$"
# Block privileged containers
export DKRPRX__CONTAINERS__DENY_PRIVILEGED="true"
# Require specific labels
export DKRPRX__CONTAINERS__REQUIRE_LABELS="env=production,team=backend"
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
The developer disclaims all liability concerning:
You use this proxy at your own risk. It is your responsibility to:
go mod download
go build -o docker-proxy ./cmd/docker-proxy
Configuration is done via environment variables:
| Variable | Description | Default |
|---|---|---|
LISTEN_SOCKET | ๐ Recommended: Unix socket to listen on (format: unix:///path or /path). Takes priority over LISTEN_ADDR. More secure than TCP. | - |
LISTEN_ADDR | TCP listen address (less secure, use LISTEN_SOCKET if possible) | :2375 |
DOCKER_SOCKET | Path to Docker socket (formats: unix:///path, /path, or tcp://host:port) | unix:///var/run/docker.sock |
LOG_LEVEL | Log level (debug, info, warn, error) | info |
API_VERSION | Docker API version (auto-detected if not set) | Auto-detection |
SOCKET_PERMS | Permissions for Unix socket created by proxy (octal format) | 0666 |
๐ Security: Prefer
LISTEN_SOCKET(Unix socket) overLISTEN_ADDR(TCP). Unix sockets offer better permission control via the filesystem and avoid network exposure.
Allowed by default (value: 1):
EVENTS - Docker eventsPING - HealthcheckVERSION - Docker versionDenied by default (value: 0), must be explicitly enabled:
AUTH - AuthenticationBUILD - Image buildingCOMMIT - Container commitCONFIGS - Swarm configsCONTAINERS - Container managementDISTRIBUTION - Image distributionEXEC - Command executionIMAGES - Image managementINFO - System informationNETWORKS - Network managementNODES - Swarm nodesPLUGINS - Docker pluginsSECRETS - Swarm secretsSERVICES - Swarm servicesSESSION - SessionsSWARM - Swarm modeSYSTEM - Docker systemTASKS - Swarm tasksVOLUMES - Volume managementGET, HEAD: Always allowed (read-only)POST: Default 0 (set POST=1 to enable)DELETE: Default 0 (set DELETE=1 to enable)PUT, PATCH: Default 0 (set PUT=1 to enable)export CONTAINERS=1
export IMAGES=1
./docker-proxy
export CONTAINERS=1
export IMAGES=1
export POST=1
export DELETE=1
./docker-proxy
# Listen on Unix socket instead of TCP
export LISTEN_SOCKET=unix:///tmp/docker-proxy.sock
export CONTAINERS=1
export IMAGES=1
./docker-proxy
# Test with curl
curl --unix-socket /tmp/docker-proxy.sock http://localhost/v1.41/containers/json
๐ Security:
LISTEN_SOCKETalways takes precedence overLISTEN_ADDR. Unix sockets avoid network exposure and offer better permission control.โ ๏ธ Docker: If you use
LISTEN_SOCKET=unix:///tmp/docker-proxy.sock, you must mount the corresponding directory in volumes:-v /tmp:/tmp. The path in theunix:///pathformat must match the mounted volume.
Proxy configuration sample:
services:
docker-proxy:
build: .
image: hypolas/proxy-docker:latest
container_name: docker-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /tmp:/tmp # โ ๏ธ IMPORTANT: must match the path in LISTEN_SOCKET
environment:
# Basic configuration (unix:// format recommended)
- LISTEN_SOCKET=unix:///tmp/docker_proxy.sock
- DOCKER_SOCKET=unix:///var/run/docker.sock
โ ๏ธ Important: The mounted volume (
/tmp:/tmp) must match the path defined inLISTEN_SOCKET. If you useLISTEN_SOCKET=unix:///tmp/docker_proxy.sock, you must mount/tmp:/tmp.
graph TB
subgraph "Agent Container"
agent[CI/CD agent]
end
subgraph "Proxy Container"
proxy[docker-proxy]
proxySock[[unix:///tmp/docker_proxy.sock]]
end
subgraph "Docker Host"
dockerSock[[/var/run/docker.sock]]
engine[(Docker Engine)]
end
start((Start))
start --> agent[CI/CD agent]
agent -->|Docker API calls| proxySock
proxy <-- binds --> proxySock
proxy -->|validates & forwards| dockerSock
dockerSock -->|native API| engine
engine -->|responses| proxy
proxy -->|responses| agent
# .github/workflows/docker.yml
name: Docker Build
on: [push]
services:
docker-proxy:
image: hypolas/proxy-docker:latest
env:
CONTAINERS: 1
IMAGES: 1
BUILD: 1
POST: 1
# Force private registry
DKRPRX__CONTAINERS__ALLOWED_IMAGES: "^registry.company.com/.*"
# Block :latest
DKRPRX__IMAGES__DENIED_TAGS: "^latest$"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
env:
DOCKER_HOST: tcp://docker-proxy:2375
run: docker build -t registry.company.com/app:${{ github.sha }} .
# .gitlab-ci.yml
variables:
DOCKER_HOST: tcp://docker-proxy:2375
services:
- name: hypolas/proxy-docker:latest
alias: docker-proxy
variables:
CONTAINERS: "1"
IMAGES: "1"
BUILD: "1"
POST: "1"
DKRPRX__CONTAINERS__ALLOWED_IMAGES: "^registry.company.com/.*"
build:
script:
- docker build -t registry.company.com/app:$CI_COMMIT_SHA .
- docker push registry.company.com/app:$CI_COMMIT_SHA
pipeline {
agent any
environment {
DOCKER_HOST = 'tcp://docker-proxy:2375'
}
stages {
stage('Build') {
steps {
sh 'docker build -t registry.company.com/app:${BUILD_NUMBER} .'
}
}
}
}
services:
docker-proxy:
build: .
ports:
- "2375:2375"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1
- IMAGES=1
- NETWORKS=1
- VOLUMES=1
- POST=0
- DELETE=0
- LOG_LEVEL=info
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o docker-proxy ./cmd/docker-proxy
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/docker-proxy .
EXPOSE 2375
CMD ["./docker-proxy"]
.
โโโ cmd/
โ โโโ docker-proxy/ # Application entry point
โ โโโ main.go
โโโ config/ # Configuration and env var loading
โ โโโ config.go
โโโ internal/
โ โโโ middleware/ # Gin middlewares
โ โ โโโ acl.go # Access control
โ โ โโโ logging.go # Structured logging
โ โโโ proxy/ # Proxy handler
โ โโโ handler.go
โโโ pkg/
โโโ rules/ # Access rules engine
โโโ matcher.go
The proxy's strength: an extremely granular and powerful filtering system.
Unlike basic proxies, this proxy allows fine-grained control of every aspect of Docker operations via regex patterns:
# Allow only specific named volumes
export DKRPRX__VOLUMES__ALLOWED_NAMES="^data-.*,^app-.*,^logs-.*"
# Block mounting sensitive system directories
export DKRPRX__VOLUMES__DENIED_PATHS="^/etc/.*,^/root/.*,^/sys/.*,^/proc/.*,^/var/run/.*"
# Allow only bind mounts in /data
export DKRPRX__VOLUMES__ALLOWED_PATHS="^/data/.*"
# Restrict to local drivers
export DKRPRX__VOLUMES__ALLOWED_DRIVERS="local"
# Allow only images from private registry with semantic versioning
export DKRPRX__CONTAINERS__ALLOWED_IMAGES="^registry.company.com/.*:v[0-9]+\.[0-9]+\.[0-9]+$"
# Block any image with :latest or :dev tag
export DKRPRX__CONTAINERS__DENIED_IMAGES=".*:(latest|dev|test)$"
# Require container names prefixed by environment
export DKRPRX__CONTAINERS__ALLOWED_NAMES="^(prod|staging|dev)-.*"
# Require mandatory labels
export DKRPRX__CONTAINERS__REQUIRE_LABELS="env=production,team=backend,cost-center=IT-001"
# Block privileged containers and host network
export DKRPRX__CONTAINERS__DENY_PRIVILEGED="true"
export DKRPRX__CONTAINERS__DENY_HOST_NETWORK="true"
# Allow only approved registries
export DKRPRX__IMAGES__ALLOWED_REPOS="^(docker\.io/library|registry\.company\.com)/.*"
# Block insecure registries
export DKRPRX__IMAGES__DENIED_REPOS=".*\.(cn|ru|suspicious)/"
# Allow only versioned tags (semver)
export DKRPRX__IMAGES__ALLOWED_TAGS="^v[0-9]+\.[0-9]+\.[0-9]+$"
# Block development tags
export DKRPRX__IMAGES__DENIED_TAGS="^(latest|dev|test|alpha|beta|rc).*"
# Allow only application networks
export DKRPRX__NETWORKS__ALLOWED_NAMES="^app-.*"
# Block host network (security)
export DKRPRX__NETWORKS__DENIED_NAMES="^host$"
# Restrict to bridge and overlay drivers
export DKRPRX__NETWORKS__ALLOWED_DRIVERS="bridge,overlay"
For complex configurations, use JSON:
{
"volumes": {
"allowed_names": ["^data-.*", "^app-.*"],
"denied_paths": ["^/etc/.*", "^/root/.*", "^/sys/.*", "^/proc/.*"],
"allowed_paths": ["^/data/.*", "^/mnt/volumes/.*"]
},
"containers": {
"allowed_images": ["^registry.company.com/.*:v[0-9]+\\.[0-9]+\\.[0-9]+$"],
"denied_images": [".*:(latest|dev)$"],
"require_labels": {
"env": "production",
"approved": "true"
},
"deny_privileged": true,
"deny_host_network": true
},
"networks": {
"allowed_names": ["^app-.*"],
"allowed_drivers": ["bridge", "overlay"]
},
"images": {
"allowed_repos": ["^registry.company.com/.*"],
"denied_tags": ["^latest$"]
}
}
export FILTERS_CONFIG=./filters.json
Note: Environment variables take priority over JSON.
TENANT_ID="client-123"
export DKRPRX__VOLUMES__ALLOWED_NAMES="^${TENANT_ID}-.*"
export DKRPRX__CONTAINERS__ALLOWED_NAMES="^${TENANT_ID}-.*"
export DKRPRX__CONTAINERS__REQUIRE_LABELS="tenant=${TENANT_ID}"
export DKRPRX__NETWORKS__ALLOWED_NAMES="^${TENANT_ID}-.*"
# Only versioned images from private registry
export DKRPRX__CONTAINERS__ALLOWED_IMAGES="^registry.prod.company.com/.*:v[0-9]+\.[0-9]+\.[0-9]+$"
# Only mounts in /data/prod
export DKRPRX__VOLUMES__ALLOWED_PATHS="^/data/prod/.*"
# Mandatory labels
export DKRPRX__CONTAINERS__REQUIRE_LABELS="env=production,approved=true,security-scan=passed"
# Enhanced security
export DKRPRX__CONTAINERS__DENY_PRIVILEGED="true"
export DKRPRX__CONTAINERS__DENY_HOST_NETWORK="true"
# Allow build but not latest
export DKRPRX__IMAGES__DENIED_TAGS="^latest$"
# Allow only CI registry
export DKRPRX__IMAGES__ALLOWED_REPOS="^registry.ci.company.com/.*"
# Block sensitive volumes
export DKRPRX__VOLUMES__DENIED_PATHS="^/(etc|root|home|sys|proc)/.*"
The filtering system allows as precise control as needed for your environment!
The proxy applies several protections by default to prevent privilege escalation:
The following paths are blocked by default:
/var/run/docker.sock/run/docker.sockThe docker-proxy container itself is protected against any manipulation:
If the proxy uses a dedicated network, it is also protected.
# Proxy container name (default: docker-proxy)
export PROXY_CONTAINER_NAME="docker-proxy"
# Proxy network name (optional)
export PROXY_NETWORK_NAME="docker-proxy-network"
To disable all protections:
export DKRPRX__DISABLE_DEFAULTS="true"
To explicitly allow Docker socket:
export DKRPRX__VOLUMES__ALLOWED_PATHS="^/var/run/docker\\.sock$"
:ro)To test the proxy:
# Start proxy with containers in read-only
export CONTAINERS=1
./docker-proxy
# In another terminal
curl http://localhost:2375/v1.41/containers/json
# Test a denied endpoint
curl http://localhost:2375/v1.41/images/json # 403 Forbidden
# Start proxy on Unix socket
export LISTEN_SOCKET=/tmp/docker-proxy.sock
export CONTAINERS=1
./docker-proxy
# In another terminal
curl --unix-socket /tmp/docker-proxy.sock http://localhost/v1.41/containers/json
# Or with Docker CLI
export DOCKER_HOST=unix:///tmp/docker-proxy.sock
docker ps
This proxy is specifically designed to secure CI/CD pipelines. See CICD_EXAMPLES.mdโ for detailed examples:
Typical use cases:
:latest, :dev, :test tagsSee SECURITY.mdโ for:
Dual License: GPL-3.0 (Free) OR Commercial
Contact [email protected]โ for commercial licensing.
Developed in high-performance Go (Golang) with Gin framework and Resty HTTP client, this proxy offers a robust API wrapper around the Docker Engine API and Docker SDK. Native support for Unix sockets and TCP sockets for flexible integration.
Implements zero-trust, least privilege, and defense in depth principles to prevent:
Native compatibility with:
Support for:
Developed for DevOps, SRE and Security teams seeking granular control over Docker in multi-tenant, cloud-native and CI/CD environments.
Content type
Image
Digest
sha256:601de334fโฆ
Size
18.6 MB
Last updated
10 months ago
docker pull hypolas/proxy-docker