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.

Ops Community
Ops Community
Ops Community
How to Harden Docker in Production: From Image Scanning to Runtime Protection

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 appuser

Validate 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.3

GitHub 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/environment

Use 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.3

Dimension 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.3

Regularly 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 -f

Dimension 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:latest

Run containers as non‑root

Docker Compose: user: "1000:1000" Kubernetes SecurityContext:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  runAsGroup: 1000

Disable new privileges

docker run --security-opt=no-new-privileges:true myapp:latest

Seccomp 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:latest

or 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:latest

Dimension 4 – Resource quota limits

Memory limits

docker run -m=512m --memory-swap=512m myapp:latest

Docker Compose:

resources:
  limits:
    memory: "512M"
  reservations:
    memory: "256M"

Kubernetes:

resources:
  limits:
    memory: "512Mi"
  requests:
    memory: "256Mi"

CPU limits

docker run --cpus=1.0 myapp:latest

Docker 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:latest

Dimension 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:latest

Disable 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:latest

Prohibit 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-socket

Run 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 --privileged

Centralised 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:latest

Run 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.conf

Checklist 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 docker

For 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerKubernetesRuntimeImage ScanningsecurityHardeningCIS Benchmark
Ops Community
Written by

Ops Community

A leading IT operations community where professionals share and grow together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.