Containerization has made it faster and easier to ship software, but speed and security are not automatic companions. Whether you are running a handful of microservices or a large Kubernetes fleet, Docker containers introduce their own attack surface that must be managed deliberately. The good news is that most of the foundational controls are well-understood and straightforward to implement.
This article summarizes the core security practices every DevOps engineer should apply when working with Docker containers, regardless of the orchestration layer on top.
1. Treat the Container Like a Real Machine
A common misconception is that process isolation inside a container implies security. Isolation reduces the blast radius of a compromise, but it is not a security boundary on its own. Apply the same hardening principles you would to any server:
- Run as a non-root user. Add a
USERinstruction in your Dockerfile to drop privileges before the application starts. Running as root inside a container can translate to root on the host if container breakout occurs. - Apply the principle of least privilege. Use Linux namespaces and cgroups to restrict what processes can see and consume. Docker exposes these through flags such as
--cap-dropand--cap-add. - Avoid world-writable directories and files. Audit filesystem permissions inside your images just as you would on a production server.
- Use read-only filesystems where possible. The
--read-onlyflag prevents processes inside the container from writing to the container filesystem, limiting persistence opportunities for attackers.
# Example: non-root user in a Dockerfile
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --chown=appuser:appgroup . /app
WORKDIR /app
CMD ["node", "server.js"]Code language: PHP (php)
2. Only Use Images from Trusted Sources
Pulling a public image is convenient, but you are trusting everything the image author chose to include. Malicious or misconfigured layers can introduce backdoors, outdated vulnerable packages, or unexpected binaries.
Practical guidelines:
- Prefer official or verified images. Docker Hub marks images as “Docker Official Images” or “Verified Publisher.” These go through additional review and automated scanning.
- Enable Docker Content Trust (DCT). Setting the environment variable
DOCKER_CONTENT_TRUST=1instructs the Docker client to only pull images that carry a valid cryptographic signature from the image publisher. This protects against tampered images in transit. - Host a private registry. For production workloads, mirror approved base images in a private registry (such as AWS ECR, Google Artifact Registry, or a self-hosted Harbor instance) and restrict developers to pulling from there.
- Scan images for vulnerabilities. Integrate a scanner such as Trivy, Grype, or Snyk into your CI pipeline to catch known CVEs before images reach production.
# Enable Docker Content Trust for the current shell session
export DOCKER_CONTENT_TRUST=1
docker pull myrepo/myimage:latest # will fail if image is unsignedCode language: PHP (php)
Note on Docker Hub: A security incident disclosed at DockerCon 2019 affected approximately 190,000 Docker Hub accounts, serving as a reminder that no hosted registry should be trusted without additional controls. Content trust and private mirrors add meaningful defence-in-depth.
3. Use Docker Bench for Security
Docker Bench for Security is an open-source shell script maintained by the Docker team. It audits your Docker host and running containers against the CIS Docker Benchmark—a community standard covering daemon configuration, image hygiene, container runtime settings, and more.
Running it is straightforward:
# Run Docker Bench for Security using Docker itself
docker run --rm --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker/docker-bench-securityCode language: PHP (php)
Each check is rated as PASS, WARN, or INFO. You can pipe the output into your CI system to fail a build when critical warnings appear, making security checks a first-class part of your delivery pipeline. The repository is available at github.com/docker/docker-bench-security.
4. Limit Direct Access to Kubernetes Nodes
When Docker containers are orchestrated by Kubernetes, the security perimeter extends to the nodes themselves. Unrestricted SSH access to nodes is one of the most common and dangerous misconfigurations in Kubernetes clusters.
- Restrict SSH access. Limit node SSH access to a very small group of platform administrators. Where cloud-provider tooling allows, remove direct SSH access entirely and use systems like AWS Systems Manager Session Manager or GCP OS Login.
- Use
kubectl execfor debugging. Developers who need to inspect or debug a running container should usekubectl execrather than accessing the underlying node. This gives them a shell inside the container namespace without host-level access. - Enforce IAM and RBAC policies. Define Kubernetes RBAC roles that grant only the permissions required for each team or service account. Avoid binding the
cluster-adminrole to user-facing accounts.
# Open a shell in a running pod without touching the node
kubectl exec -it <pod-name> -n <namespace> -- /bin/shCode language: PHP (php)
5. Isolate Workloads Using Kubernetes Namespaces and Policies
A compromised workload in a flat Kubernetes cluster can potentially reach every other workload. Namespaces and policy controls reduce this lateral movement risk.
- Use namespaces to segment workloads. Group related services into dedicated namespaces (e.g.,
payments,auth,logging) rather than leaving everything indefault. - Apply ResourceQuotas and LimitRanges. Namespaced resource quotas prevent a compromised or runaway container from consuming all cluster resources and affecting other tenants.
- Use Kubernetes RBAC authorization. Kubernetes’ built-in authorization plugin allows you to create fine-grained policies defining which users or service accounts can perform which actions on which resources, scoped to specific namespaces.
- Consider Pod Security Admission or OPA/Gatekeeper. Policy admission controllers let you enforce security standards (e.g., no privileged containers, no host network access) at the cluster level, before workloads are scheduled.
6. Protect the Network
Network controls are the last line of defence if a container is compromised. Even well-hardened containers should not be able to freely communicate with every other service in the cluster.
- Apply Kubernetes NetworkPolicies. NetworkPolicy resources define ingress and egress rules at the pod level. By default, Kubernetes allows all pod-to-pod communication; adding a default-deny NetworkPolicy and then allowlisting only the required connections significantly limits lateral movement.
- Use Ingress controllers to control external access. Define Ingress resources to explicitly declare which services are reachable via HTTP/HTTPS from outside the cluster, rather than exposing services directly with
NodePortorLoadBalancertypes. - Consider a service mesh for mTLS. Tools like Istio or Linkerd can enforce mutual TLS between services automatically, ensuring that service-to-service traffic is encrypted and authenticated even within the cluster.
- Audit exposed ports. Review every container’s
EXPOSEinstruction and every Kubernetes Service definition. Eliminate any port that is not required for the service to function.
# Example: default-deny NetworkPolicy for a namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressCode language: PHP (php)
7. Keep Images and Dependencies Up to Date
Container images are point-in-time snapshots. An image built six months ago almost certainly contains packages with known CVEs by the time it reaches production, or shortly after. Treat image freshness as an ongoing operational concern:
- Rebuild base images on a regular schedule, not just when application code changes.
- Use minimal base images (e.g.,
alpineordistroless) to reduce the number of packages that need patching. - Integrate vulnerability scanning (Trivy, Grype, Snyk) into both CI and as a recurring registry scan so you are notified when new CVEs affect images already in production.
Summary: Docker Security Checklist
| Area | Key Action |
|---|---|
| Runtime privileges | Run as non-root; drop unnecessary Linux capabilities |
| Image provenance | Enable Docker Content Trust; use verified or private registries |
| Benchmark compliance | Run Docker Bench for Security in CI |
| Kubernetes access | Remove direct node SSH; use kubectl exec and RBAC |
| Workload isolation | Use namespaces, ResourceQuotas, and admission policies |
| Network segmentation | Apply NetworkPolicies; restrict Ingress; consider mTLS |
| Image currency | Rebuild images regularly; scan for CVEs continuously |
Containers are powerful tools, but isolation is not invincibility. Each layer of your stack—image build, registry, runtime, orchestrator, and network—introduces its own risks and its own mitigations. Applying the controls above will meaningfully reduce your attack surface and make incidents easier to contain when they do occur.
Original article concept sourced from containerjournal.com. Expanded and updated for accuracy by the BestDevOps editorial team.