Docker Container Security: Best Practices for Production
Practice4 minInnovialabs editorial team
In short
A secure container runs as a non-root user from a minimal, scanned image, holds no secrets in its layers, has dropped capabilities and resource limits, and nobody outside the host can reach the Docker socket.
What matters most
A container is not a virtual machine: it shares the kernel with the host. A compromised container with excessive rights can become a compromised server. The core rules for production:
- Run processes as a non-root user.
- Build from minimal base images and pin them.
- Scan images for CVEs in CI before deployment.
- Never bake secrets into image layers.
- Drop capabilities, use a read-only filesystem and set resource limits.
- Protect the Docker socket and the registry.
1. Run as non-root
By default, a process in a container runs as root. If an attacker escapes the application, root inside the container makes the next step much easier.
FROM node:20-alpine
WORKDIR /app
COPY --chown=node:node . .
RUN npm ci --omit=dev
USER node
CMD ["node", "server.js"]
- Create a dedicated user or use the one the base image provides.
- Listen on ports above 1024 so root is not needed.
- Consider rootless Docker or user namespace remapping on the host.
2. Minimal base images
Every extra package is extra attack surface and extra CVEs.
| Option | When to use |
|---|---|
alpine / -slim variants | General services, small size |
| Distroless images | Compiled apps and runtimes, no shell |
scratch | Static binaries (Go, Rust) |
- Use multi-stage builds: compilers and dev dependencies stay in the build stage.
- Pin the base image by tag and, for critical services, by digest (
image@sha256:...). - Rebuild images regularly so security patches in the base image reach production.
3. Scan images for CVEs
- Add a scanner such as Trivy, Grype or Docker Scout to the CI pipeline.
- Fail the build on critical vulnerabilities that have a fix available.
- Scan not only at build time but also images already running, since new CVEs appear every day.
- Keep a short, documented exception list for findings that do not apply.
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:1.4.0
4. Keep secrets out of layers
Anything written in a layer stays in the image history, even if a later RUN rm deletes the file. ENV API_KEY=... and COPY .env are visible to anyone who can pull the image.
- Add
.env, keys and credentials to.dockerignore. - For secrets needed during the build, use BuildKit secret mounts:
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
- Pass runtime secrets through the orchestrator: Docker/Swarm secrets, Kubernetes Secrets, or a secrets manager such as Vault or cloud KMS.
- If a secret was ever pushed in an image, rotate it: deleting the tag is not enough.
5. Limit capabilities and resources
docker run -d \
--user 1000:1000 \
--read-only --tmpfs /tmp \
--cap-drop ALL --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges \
--memory 512m --cpus 1 --pids-limit 200 \
myapp:1.4.0
--cap-drop ALL, then add back only what is needed.- Never use
--privilegedin production. --read-onlyroot filesystem with explicit writable volumes or tmpfs.- Memory, CPU and PID limits stop one container from taking down the host.
- Keep the default seccomp and AppArmor/SELinux profiles enabled.
- Do not publish database or internal ports to
0.0.0.0; use internal networks.
6. Protect the Docker socket and registry
Access to /var/run/docker.sock equals root on the host. Whoever can talk to it can start a privileged container and mount the host filesystem.
- Do not mount the socket into application containers. If a tool truly needs it, use a restricted socket proxy.
- Never expose the Docker API over TCP without TLS client authentication.
- Limit membership in the
dockergroup on the host.
For the registry:
- Use a private registry with authentication and per-project access.
- Give CI a push token and servers a pull-only token.
- Sign images (for example with cosign) and verify signatures before deployment.
- Enable automatic scanning in the registry if it is available.
Common mistakes
latesttag in production: you never know what is actually running.- Secrets in
ENVor copied.envfiles. - Socket mounted "just for monitoring".
- No resource limits, so one memory leak kills every service on the host.