---
# === IDENTITY ===
id: software/security/container-security/2026
canonical_question: "What is the container security checklist (Docker)?"
aliases:
  - "Docker container security best practices"
  - "Docker security checklist"
  - "container hardening guide"
  - "Docker image security scanning"
  - "CIS Docker Benchmark checklist"
  - "OWASP Docker security"
  - "secure Dockerfile best practices"
  - "Docker runtime security configuration"
entity_type: software_reference
domain: software > security > Container Security
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.94
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "CIS Docker Benchmark v1.8.0 (August 2025) updated 27 recommendations for Docker Server v28; Docker BuildKit secrets mount syntax stable since Docker 18.09"
  next_review: 2026-08-26
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "NEVER run containers with --privileged flag -- this grants ALL Linux kernel capabilities and device access"
  - "NEVER store secrets (passwords, API keys, tokens) in Dockerfiles, environment variables, or image layers"
  - "NEVER use the :latest tag in production -- pin to specific version or SHA256 digest for reproducibility"
  - "NEVER expose the Docker daemon socket (/var/run/docker.sock) to containers -- this grants unrestricted root access to the host"
  - "ALWAYS run containers as a non-root user via the USER directive in Dockerfile"
  - "ALWAYS scan images for vulnerabilities before deployment -- block builds with critical CVEs in CI/CD"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need Kubernetes-specific security (RBAC, NetworkPolicy, PodSecurityStandards)"
    use_instead: "software/security/kubernetes-security/2026"
  - condition: "Need general web application security, not container security"
    use_instead: "software/security/xss-prevention/2026"
  - condition: "Need secrets management solution comparison (Vault vs AWS Secrets Manager)"
    use_instead: "software/devops/secrets-management/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "orchestrator"
    question: "Are you using Docker standalone, Docker Compose, or Kubernetes?"
    type: choice
    options: ["Docker standalone", "Docker Compose", "Kubernetes", "Docker Swarm"]
  - key: "environment"
    question: "Is this for development, CI/CD, or production?"
    type: choice
    options: ["Development", "CI/CD pipeline", "Production"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/container-security/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention Guide"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/kubernetes-security/2026"
      label: "Kubernetes Security Guide"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Docker Security Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html
    type: community_resource
    published: 2025-01-15
    reliability: authoritative
  - id: src2
    title: "Docker Engine Security"
    author: Docker Inc.
    url: https://docs.docker.com/engine/security/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src3
    title: "CIS Docker Benchmark v1.8.0"
    author: Center for Internet Security
    url: https://www.cisecurity.org/benchmark/docker
    type: industry_report
    published: 2025-08-01
    reliability: authoritative
  - id: src4
    title: "10 Docker Image Security Best Practices"
    author: Snyk
    url: https://snyk.io/blog/10-docker-image-security-best-practices/
    type: technical_blog
    published: 2025-03-01
    reliability: high
  - id: src5
    title: "Trivy - Comprehensive Security Scanner"
    author: Aqua Security
    url: https://trivy.dev/
    type: official_docs
    published: 2025-10-01
    reliability: high
  - id: src6
    title: "Dockerfile Best Practices"
    author: Docker Inc.
    url: https://docs.docker.com/build/building/best-practices/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src7
    title: "Docker CIS Benchmark Best Practices"
    author: Aqua Security
    url: https://www.aquasec.com/cloud-native-academy/docker-container/docker-cis-benchmark/
    type: technical_blog
    published: 2025-05-01
    reliability: high
---

# Container Security: Docker Security Checklist

## TL;DR

- **Bottom line**: Secure Docker containers require layered defenses across image build (minimal base, non-root user, no secrets), runtime (drop capabilities, read-only filesystem, resource limits), and operations (vulnerability scanning, image signing, network segmentation).
- **Key tool/command**: `trivy image --severity CRITICAL,HIGH myapp:latest` to scan images for vulnerabilities before deployment.
- **Watch out for**: Running containers as root with `--privileged` -- the single most dangerous misconfiguration that grants full host access on container escape.
- **Works with**: Docker Engine 20.10+, Docker Compose v2+, Docker Desktop, BuildKit, CIS Docker Benchmark v1.8.0.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER run containers with `--privileged` flag -- this grants ALL Linux kernel capabilities and device access
- NEVER store secrets (passwords, API keys, tokens) in Dockerfiles, environment variables, or image layers
- NEVER use the `:latest` tag in production -- pin to specific version or SHA256 digest for reproducibility
- NEVER expose the Docker daemon socket (`/var/run/docker.sock`) to containers -- this grants unrestricted root access to the host
- ALWAYS run containers as a non-root user via the `USER` directive in Dockerfile
- ALWAYS scan images for vulnerabilities before deployment -- block builds with critical CVEs in CI/CD

## Quick Reference

| # | Security Control | Risk Level | Implementation | Verification |
|---|---|---|---|---|
| 1 | Use minimal base image | High | `FROM node:20-alpine` or distroless/scratch | `docker image ls` -- check size < 100MB |
| 2 | Run as non-root user | Critical | `USER appuser` in Dockerfile | `docker exec <ctr> whoami` -- should NOT be root |
| 3 | Drop all capabilities | High | `--cap-drop=ALL --cap-add=NET_BIND_SERVICE` | `docker inspect --format '{{.HostConfig.CapDrop}}'` |
| 4 | Read-only filesystem | Medium | `--read-only --tmpfs /tmp` | `docker exec <ctr> touch /test` -- should fail |
| 5 | No secrets in image | Critical | Use BuildKit `--mount=type=secret` or Docker Secrets | `docker history myimage` -- no sensitive data in layers |
| 6 | Pin image versions | High | `FROM node:20.11.1-alpine3.19` or `@sha256:...` | Verify `FROM` lines in Dockerfile |
| 7 | Scan for vulnerabilities | Critical | `trivy image myapp:latest` in CI/CD pipeline | Exit code 1 on CRITICAL/HIGH findings |
| 8 | Limit resources | Medium | `--memory=512m --cpus=1.0 --pids-limit=100` | `docker stats` -- check limits enforced |
| 9 | Use custom networks | Medium | `docker network create --internal mynet` | `docker network inspect mynet` |
| 10 | Sign images | High | `cosign sign --key cosign.key myimage:1.0` | `cosign verify myimage:1.0` |
| 11 | Prevent privilege escalation | High | `--security-opt=no-new-privileges` | `docker inspect --format '{{.HostConfig.SecurityOpt}}'` |
| 12 | Enable rootless mode | Medium | `dockerd-rootless-setuptools.sh install` | `docker info` -- check "rootless" |
| 13 | Use COPY not ADD | Medium | `COPY . /app` instead of `ADD . /app` | Lint with `hadolint Dockerfile` |
| 14 | Keep host and Docker updated | Critical | Regular patch cycle, auto-updates for Docker Engine | `docker version` -- check latest stable |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (35 lines)

```
START: What phase of the container lifecycle are you securing?
|-- BUILD TIME (Dockerfile)?
|   |-- Using a full OS base image (ubuntu, debian)?
|   |   |-- YES -> Switch to alpine, distroless, or scratch (see Step 1)
|   |   +-- NO -> Good. Verify image is pinned to specific version
# ... (see full script)
```

## Step-by-Step Guide

### 1. Use minimal base images

Smaller images have fewer packages, fewer vulnerabilities, and a smaller attack surface. Alpine Linux images are ~5MB versus ~120MB for Debian. Distroless images contain only the application runtime with no shell or package manager. [src4]

```dockerfile
# GOOD: Minimal Alpine-based image
FROM node:20.11.1-alpine3.19

# BETTER: Distroless for compiled languages
FROM gcr.io/distroless/static-debian12:nonroot

# BEST: Scratch for statically-compiled binaries (Go, Rust)
FROM scratch
COPY --from=builder /app/binary /binary
ENTRYPOINT ["/binary"]
```

**Verify**: `docker image ls myapp` -- image size should be under 100MB for most applications.

### 2. Create and use a non-root user

Running as root inside a container means a container escape vulnerability gives the attacker root access on the host. Always create a dedicated user with minimal permissions. [src1]

```dockerfile
FROM node:20.11.1-alpine3.19

# Create non-root user and group
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --omit=dev

# Switch to non-root user
USER appuser

EXPOSE 3000
CMD ["node", "server.js"]
```

**Verify**: `docker exec <container> whoami` -- should output `appuser`, NOT `root`.

### 3. Handle secrets securely with BuildKit

Never embed secrets in Dockerfiles or image layers. Use BuildKit secret mounts which are not persisted in the final image. [src4]

```dockerfile
# syntax=docker/dockerfile:1
FROM node:20.11.1-alpine3.19

WORKDIR /app
COPY package*.json ./

# Secret is available only during this RUN step, not stored in layers
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) \
    npm ci --omit=dev

COPY . .
USER appuser
CMD ["node", "server.js"]
```

Build with: `DOCKER_BUILDKIT=1 docker build --secret id=npm_token,src=.npmrc -t myapp .`

**Verify**: `docker history myapp` -- no secret values should appear in any layer.

### 4. Scan images for vulnerabilities in CI/CD

Integrate vulnerability scanning as a mandatory gate in your CI/CD pipeline. Trivy is the most widely adopted open-source scanner. [src5]

```bash
# Scan image and fail on CRITICAL or HIGH severity
trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:latest

# Generate SBOM (Software Bill of Materials)
trivy image --format spdx-json --output sbom.json myapp:latest

# Scan Dockerfile for misconfigurations
trivy config --severity HIGH,CRITICAL Dockerfile
```

GitHub Actions integration:

```yaml
# .github/workflows/scan.yml
name: Container Security Scan
on: push
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .
      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@0.28.0
        with:
          image-ref: myapp:${{ github.sha }}
          severity: CRITICAL,HIGH
          exit-code: '1'
          format: table
```

**Verify**: CI pipeline fails (exit code 1) when critical vulnerabilities are found.

### 5. Sign and verify images

Image signing ensures images have not been tampered with between build and deployment. Cosign (Sigstore) is the modern standard. [src4]

```bash
# Generate a key pair
cosign generate-key-pair

# Sign an image after build
cosign sign --key cosign.key myregistry.com/myapp:1.0

# Verify before deployment
cosign verify --key cosign.pub myregistry.com/myapp:1.0
```

Alternatively, use Docker Content Trust:

```bash
# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1

# Push will automatically sign
docker push myregistry.com/myapp:1.0

# Pull will only accept signed images
docker pull myregistry.com/myapp:1.0
```

**Verify**: `cosign verify --key cosign.pub myregistry.com/myapp:1.0` returns signature details.

### 6. Harden runtime configuration

Apply defense-in-depth at runtime: drop capabilities, read-only filesystem, resource limits, and prevent privilege escalation. [src1]

```bash
docker run -d \
  --name myapp \
  --user appuser \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges:true \
  --security-opt apparmor=docker-default \
  --memory 512m \
  --cpus 1.0 \
  --pids-limit 100 \
  --restart on-failure:3 \
  myapp:1.0
```

**Verify**: `docker inspect myapp --format '{{json .HostConfig}}'` -- confirm CapDrop, ReadonlyRootfs, Memory, SecurityOpt are set.

## Code Examples

### Dockerfile: Production-Ready Secure Multi-Stage Build

> Full script: [dockerfile-production-ready-secure-multi-stage-bui.Dockerfile](scripts/dockerfile-production-ready-secure-multi-stage-bui.Dockerfile) (26 lines)

```dockerfile
# syntax=docker/dockerfile:1
# Stage 1: Build
FROM node:20.11.1-alpine3.19 AS builder
WORKDIR /app
COPY package*.json ./
# ... (see full script)
```

### docker-compose.yml: Security-Hardened Service Configuration

> Full script: [docker-compose-yml-security-hardened-service-confi.yml](scripts/docker-compose-yml-security-hardened-service-confi.yml) (68 lines)

```yaml
# docker-compose.yml -- security-hardened configuration
version: "3.9"
services:
  api:
    image: myapp:1.0.0  # Pinned version, never :latest
# ... (see full script)
```

### Python: Trivy Scan Wrapper for CI/CD

> Full script: [python-trivy-scan-wrapper-for-ci-cd.py](scripts/python-trivy-scan-wrapper-for-ci-cd.py) (34 lines)

```python
#!/usr/bin/env python3
"""Container security scan wrapper for CI/CD pipelines."""
# Input:  Docker image reference (e.g., myapp:latest)
# Output: Pass/fail with vulnerability summary
import subprocess
# ... (see full script)
```

### Go: Secure Dockerfile for Go Applications

```dockerfile
# Stage 1: Build with full toolchain
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git ca-certificates
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Static binary, no CGO dependencies
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -ldflags="-w -s" -o /app/server ./cmd/server

# Stage 2: Minimal production image
FROM scratch
# Import CA certificates for HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Import the binary
COPY --from=builder /app/server /server
# Run as non-root (UID 65534 = nobody)
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/server"]
```

## Anti-Patterns

### Wrong: Running containers as root

```dockerfile
# BAD -- no USER directive means container runs as root
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
# If attacker escapes container, they have root on host
```

### Correct: Non-root user with minimal permissions

```dockerfile
# GOOD -- dedicated non-root user
FROM node:20.11.1-alpine3.19
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --chown=app:app . .
RUN npm ci --omit=dev
USER app
CMD ["node", "server.js"]
```

### Wrong: Storing secrets in Dockerfile

```dockerfile
# BAD -- secret is cached in image layer forever
FROM node:20-alpine
ENV DATABASE_URL=postgres://admin:s3cret@db:5432/mydb
COPY .env /app/.env
RUN echo "machine github.com login token password ghp_xxxx" > ~/.netrc
```

### Correct: BuildKit secret mounts

```dockerfile
# GOOD -- secret is never stored in image layers
# syntax=docker/dockerfile:1
FROM node:20.11.1-alpine3.19
RUN --mount=type=secret,id=db_url \
    export DATABASE_URL=$(cat /run/secrets/db_url) && \
    npm run migrate
# Secret is gone after this RUN step
```

### Wrong: Using :latest tag and ADD instruction

```dockerfile
# BAD -- unpinned tag and ADD instead of COPY
FROM python:latest
ADD https://example.com/setup.sh /app/
ADD . /app
RUN pip install -r requirements.txt
```

### Correct: Pinned version and COPY instruction

```dockerfile
# GOOD -- pinned version and explicit COPY
FROM python:3.12.2-slim-bookworm
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
```

### Wrong: Running with --privileged and exposed socket

```bash
# BAD -- --privileged grants ALL capabilities, socket gives root host access
docker run --privileged \
  -v /var/run/docker.sock:/var/run/docker.sock \
  myapp:latest
```

### Correct: Minimal capabilities and no socket

```bash
# GOOD -- drop all caps, add only what's needed, no socket
docker run -d \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges:true \
  --read-only \
  --tmpfs /tmp \
  myapp:1.0.0
```

## Common Pitfalls

- **Build cache leaks secrets**: Multi-stage builds don't guarantee secret removal if secrets are copied in early stages. Fix: Use BuildKit `--mount=type=secret` which never writes secrets to layers. [src4]
- **Alpine musl vs glibc incompatibility**: Applications compiled on glibc-based systems may fail on Alpine (musl libc). Fix: Compile on Alpine or use `-slim` Debian variants. Test thoroughly. [src6]
- **Ignoring .dockerignore**: Without `.dockerignore`, `docker build` copies `.git`, `.env`, `node_modules`, and other sensitive/large directories. Fix: Create `.dockerignore` excluding `.git`, `.env`, `*.pem`, `node_modules`. [src4]
- **HEALTHCHECK exposes internal ports**: `HEALTHCHECK CMD curl localhost:3000/health` installs curl in the image, increasing attack surface. Fix: Use `wget` (pre-installed in Alpine) or write a lightweight custom health check binary. [src6]
- **Layer ordering breaks cache**: Copying all source files before `npm install` invalidates the dependency cache on every code change. Fix: Copy `package*.json` first, run `npm ci`, then copy source code. [src6]
- **Read-only filesystem breaks applications**: Some apps need writable directories for logs, uploads, or PID files. Fix: Use `--tmpfs` for ephemeral data and named volumes for persistent data. [src1]
- **Docker Compose .env file in image context**: `docker compose build` copies the `.env` file into the build context by default. Fix: Add `.env` to `.dockerignore` and use `secrets` configuration in compose. [src4]
- **User namespace remapping disabled by default**: Docker's user namespace feature maps container root to host non-root, but is not enabled by default. Fix: Configure `userns-remap` in `/etc/docker/daemon.json`. [src2]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (28 lines)

```bash
# Check if container runs as root
docker exec <container> whoami
docker exec <container> id
# Inspect security configuration of running container
docker inspect <container> --format '{{json .HostConfig.SecurityOpt}}'
# ... (see full script)
```

## Version History & Compatibility

| Component | Version | Status | Key Security Feature |
|---|---|---|---|
| Docker Engine | 28.x | Current | BuildKit default, rootless improvements |
| Docker Engine | 27.x | Supported | Init containers, compose v2 built-in |
| Docker Engine | 25.x | EOL | Leaky Vessels fixes (CVE-2024-21626) |
| CIS Benchmark | v1.8.0 | Current | 27 updated recommendations for Docker 28 |
| CIS Benchmark | v1.7.0 | Previous | Docker 27 support |
| Trivy | 0.58.x | Current | VEX support, SBOM generation, policy engine |
| Cosign | 2.4.x | Current | Keyless signing via OIDC, Rekor transparency log |
| BuildKit | 0.17.x | Current | Secret mounts, SSH forwarding, cache export |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Deploying any Docker container to production | Running trusted code in development only | Basic Docker Compose defaults suffice |
| Building CI/CD pipelines that create images | Using VM-based isolation (no containers) | VM hardening guides |
| Operating multi-tenant container environments | Running Kubernetes (need K8s-specific controls) | Kubernetes security + Pod Security Standards |
| Storing or processing sensitive data in containers | Using serverless functions (no container management) | Cloud provider serverless security docs |

## Important Caveats

- Container isolation is NOT equivalent to VM isolation -- containers share the host kernel, so kernel exploits (e.g., Leaky Vessels CVE-2024-21626) can lead to host compromise
- Rootless mode introduces performance overhead and some incompatibilities with specific network drivers and storage backends
- Alpine Linux uses musl libc instead of glibc, which can cause subtle runtime issues with some applications -- test thoroughly before switching base images
- Docker Content Trust (DCT/Notary v1) is being superseded by Sigstore/Cosign for image signing -- prefer Cosign for new implementations
- The CIS Docker Benchmark is a guideline, not a compliance standard -- adapt recommendations to your threat model and operational requirements
- Seccomp and AppArmor profiles vary by host OS distribution -- default profiles provide baseline protection but may need customization for specific applications

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention Guide](/software/security/xss-prevention/2026)
- [Kubernetes Security Guide](/software/security/kubernetes-security/2026)
