Learn comprehensive security practices for Docker containers, including image security, runtime protection, secrets management, and security scanning.
Container security requires a defense-in-depth approach, addressing vulnerabilities at every layer: the host system, Docker daemon, container images, and runtime configuration. While containers provide process isolation, they share the host kernel and can expose significant attack surface if not properly secured.
Understanding the Docker security model helps you implement appropriate controls. Containers use Linux kernel features like namespaces, cgroups, and capabilities to provide isolation and resource limits. However, by default, containers run with more privileges than necessary, making security hardening essential for production deployments.
Container image security is the foundation of a secure container deployment. Vulnerabilities in base images, dependencies, or application code can be exploited at runtime.
Key Image Security Principles:
Image Scanning:
Scan images during CI/CD to detect:
Trusted Content:
Writing secure Dockerfiles requires attention to user permissions, file handling, and build practices. Every instruction in a Dockerfile can introduce security risks if not properly configured.
Critical Dockerfile Practices:
Secret Management in Build:
Layer Security:
Container runtime security involves configuring the Docker daemon and individual containers to minimize privileges and isolate workloads.
Docker Daemon Security:
Container Isolation:
Resource Limits:
Prevent denial-of-service by limiting:
Managing secrets in containers requires careful handling to prevent exposure in images, logs, or environment variables.
Secrets Management Approaches:
Best Practices:
Runtime Secrets:
Mount secrets as files rather than environment variables:
Continuous security requires scanning images, monitoring runtime behavior, and detecting anomalies in container workloads.
Scanning Types:
Monitoring Areas:
Security Tools:
Implement a complete security pipeline including secure Dockerfile, scanning, runtime security, and monitoring.
1# Complete Secure Container Deployment
2
3# === 1. Secure Dockerfile ===
4# Dockerfile
5# syntax=docker/dockerfile:1.4
6FROM node:20-alpine AS builder
7
8# Security: Use specific version
9# Security: Start with minimal base
10
11WORKDIR /app
12
13# Security: Copy only necessary files
14COPY package*.json ./
15
16# Security: Use BuildKit secrets for private npm
17RUN --mount=type=secret,id=npm_token,target=/root/.npmrc \
18 npm ci --only=production && \
19 npm cache clean --force
20
21COPY . .
22RUN npm run build
23
24# Production stage
25FROM gcr.io/distroless/nodejs20-debian12
26
27# Security: Metadata labels
28LABEL org.opencontainers.image.source="https://github.com/org/app"
29LABEL org.opencontainers.image.description="Secure Node.js App"
30LABEL org.opencontainers.image.licenses="MIT"
31
32WORKDIR /app
33
34# Security: Non-root already in distroless
35# Security: Minimal filesystem
36
37COPY --from=builder /app/dist ./dist
38COPY --from=builder /app/node_modules ./node_modules
39
40EXPOSE 3000
41
42# Security: No shell, exec form only
43CMD ["dist/server.js"]
44
45# === 2. Security Scanning Script ===
46#!/bin/bash
47# security-scan.sh
48
49set -e
50
51IMAGE="myapp:latest"
52SEVERITY_THRESHOLD="HIGH,CRITICAL"
53EXIT_ON_VULN=1
54
55echo "=== Building Image ==="
56DOCKER_BUILDKIT=1 docker build \
57 --secret id=npm_token,src=.npmrc \
58 -t $IMAGE .
59
60echo "=== Scanning for Vulnerabilities ==="
61trivy image --severity $SEVERITY_THRESHOLD \
62 --exit-code $EXIT_ON_VULN \
63 --format table \
64 $IMAGE
65
66echo "=== Checking Dockerfile ==="
67trivy config --severity $SEVERITY_THRESHOLD Dockerfile
68
69echo "=== Scanning for Secrets ==="
70trivy fs --security-checks secret --exit-code 1 .
71
72echo "=== Docker Scout Analysis ==="
73docker scout cves $IMAGE
74docker scout recommendations $IMAGE
75
76echo "=== All security checks passed ==="
77
78# === 3. Secure Docker Compose ===
79# docker-compose.secure.yml
80version: '3.8'
81
82services:
83 app:
84 image: myapp:latest
85 container_name: secure-app
86 restart: unless-stopped
87
88 # Security: Non-root user
89 user: "1000:1000"
90
91 # Security: Read-only filesystem
92 read_only: true
93 tmpfs:
94 - /tmp:rw,noexec,nosuid,size=100m
95 - /var/run:rw,noexec,nosuid,size=10m
96
97 # Security: Drop all capabilities
98 cap_drop:
99 - ALL
100 cap_add:
101 - NET_BIND_SERVICE
102
103 # Security: Prevent privilege escalation
104 security_opt:
105 - no-new-privileges:true
106 - seccomp:./seccomp-profile.json
107
108 # Security: Resource limits
109 deploy:
110 resources:
111 limits:
112 cpus: '1.0'
113 memory: 512M
114 pids: 100
115 reservations:
116 cpus: '0.25'
117 memory: 128M
118
119 # Security: Network isolation
120 networks:
121 - app-internal
122 - app-external
123
124 # Security: File-based secrets
125 secrets:
126 - db_password
127 - api_key
128
129 environment:
130 NODE_ENV: production
131 DB_PASSWORD_FILE: /run/secrets/db_password
132 API_KEY_FILE: /run/secrets/api_key
133
134 healthcheck:
135 test: ["CMD", "/nodejs/bin/node", "healthcheck.js"]
136 interval: 30s
137 timeout: 10s
138 retries: 3
139 start_period: 40s
140
141 logging:
142 driver: "json-file"
143 options:
144 max-size: "10m"
145 max-file: "3"
146
147 # Security: Falco for runtime monitoring
148 falco:
149 image: falcosecurity/falco:latest
150 container_name: falco
151 privileged: true
152 volumes:
153 - /var/run/docker.sock:/host/var/run/docker.sock:ro
154 - /dev:/host/dev
155 - /proc:/host/proc:ro
156 - ./falco-rules:/etc/falco/rules.d:ro
157 networks:
158 - monitoring
159
160secrets:
161 db_password:
162 file: ./secrets/db_password.txt
163 api_key:
164 file: ./secrets/api_key.txt
165
166networks:
167 app-internal:
168 internal: true # No external access
169 app-external:
170 driver: bridge
171 monitoring:
172 driver: bridge
173
174# === 4. CI/CD Security Pipeline ===
175# .github/workflows/secure-deploy.yml
176name: Secure Build and Deploy
177
178on:
179 push:
180 branches: [main]
181 pull_request:
182 branches: [main]
183
184jobs:
185 security-scan:
186 runs-on: ubuntu-latest
187 steps:
188 - uses: actions/checkout@v4
189
190 - name: Build image
191 run: |
192 DOCKER_BUILDKIT=1 docker build \
193 --secret id=npm_token,env=NPM_TOKEN \
194 -t myapp:${{ github.sha }} .
195 env:
196 NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
197
198 - name: Trivy vulnerability scan
199 uses: aquasecurity/trivy-action@master
200 with:
201 image-ref: myapp:${{ github.sha }}
202 format: 'sarif'
203 output: 'trivy.sarif'
204 severity: 'HIGH,CRITICAL'
205 exit-code: '1'
206
207 - name: Upload scan results
208 uses: github/codeql-action/upload-sarif@v2
209 with:
210 sarif_file: trivy.sarif
211
212 - name: Docker Scout scan
213 uses: docker/scout-action@v1
214 with:
215 command: cves
216 image: myapp:${{ github.sha }}
217 only-severities: critical,high
218 exit-code: true
219
220 deploy:
221 needs: security-scan
222 runs-on: ubuntu-latest
223 if: github.ref == 'refs/heads/main'
224 steps:
225 - name: Deploy to production
226 run: |
227 # Sign and push image
228 export DOCKER_CONTENT_TRUST=1
229 docker push $REGISTRY/myapp:${{ github.sha }}
230
231 # Deploy with security configs
232 docker compose -f docker-compose.secure.yml up -dYour progress is saved in this browser, on this device only.