01Security Isn't Optional, Even for Side Projects
021. Never Hardcode Secrets
1# BAD: Hardcoded secrets2services: 3 db: 4 image: postgres:165 environment: 6 - POSTGRES_PASSWORD=supersecret12378# GOOD: Environment variable from .env file9services: 10 db: 11 image: postgres:1612 environment: 13 - POSTGRES_PASSWORD=${DB_PASSWORD}1415# BETTER: Docker secrets (Compose v2)16services: 17 db: 18 image: postgres:1619 secrets: 20 - db_password21 environment: 22 - POSTGRES_PASSWORD_FILE=/run/secrets/db_password2324secrets: 25 db_password: 26 file: ./secrets/db_password.txtAdd .env and secrets/ to your .gitignore immediately. Leaked credentials are the number one cause of Docker container compromises.
032. Isolate Your Networks
1services: 2 # Web app only needs to talk to the database3 app: 4 image: myapp:latest5 networks: 6 - frontend7 - backend89 # Database should NOT be accessible from the internet10 db: 11 image: postgres:1612 networks: 13 - backend # Only on the backend network1415 # Reverse proxy only needs frontend access16 traefik: 17 image: traefik:v3.018 networks: 19 - frontend20 ports: 21 - "443:443"2223networks: 24 frontend: 25 backend: 26 internal: true # No external accessUse 'internal: true' on networks that should never have external access. This prevents containers on that network from reaching the internet, which limits what an attacker can do if they compromise a container.
043. Pin Image Versions and Use Minimal Images
1# Scan an image with Docker Scout2docker scout cves postgres:16.3-alpine34# Or use Trivy (free, open source)5docker run --rm aquasec/trivy image postgres:16.3-alpine67# Update images regularly8docker compose pull9docker compose up -d054. Set Resource Limits
1services: 2 app: 3 image: myapp:latest4 deploy: 5 resources: 6 limits: 7 cpus: "2.0"8 memory: 1G9 reservations: 10 cpus: "0.5"11 memory: 256M12 # Also prevent fork bombs13 ulimits: 14 nproc: 10015 nofile: 16 soft: 6553617 hard: 65536065. Minimize Container Privileges
1services: 2 app: 3 image: myapp:latest4 user: "1000:1000"5 read_only: true6 tmpfs: 7 - /tmp8 - /var/run9 security_opt: 10 - no-new-privileges:true11 cap_drop: 12 - ALL13 cap_add: 14 - NET_BIND_SERVICE # Only if binding to ports < 1024Test your security settings gradually. Start with read_only and no-new-privileges (these rarely break things), then try dropping capabilities and running as non-root. Some applications need specific capabilities to function.