Docker containers have transformed how organizations build and deploy applications. They offer faster startup times than VMs, consistent environments, and strong process isolation. But isolation is not the same as security — containers share the host kernel, and a misconfigured container can expose your entire infrastructure.
1. Run Containers as Non-Root
Docker doesn’t require containers to run as root, and almost no production workload should. Running as root means that if the application is compromised, the attacker has root access inside the container and potential kernel-level access on the host.
# WRONG: Default is root
FROM node:18
# CORRECT: Create and switch to a non-root user
FROM node:18-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
Set runAsNonRoot: true in Kubernetes pod specs to enforce this at the orchestration layer.
2. Implement Resource Quotas
Without resource limits, a single misbehaving container can starve the entire host of CPU and memory — whether through a bug or a deliberate attack.
# docker-compose.yml
services:
api:
image: myapp:latest
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
In Kubernetes:
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"
3. Use Trusted Registries
Pull images only from trusted registries. Public Docker Hub images are not vetted for security — many popular images contain outdated software, unpatched CVEs, or in some cases, deliberately malicious code.
Best practices:
- Use official base images (tagged
officialon Docker Hub) - Mirror approved images to a private registry (Docker Trusted Registry, AWS ECR, GCR, GitHub Container Registry)
- Implement registry access controls — not everyone should be able to push images
- Block unapproved public registries at the network level
4. Scan Images for Vulnerabilities
Every Docker image is built from a base image plus layers of installed packages. Each layer can contain CVEs. Scan images before pushing to your registry and before deploying to production.
# Using Trivy (open source)
trivy image myapp:latest
# Using Docker Scout
docker scout cves myapp:latest
Integrate scanning into your CI pipeline so vulnerable images are blocked before merge.
5. Keep Images Minimal
Smaller images have a smaller attack surface. Use multi-stage builds to produce minimal final images:
# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o server .
# Final stage — minimal image
FROM scratch
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
Avoid installing debugging tools, shells, or package managers in production images. If you need to debug, do it in a separate debugging sidecar.
6. Never Store Secrets in Images
Secrets embedded in Docker images — even in intermediate build layers — are extractable by anyone with access to the image.
# This exposes the secret in the image layer history
RUN apt-get install -y curl && \
curl -H "Authorization: Bearer $SECRET_TOKEN" https://internal-api/...
Use Docker Buildx secrets for build-time secrets:
docker buildx build --secret id=mysecret,src=./secret.txt .
For runtime secrets, use Docker secrets, Kubernetes Secrets, or a dedicated secrets manager (HashiCorp Vault, AWS Secrets Manager).
7. Apply Read-Only Filesystems
Make the container filesystem read-only to prevent malicious code from writing to disk:
# docker run
docker run --read-only myapp:latest
# Kubernetes
securityContext:
readOnlyRootFilesystem: true
If the application needs to write files, mount specific writable volumes rather than making the entire filesystem writable.
8. Network Segmentation
Containers on the same Docker network can communicate freely by default. Segment your networks:
# docker-compose.yml
networks:
frontend:
backend:
services:
nginx:
networks: [frontend, backend]
api:
networks: [backend]
db:
networks: [backend]
The database is only reachable from the backend network — not from the public-facing nginx container.
9. Monitor API and Network Activity
Containers rely on the Docker API and network activity for inter-process communication. Monitor for anomalies:
- Unexpected outbound connections
- Unusual process execution (shells launched inside a container)
- Filesystem writes in unexpected locations
- Network port scanning behavior
Tools like Falco provide runtime security monitoring for containerized environments.
10. Scan Your Application Code
Docker images contain your application code. Vulnerabilities in the code itself — SQL injection, hardcoded credentials, insecure cryptography — exist regardless of how well the container is hardened. Run SAST against your source code as part of the image build pipeline.
Summary
Docker security requires a layered approach:
| Layer | Control |
|---|---|
| Image | Scan for CVEs, use minimal base images, non-root user |
| Build | No secrets in layers, multi-stage builds |
| Runtime | Read-only filesystem, resource limits, network segmentation |
| Operations | Registry access controls, runtime monitoring, secret management |
No single control is sufficient. Implement all layers for defense in depth.