10 steps
Containers reduce the blast radius of a compromise - but only if they are built and run with security in mind. These steps help you minimize the attack surface of your containerized workloads from image build time all the way through runtime.
Start from distroless or slim images (e.g., alpine, gcr.io/distroless). Strip out shells, package managers, and tools that are not needed at runtime - every unnecessary binary is a potential exploit path.
Never run containers as root. Create a dedicated low-privilege user in your Dockerfile with RUN useradd, then switch to it with the USER instruction before your entrypoint.
Mount the container filesystem as read-only (--read-only in Docker, readOnlyRootFilesystem: true in Kubernetes). Use tmpfs volumes for specific writable paths like /tmp that are genuinely required.
Define explicit CPU and memory limits for every container. This prevents a compromised or runaway container from starving neighboring workloads or contributing to a denial-of-service condition.
Integrate image scanning (Trivy, Grype, Snyk Container) into your CI/CD pipeline. Fail builds on critical CVEs. Rescan regularly even without code changes, since vulnerabilities are discovered daily.
Avoid baking credentials, API keys, or environment-specific config into image layers - these are visible via docker history and any image pull. Use a secrets manager or runtime secret injection instead.
Avoid mutable tags like latest in production. Pin images to immutable SHA256 digest hashes to guarantee what is deployed at each point in time and prevent supply chain drift.
Restrict container-to-container and ingress/egress traffic using Kubernetes NetworkPolicies or a service mesh. Adopt a default-deny posture and only permit explicitly required communication paths.
Use --cap-drop=ALL and only add back capabilities that are strictly required. Set no-new-privileges: true to prevent privilege escalation attacks from within the container.
Build on distroless or scratch base images that omit shells and package managers. Set a non-root USER in the Dockerfile, use a read-only root filesystem, drop all Linux capabilities and add back only what is needed, and run the container engine in rootless mode where supported.