docker.recipes
Security12 min read

How to Secure Self-Hosted Docker Services

A comprehensive guide to hardening your Docker containers and protecting your self-hosted infrastructure.

01Introduction

Self-hosting puts you in control, but also makes you responsible for security. Docker provides isolation, but misconfiguration can expose your services to attack. This guide covers essential security practices for self-hosted Docker deployments.

02Keep Images Updated

Container images include operating systems and libraries with potential vulnerabilities. Regular updates are your first line of defense.
1# Check for updated images
2docker compose pull
3
4# Update and recreate containers
5docker compose up -d
6
7# Remove old unused images
8docker image prune -a
9
10# Automate with Watchtower (use cautiously)
11services:
12 watchtower:
13 image: containrrr/watchtower
14 volumes:
15 - /var/run/docker.sock:/var/run/docker.sock
16 environment:
17 - WATCHTOWER_CLEANUP=true
18 - WATCHTOWER_SCHEDULE=0 0 4 * * * # 4 AM daily

Watchtower auto-updates can break things. Consider using it only for monitoring (WATCHTOWER_MONITOR_ONLY=true) and updating manually.

03Network Segmentation

Don't put all containers on the same network. Segment by trust level—public-facing services shouldn't directly access databases.
1services:
2 # Public-facing reverse proxy
3 traefik:
4 networks:
5 - frontend
6 - backend
7
8 # Application tier
9 app:
10 networks:
11 - backend
12 - database
13
14 # Database tier - most restricted
15 postgres:
16 networks:
17 - database
18
19networks:
20 frontend:
21 # Exposed to internet via Traefik
22 backend:
23 internal: true # No external access
24 database:
25 internal: true # No external access

Use internal: true for networks that should never be accessible from outside Docker.

04Read-Only Containers

Making the container filesystem read-only prevents attackers from modifying binaries or planting malware. Combine with tmpfs for directories that need writes.
1services:
2 app:
3 image: myapp:1.0
4 read_only: true
5 tmpfs:
6 - /tmp
7 - /var/run
8 volumes:
9 - app_data:/data # Only this is writable
10
11# Not all images support read-only mode
12# Test first: docker run --read-only myapp:1.0

05Drop Linux Capabilities

By default, containers get many Linux capabilities they don't need. Drop all and add back only what's required.
1services:
2 app:
3 image: myapp:1.0
4 cap_drop:
5 - ALL
6 cap_add:
7 - NET_BIND_SERVICE # Only if binding to ports < 1024
8
9 # For web servers, you might need:
10 nginx:
11 cap_drop:
12 - ALL
13 cap_add:
14 - CHOWN
15 - SETGID
16 - SETUID
17 - NET_BIND_SERVICE

06Prevent Privilege Escalation

The no-new-privileges flag prevents processes from gaining additional privileges via setuid binaries or other mechanisms.
1services:
2 app:
3 image: myapp:1.0
4 security_opt:
5 - no-new-privileges:true
6
7# This prevents attacks like:
8# - Exploiting setuid binaries
9# - Using sudo inside containers
10# - Privilege escalation via kernel exploits

07Resource Limits

Limit CPU and memory to prevent denial-of-service attacks and contain misbehaving containers.
1services:
2 app:
3 image: myapp:1.0
4 deploy:
5 resources:
6 limits:
7 cpus: '1'
8 memory: 512M
9 pids: 100 # Prevent fork bombs
10 reservations:
11 cpus: '0.25'
12 memory: 128M

pids limit prevents fork bomb attacks that spawn processes until the system crashes.

08Secrets Management

Never put secrets in environment variables visible in docker inspect. Use Docker secrets or mount secret files.
1services:
2 app:
3 image: myapp:1.0
4 secrets:
5 - db_password
6 - api_key
7 environment:
8 # Reference file, not value
9 - DB_PASSWORD_FILE=/run/secrets/db_password
10
11secrets:
12 db_password:
13 file: ./secrets/db_password.txt
14 api_key:
15 file: ./secrets/api_key.txt
16
17# The secret is mounted at /run/secrets/<name>
18# Never visible in docker inspect or logs

09Protect the Docker Socket

Access to the Docker socket equals root access to the host. Protect it carefully.
1# If you must expose Docker socket, use a proxy
2services:
3 # Socket proxy limits which API calls are allowed
4 docker-proxy:
5 image: tecnativa/docker-socket-proxy
6 environment:
7 - CONTAINERS=1
8 - IMAGES=1
9 - INFO=1
10 - NETWORKS=0
11 - VOLUMES=0
12 - POST=0 # Read-only access
13 volumes:
14 - /var/run/docker.sock:/var/run/docker.sock:ro
15 networks:
16 - docker-api
17
18 # Services connect to proxy, not socket directly
19 traefik:
20 environment:
21 - DOCKER_HOST=tcp://docker-proxy:2375
22 networks:
23 - docker-api
24
25networks:
26 docker-api:
27 internal: true

Any container with Docker socket access can take over the entire host. Treat it like root SSH access.

10Implement Healthchecks

Healthchecks detect compromised or malfunctioning containers. Combined with restart policies, they ensure self-healing.
1services:
2 app:
3 image: myapp:1.0
4 healthcheck:
5 test: ["CMD", "curl", "-f", "http://localhost/health"]
6 interval: 30s
7 timeout: 10s
8 retries: 3
9 start_period: 40s
10 restart: unless-stopped
11
12 postgres:
13 image: postgres:15
14 healthcheck:
15 test: ["CMD-SHELL", "pg_isready -U postgres"]
16 interval: 10s
17 timeout: 5s
18 retries: 5

11Logging and Monitoring

You can't secure what you can't see. Centralize logs and monitor for anomalies.
1services:
2 app:
3 logging:
4 driver: "json-file"
5 options:
6 max-size: "10m"
7 max-file: "5"
8 labels: "service,environment"
9 labels:
10 service: "myapp"
11 environment: "production"
12
13# Consider centralized logging
14 loki:
15 image: grafana/loki:latest
16 volumes:
17 - loki_data:/loki
18
19 promtail:
20 image: grafana/promtail:latest
21 volumes:
22 - /var/lib/docker/containers:/var/lib/docker/containers:ro
23 - /var/run/docker.sock:/var/run/docker.sock

12Security Checklist

Run through this checklist for every deployment: - [ ] Images pinned to specific versions - [ ] Containers running as non-root - [ ] Unnecessary capabilities dropped - [ ] No Docker socket mounted (or proxied) - [ ] Secrets not in environment variables - [ ] Networks segmented appropriately - [ ] Resource limits configured - [ ] Healthchecks implemented - [ ] Logging configured with rotation - [ ] Host firewall configured - [ ] Automatic updates planned