How to Harden Docker in Production: From Image Scanning to Runtime Protection
This guide walks DevOps engineers through a complete Docker hardening workflow—explaining the security model, recommending safe base images, removing secrets, applying multi‑stage builds, enforcing image signing, configuring runtime privileges, resource limits, network isolation, logging, and continuous audit with tools like Trivy, Cosign, Falco and CIS benchmarks.
Background and applicable scenarios
Docker is the de‑facto standard for server deployment. Production containers introduce attack surfaces such as supply‑chain risk, container‑escape, privileged execution, secret leakage, and lateral network movement.
Docker security model
Containers share the host kernel; root inside a container maps to the host UID unless a user namespace is enabled. Key mechanisms: Linux namespaces, cgroups, capabilities, Seccomp, AppArmor/SELinux.
Hardening is divided into six dimensions: image‑build security, image source control, runtime privilege reduction, resource quota limits, network isolation, and monitoring/audit.
Dimension 1 – Image build security
Base image selection
Never use the latest tag. Specify an exact version (e.g. nginx:1.26.2‑alpine) or a digest ( nginx@sha256:…). Minimal images such as Alpine (5‑10 MB) or distroless reduce the CVE attack surface.
Do not store secrets in the image
Embedding passwords, API keys, or private keys in Dockerfiles or copying them into the image is a common high‑risk mistake.
Check for secrets with hadolint and trivy:
docker run --rm -i hadolint/hadolint < Dockerfile
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image --security-checks secret <your-image:tag>Inject secrets at runtime via environment variables, mounted files, Docker Secrets (Swarm), or Kubernetes Secrets.
User remapping in Dockerfile
Create a low‑privilege user and set ownership on copied files:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --chown=appuser:appgroup . .
USER appuserValidate the effective user with docker exec <container> id and inspect the image’s default user.
Multi‑stage builds
Separate build tools and source code from the final image to minimise attack surface. Example:
# Builder stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY . .
RUN npm run build
# Runtime stage
FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node","dist/server.js"]Compare image sizes before and after to verify reduction.
CI/CD image scanning
Integrate Trivy to block builds with high‑severity CVEs:
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed --no-progress myregistry/myapp:1.2.3GitHub Actions example:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myregistry/myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'HIGH,CRITICAL'
exit-code: '1'Image signing and verification
Enable Docker Content Trust (DCT) to verify signed images:
export DOCKER_CONTENT_TRUST=1 # temporary for the session
# or permanently
echo 'DOCKER_CONTENT_TRUST=1' >> /etc/environmentUse Cosign for signing and verification:
# Install cosign
wget https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 -O /usr/local/bin/cosign
chmod +x /usr/local/bin/cosign
# Sign
cosign sign --yes myregistry/myapp:1.2.3
# Verify
cosign verify --certificate-identity mycompany.com \
--certificate-oidc-issuer https://mycompany.com/ \
myregistry/myapp:1.2.3Dimension 2 – Image source control
Pull only from trusted registries. Enable DCT and use a private registry (e.g. Harbor) that supports scanning and signing. Example pull:
docker pull harbor.mycompany.com/myproject/myapp:1.2.3Regularly prune dangling images and old tags to prevent reuse of vulnerable layers:
docker image ls -f dangling=true
docker image prune -f
docker image prune -a -fDimension 3 – Runtime privilege reduction
Disallow privileged containers
Running with --privileged gives the container full host access. Use capability drop/add instead:
# Bad
docker run --privileged myapp:latest
# Good
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp:latestRun containers as non‑root
Docker Compose: user: "1000:1000" Kubernetes SecurityContext:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000Disable new privileges
docker run --security-opt=no-new-privileges:true myapp:latestSeccomp profiles
Docker’s default Seccomp blocks ~44 dangerous syscalls. A custom profile can further restrict calls:
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{
"names": ["read","write","exit","exit_group","fstat"],
"action": "SCMP_ACT_ALLOW"
}
]
}Apply with
docker run --security-opt seccomp=/path/to/profile.json myapp:latestor via Kubernetes seccompProfile.type: Localhost.
Drop dangerous capabilities
Example to drop all and add only required:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp:latestDimension 4 – Resource quota limits
Memory limits
docker run -m=512m --memory-swap=512m myapp:latestDocker Compose:
resources:
limits:
memory: "512M"
reservations:
memory: "256M"Kubernetes:
resources:
limits:
memory: "512Mi"
requests:
memory: "256Mi"CPU limits
docker run --cpus=1.0 myapp:latestDocker Compose:
resources:
limits:
cpus: '1.0'
reservations:
cpus: '0.5'Kubernetes:
resources:
limits:
cpu: "1000m"
requests:
cpu: "500m"Storage I/O limits
docker run --device-read-bps=/dev/sda:10mb --device-write-bps=/dev/sda:10mb myapp:latest
docker run --blkio-weight=500 myapp:latestDimension 5 – Network isolation
Network mode selection
Create a dedicated bridge network for each application:
docker network create --driver bridge \
--opt com.docker.network.bridge.name=br-webapp \
--subnet=172.28.0.0/16 webapp-network
docker run --network=webapp-network myapp:latestDisable inter‑container communication (ICC)
Set "icc": false in /etc/docker/daemon.json and restart Docker.
Restrict network capabilities
docker run --cap-drop=NET_ADMIN myapp:latest
docker run --cap-add=NET_BIND_SERVICE myapp:latestProhibit host network mode
In Kubernetes, set hostNetwork: false in the pod spec or use a PodSecurityPolicy that disallows it.
Dimension 6 – Monitoring, audit and logging
Runtime security monitoring
Configure auditd rules for Docker paths:
-w /var/lib/docker -p wa -k docker
-w /etc/docker -p wa -k docker
-w /usr/bin/docker -p x -k docker
-w /var/run/docker.sock -p rw -k docker-socketRun Falco to detect abnormal syscalls:
docker run -d --name falco \
--net host --pid host \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc:/host/etc \
falcosecurity/falco --privilegedCentralised log collection
Configure Docker daemon log driver to syslog or fluentd:
{
"log-driver": "syslog",
"log-opts": {
"syslog-address": "tcp://log-server:514",
"tag": "{{.Name}}/{{.ID}}",
"max-size": "10m",
"max-file": "5"
}
}Periodic configuration audit
Run Docker CIS benchmark:
docker run -it --rm \
--net host --pid host --privileged \
-v /var/lib/docker:/var/lib/docker \
aquasec/docker-bench-security:latestRun Kubernetes CIS benchmark:
kubectl run kube-bench --image=aquasec/kube-bench:latest --restart=Never \
--overrides='{"spec":{"hostPID":true,"hostNetwork":true,"containers":[{"name":"kube-bench","securityContext":{"privileged":true}}]}}' \
-- node --kubeconfig=/etc/kubernetes/admin.confChecklist for production hardening
Base images pinned to exact versions or digests; prefer Alpine or distroless.
No secrets stored in images; use runtime injection or secret management.
Multi‑stage builds to minimise final image size.
CI integration of Trivy/Grype with high‑severity blocking.
Image signing with Cosign and verification via DCT.
Pull only from trusted registries; prune dangling images.
Run containers as non‑root; set explicit UID/GID.
Drop all capabilities by default; add only required.
Enable no-new-privileges.
Set memory and CPU limits in Docker/Kubernetes.
Throttle storage I/O for high‑write workloads.
Use dedicated bridge networks; disable ICC.
Avoid host network mode.
Configure centralized logging (syslog or fluentd) with rotation.
Deploy Falco or similar runtime security monitor.
Run docker‑bench‑security and kube‑bench regularly.
Risk summary
Modify /etc/docker/daemon.json – Docker daemon restart interrupts containers. Mitigation: Perform during maintenance window and record container start order.
Change container capabilities – Application may fail. Mitigation: Validate in test environment; document required capabilities.
Enable userland-proxy=false – NodePort/hostPort access may break. Mitigation: Test before production rollout; keep rollback plan.
Disable ICC – Inter‑container calls fail. Mitigation: Map dependencies and verify after change.
Adjust resource limits – OOM or throttling. Mitigation: Conduct load testing; review historical usage.
Upgrade Docker version – Behavior changes. Mitigation: Test in staging; review release notes.
Rollback procedures
If Docker fails to start after daemon.json changes, restore the backup and restart:
systemctl stop docker
cp /etc/docker/daemon.json.bak /etc/docker/daemon.json
systemctl start dockerFor container‑level changes, revert the manifest (docker‑compose.yml or Kubernetes deployment) and redeploy.
Conclusion
Docker security hardening is an ongoing operational process. Apply the three core principles:
Least‑privilege – grant only the permissions, resources, and network access required.
Defense‑in‑depth – combine image‑time safeguards (scanning, signing) with runtime controls (capabilities, Seccomp, user mapping) and continuous monitoring/audit.
Shift‑left security – detect and block issues early in the build pipeline to minimise remediation cost.
Following these practices provides a solid security foundation for production Docker workloads.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Ops Community
A leading IT operations community where professionals share and grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
