L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Déploiement et opérations · SKILL.md

docker-security

docker-security

Use when hardening Docker deployments — image scanning (Trivy, Scout, Snyk), rootless Docker, user namespaces, seccomp profiles, AppArmor/SELinux integration, image signing (Sigstore cosign, Notation — Docker Content Trust retired 2025), secrets management, read-only containers, Linux capability dropping, CIS Docker Benchmark, supply chain security, and run…

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: docker-security description: Use when hardening Docker deployments — image scanning (Trivy, Scout, Snyk), rootless Docker, user namespaces, seccomp profiles, AppArmor/SELinux integration, image signing (Sigstore cosign, Notation — Docker Content Trust retired 2025), secrets management, read-only containers, Linux capability dropping, CIS Docker Benchmark, supply chain security, and runtime security monitoring. Part of the docker-* skill family. OS-agnostic. family: docker disambiguation: Hardening a deployment — image scanning, rootless, user namespaces, seccomp, AppArmor/SELinux. Pipeline build mechanics are docker-cicd.

Docker Security

OS-agnostic Docker security hardening. For core Docker concepts and Dockerfile patterns, see docker-fundamentals. For OS-specific host hardening, see ubuntu-docker-host or rhel-docker-host.


1. Image Security

Scanning with Trivy

# Install Trivy (universal)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Scan a local image
trivy image myapp:latest

# Scan with severity filter
trivy image --severity HIGH,CRITICAL myapp:latest

# Scan and fail CI if critical vulnerabilities found
trivy image --exit-code 1 --severity CRITICAL myapp:latest

# Scan a Dockerfile for misconfigurations
trivy config Dockerfile

# Scan filesystem (source code dependencies)
trivy fs --scanners vuln,secret,misconfig .

# Generate JSON report
trivy image --format json --output report.json myapp:latest

# Ignore unfixed vulnerabilities
trivy image --ignore-unfixed myapp:latest

# Scan with SBOM output
trivy image --format spdx-json --output sbom.json myapp:latest

Docker Scout

# Enable Docker Scout (Docker Desktop or CLI plugin)
docker scout quickview myapp:latest

# Full CVE analysis
docker scout cves myapp:latest

# Compare two image versions
docker scout compare --to myapp:v1.0.0 myapp:v1.1.0

# Recommendations for base image updates
docker scout recommendations myapp:latest

# SBOM generation
docker scout sbom myapp:latest

Snyk Container Scanning

# Authenticate
snyk auth

# Scan image
snyk container test myapp:latest

# Scan and monitor (continuous tracking)
snyk container monitor myapp:latest

# Scan with severity threshold
snyk container test --severity-threshold=high myapp:latest

# Scan Dockerfile for best practices
snyk container test --file=Dockerfile myapp:latest

Choosing Secure Base Images

# BEST: Distroless — no shell, no package manager, minimal attack surface
FROM gcr.io/distroless/static-debian12        # Go/Rust static binaries
FROM gcr.io/distroless/base-debian12          # C/C++ with glibc
FROM gcr.io/distroless/java21-debian12        # Java apps
FROM gcr.io/distroless/nodejs22-debian12      # Node.js apps
FROM gcr.io/distroless/python3-debian12       # Python apps

# GOOD: Alpine — small (~5MB), uses musl libc
FROM node:22-alpine
FROM python:3.12-alpine

# ACCEPTABLE: Slim — Debian with non-essential packages removed
FROM node:22-slim
FROM python:3.12-slim

# AVOID: Full images — large, many unnecessary packages
# FROM node:22        # ~350MB more attack surface
# FROM ubuntu:24.04   # general-purpose, not optimized for containers

Pinning Image Digests

# BAD: Tags are mutable — "latest" or even "1.27" can change underneath you
FROM nginx:1.27-alpine

# GOOD: Pin to digest for reproducible, tamper-proof builds
FROM nginx:1.27-alpine@sha256:a5127daff3d6f4a3ed1db3...

# Find digest
# docker inspect --format='{{index .RepoDigests 0}}' nginx:1.27-alpine
# docker pull nginx:1.27-alpine && docker images --digests nginx

SBOM Generation and Provenance

# Generate SBOM with Trivy
trivy image --format spdx-json --output sbom.spdx.json myapp:latest
trivy image --format cyclonedx --output sbom.cdx.json myapp:latest

# Generate SBOM with syft
syft myapp:latest -o spdx-json > sbom.spdx.json
syft myapp:latest -o cyclonedx-json > sbom.cdx.json

# Build with provenance attestation (BuildKit)
docker buildx build --provenance=true --sbom=true -t myapp:latest .

# Inspect attestations
docker buildx imagetools inspect myapp:latest --format '{{json .Provenance}}'
docker buildx imagetools inspect myapp:latest --format '{{json .SBOM}}'

2. Image Signing

Current guidance (verified 2026-06-10): sign and verify images with Sigstore cosign (keyless or key-based) or Notation (Notary Project / Notary v2), and pin base images by digest. Docker Content Trust (DCT) is RETIRED — Docker announced its retirement in 2025; see the legacy reference at the end of this section for the timeline.

cosign (Sigstore) — primary recommendation

# Install cosign
go install github.com/sigstore/cosign/v2/cmd/cosign@latest
# Or download a release binary: https://github.com/sigstore/cosign/releases

# ALWAYS sign by digest, not tag — tags are mutable
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' myregistry.example.com/myapp:v1.0.0)

# Sign image (keyless — uses your OIDC identity, records to the Rekor transparency log)
cosign sign "$DIGEST"

# Sign image with a key pair (air-gapped / no-OIDC environments)
cosign generate-key-pair
cosign sign --key cosign.key "$DIGEST"

# Verify a key-based signature
cosign verify --key cosign.pub myregistry.example.com/myapp:v1.0.0

# Verify a keyless signature (pin BOTH identity and issuer)
cosign verify --certificate-identity user@example.com \
  --certificate-oidc-issuer https://accounts.google.com \
  myregistry.example.com/myapp:v1.0.0

# Keyless in CI (e.g., GitHub Actions OIDC) — verify the workflow identity
cosign verify \
  --certificate-identity-regexp '^https://github.com/myorg/myapp/\.github/workflows/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  myregistry.example.com/myapp:v1.0.0

Notation (Notary Project / Notary v2)

# Install notation — see https://notaryproject.dev for current releases
# Generate a test key + self-signed cert (production: use a CA-issued cert
# or a KMS plugin — Azure Key Vault, AWS Signer, HashiCorp Vault)
notation cert generate-test --default "myorg.example.com"

# Sign (by digest)
notation sign registry.example.com/myapp@sha256:abc123...

# List signatures on an artifact
notation ls registry.example.com/myapp@sha256:abc123...

# Configure a trust policy, then verify
notation policy import trustpolicy.json
notation verify registry.example.com/myapp@sha256:abc123...

Notation is spec-driven (OCI signatures attached in the registry), supports multiple signatures per artifact, and integrates with enterprise PKI/KMS — it is the natural DCT successor for organizations with existing certificate infrastructure. cosign keyless is the lower-friction choice for OIDC-centric (CI-driven) workflows.

Docker Content Trust (RETIRED — legacy reference)

RETIRED. Docker announced the retirement of Docker Content Trust in 2025 (announced 2025; verify current status):

  • 2025-08-08 — the oldest Docker Official Image (DOI) DCT signing certificates began expiring. With DOCKER_CONTENT_TRUST=1 set, DOI pulls fail; docker trust inspect also fails for DOI.
  • 2025-09-30 — DCT can no longer be enabled on new registries.
  • 2028-03-31 — all DCT trust data will be permanently deleted and the feature removed.

Upstream Notary v1 is unmaintained, and fewer than 0.05% of Docker Hub pulls used DCT. Docker's official recommendation is to migrate to Sigstore cosign or Notation (above). Do NOT set DOCKER_CONTENT_TRUST=1 in new setups — and unset it where present to avoid pull failures.

The commands below are retained ONLY for auditing/decommissioning legacy DCT estates (e.g., a private registry still running Notary v1):

# Find lingering DCT enablement (remove it — causes pull failures since 2025-08)
env | grep DOCKER_CONTENT_TRUST
unset DOCKER_CONTENT_TRUST

# Inspect remaining trust data on a legacy private registry
docker trust inspect --pretty registry.example.com/myapp

# Inventory local signing keys before key destruction
docker trust key list

# Revoke trust data for a tag during decommissioning
docker trust revoke registry.example.com/myapp:v1.0.0

3. Rootless Docker

# Install rootless Docker (run as non-root user, NOT as root)
dockerd-rootless-setuptool.sh install

# Verify rootless mode
docker info --format '{{.SecurityOptions}}'
# Should include "rootless"

# Start/stop rootless daemon
systemctl --user start docker
systemctl --user stop docker
systemctl --user enable docker    # auto-start on login

# Enable lingering (keep services running after logout)
sudo loginctl enable-linger $(whoami)

Rootless Configuration

# Rootless Docker stores data in user home
# Default: ~/.local/share/docker

# Set custom data root
mkdir -p ~/.config/docker
cat > ~/.config/docker/daemon.json <<'EOF'
{
  "data-root": "/data/docker-rootless"
}
EOF

Rootless Limitations

FeatureRootless SupportWorkaround
Binding ports < 1024No (by default)sysctl net.ipv4.ip_unprivileged_port_start=0 or use slirp4netns port driver
--net=hostLimitedUse slirp4netns (default) or pasta
AppArmorNoUse rootless + user namespaces instead
Overlay network (Swarm)NoUse alternative orchestration
cgroup v1 resource limitsNoMigrate to cgroup v2
PingNo (by default)sysctl net.ipv4.ping_group_range="0 2147483647"
NFS/FUSE mounts in containersLimitedBind mount from host

When to Use Rootless

  • Use rootless for development environments, CI runners, multi-tenant hosts, and anywhere the daemon itself should not run as root.
  • Use rootful with hardening for production workloads needing privileged ports, overlay networking, or AppArmor/SELinux enforcement.

4. User Namespaces

User namespaces remap container UID/GID ranges so that root (UID 0) inside the container maps to an unprivileged user on the host.

Enable userns-remap

# Create subordinate UID/GID ranges
sudo usermod --add-subuids 100000-165535 dockremap
sudo usermod --add-subgids 100000-165535 dockremap

# Or manually edit
# /etc/subuid: dockremap:100000:65536
# /etc/subgid: dockremap:100000:65536

# Configure daemon
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "userns-remap": "default"
}
EOF

# "default" creates a dockremap user automatically
# Or specify a user: "userns-remap": "myuser"

# Restart Docker
sudo systemctl restart docker

# Verify — root in container should map to high UID on host
docker run --rm alpine id
# uid=0(root) gid=0(root) — but host sees UID 100000

Volume Permission Implications

# With userns-remap, container UID 0 maps to host UID 100000
# Volumes must be owned by the remapped UID on the host

# Example: container writes as UID 1000 -> host UID 101000
sudo chown -R 101000:101000 /data/myapp

# To find the mapped UID:
# Container UID + subordinate UID start = host UID
# 1000 + 100000 = 101000

# Disable userns-remap per container (requires daemon-level setting)
docker run --userns=host myapp    # runs without remapping

5. Container Hardening

Non-Root USER in Dockerfile

# Create user and group
RUN groupadd -r appgroup && useradd -r -g appgroup -d /app -s /sbin/nologin appuser

# Own application files
COPY --chown=appuser:appgroup . /app

# Switch to non-root user (do this LAST, after all root-required operations)
USER appuser

Read-Only Root Filesystem

# Run with read-only filesystem
docker run -d --read-only myapp

# Containers often need tmpfs for temporary files
docker run -d --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --tmpfs /run:rw,noexec,nosuid \
  myapp

# With writable volume for data
docker run -d --read-only \
  --tmpfs /tmp \
  -v appdata:/app/data \
  myapp

Dropping Capabilities

# Drop ALL capabilities, then add only what is needed
docker run -d \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  myapp

# Common capabilities to add back selectively:
#   NET_BIND_SERVICE  — bind ports < 1024
#   CHOWN             — change file ownership
#   SETUID/SETGID     — change process UID/GID
#   DAC_OVERRIDE      — bypass file permission checks (avoid if possible)
#   SYS_PTRACE        — needed for debugging tools (never in production)

# List current container capabilities
docker exec <container> cat /proc/1/status | grep -i cap

# Decode capability hex
capsh --decode=00000000a80425fb

No-New-Privileges

# Prevent privilege escalation via setuid/setgid binaries
docker run -d --security-opt=no-new-privileges myapp

# This blocks:
#   - setuid binaries (su, sudo, ping)
#   - setgid binaries
#   - Any process from gaining more privileges than its parent

Disable Inter-Container Communication

# Default: containers on the same bridge can communicate
# Disable at daemon level
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "icc": false
}
EOF
sudo systemctl restart docker

# With icc=false, containers must use explicit --link or user-defined networks
# User-defined networks provide DNS-based discovery and automatic isolation

Combined Hardened Run

docker run -d \
  --name secure-app \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --security-opt=no-new-privileges \
  --security-opt=apparmor=docker-default \
  --memory=512m \
  --cpus=1 \
  --pids-limit=100 \
  --user 1000:1000 \
  --restart=unless-stopped \
  --health-cmd="curl -f http://localhost:8080/health || exit 1" \
  --health-interval=30s \
  myapp:v1.0.0@sha256:abc123...

6. Seccomp Profiles

Seccomp (Secure Computing) filters restrict which Linux syscalls a container can make.

Default Profile

# Docker applies a default seccomp profile that blocks ~44 dangerous syscalls
# including: mount, reboot, clock_settime, init_module, delete_module, etc.

# Verify seccomp is active
docker info --format '{{.SecurityOptions}}'

# Run with default profile (explicit)
docker run --security-opt seccomp=unconfined myapp    # DANGEROUS: disables seccomp
docker run --security-opt seccomp=default myapp       # explicit default (Docker 20.10+)

Custom Seccomp Profile

# Generate default profile as starting point
docker run --rm -it alpine cat /proc/1/status | grep Seccomp

# Create custom profile
cat > seccomp-strict.json <<'PROFILE'
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 1,
  "architectures": [
    "SCMP_ARCH_X86_64",
    "SCMP_ARCH_AARCH64"
  ],
  "syscalls": [
    {
      "names": [
        "accept", "accept4", "access", "bind", "brk", "chdir",
        "chmod", "chown", "close", "connect", "dup", "dup2", "dup3",
        "epoll_create", "epoll_create1", "epoll_ctl", "epoll_wait",
        "epoll_pwait", "execve", "exit", "exit_group", "faccessat",
        "fchmod", "fchown", "fcntl", "fstat", "fstatfs", "futex",
        "getcwd", "getdents64", "getegid", "geteuid", "getgid",
        "getpid", "getppid", "getuid", "ioctl", "listen", "lseek",
        "lstat", "madvise", "memfd_create", "mkdir", "mmap", "mprotect",
        "munmap", "nanosleep", "newfstatat", "open", "openat",
        "pipe", "pipe2", "poll", "ppoll", "pread64", "pwrite64",
        "read", "readlink", "recvfrom", "recvmsg", "rename",
        "rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
        "sched_getaffinity", "sched_yield", "select", "sendmsg",
        "sendto", "set_robust_list", "set_tid_address", "setgid",
        "setgroups", "setsockopt", "setuid", "shutdown", "sigaltstack",
        "socket", "stat", "statfs", "symlink", "tgkill", "umask",
        "uname", "unlink", "wait4", "write", "writev"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
PROFILE

# Apply custom profile
docker run -d --security-opt seccomp=seccomp-strict.json myapp

Profiling Syscalls (Build a Minimal Profile)

# Use strace to discover which syscalls your app actually uses
docker run --rm --security-opt seccomp=unconfined \
  strace -c -f myapp 2>&1 | tail -30

# Or use OCI seccomp BPF logging to generate a profile
# https://github.com/containers/oci-seccomp-bpf-hook

7. AppArmor / SELinux

AppArmor (Debian/Ubuntu)

# Docker applies "docker-default" AppArmor profile automatically
# Check current profile
docker inspect --format='{{.AppArmorProfile}}' <container>

# Run with specific profile
docker run -d --security-opt apparmor=docker-default myapp

# Run without AppArmor (not recommended)
docker run -d --security-opt apparmor=unconfined myapp

# Load custom AppArmor profile
sudo apparmor_parser -r -W /etc/apparmor.d/docker-myapp
docker run -d --security-opt apparmor=docker-myapp myapp

Custom AppArmor Profile Example

# /etc/apparmor.d/docker-myapp
#include <tunables/global>

profile docker-myapp flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>

  # Allow network access
  network inet tcp,
  network inet udp,
  network inet6 tcp,

  # Allow reading application files
  /app/** r,
  /app/node_modules/** mr,

  # Allow tmp writes
  /tmp/** rw,

  # Deny sensitive paths
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /proc/*/mem rw,
  deny /sys/firmware/** r,

  # Deny mount operations
  deny mount,
  deny umount,

  # Deny raw socket access
  deny network raw,
  deny network packet,
}

SELinux (RHEL/Fedora/CentOS)

# Check SELinux status
getenforce                    # Enforcing, Permissive, or Disabled
sestatus                      # detailed status

# Docker volume SELinux labels
docker run -v /host/data:/data:Z myapp     # :Z = private (single container)
docker run -v /host/data:/data:z myapp     # :z = shared (multiple containers)

# The :Z flag relabels the volume with the container's exact SELinux label
# The :z flag relabels the volume with a shared label

# Check container SELinux context
docker inspect --format='{{.ProcessLabel}}' <container>
ps -eZ | grep docker

# Custom SELinux type for container process
docker run -d --security-opt label=type:svirt_apache_t myapp

# Disable SELinux confinement for a container (not recommended)
docker run -d --security-opt label=disable myapp

When to Use Which

SystemAppArmorSELinux
Ubuntu/DebianDefault, simpler profilesAvailable but not default
RHEL/CentOS/FedoraNot defaultDefault, mandatory
ComplexityPath-based, easier to writeLabel-based, steeper learning curve
GranularityFile path rulesProcess + file + network labels
Docker default profileYes (docker-default)Yes (automatic labeling)
RecommendationUse where availableUse where it is the OS default

8. Secrets Management

Docker Secrets (Swarm Mode)

# Create a secret
echo "s3cr3t_pa55w0rd" | docker secret create db_password -
docker secret create tls_cert /path/to/cert.pem

# List and inspect
docker secret ls
docker secret inspect db_password

# Use in a service
docker service create \
  --name mydb \
  --secret db_password \
  postgres:16

# Secret is mounted at /run/secrets/db_password inside the container
# Application reads it:
# password = open('/run/secrets/db_password').read().strip()

BuildKit Secret Mounts (Build-Time)

# syntax=docker/dockerfile:1

# Secret is available during build but NEVER stored in any layer
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) \
    npm ci --production

# For .npmrc or pip.conf
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci --production

# Multiple secrets
RUN --mount=type=secret,id=aws_key \
    --mount=type=secret,id=aws_secret \
    aws s3 cp s3://bucket/data /app/data
# Pass secrets during build
docker build \
  --secret id=npm_token,src=$HOME/.npm_token \
  --secret id=npmrc,src=$HOME/.npmrc \
  -t myapp .

# From environment variable
echo "$NPM_TOKEN" | docker build --secret id=npm_token -t myapp .

Environment Variable Risks

# BAD: Secrets in ENV are exposed everywhere
docker run -e DB_PASSWORD=secret123 myapp

# Visible via inspect
docker inspect <container> --format='{{.Config.Env}}'
# Output: [DB_PASSWORD=secret123 ...]

# Visible inside container
docker exec <container> env | grep DB_PASSWORD

# Visible in /proc
docker exec <container> cat /proc/1/environ | tr '\0' '\n'

External Vault Integration Patterns

# HashiCorp Vault — agent sidecar pattern
docker run -d \
  --name vault-agent \
  -v vault-secrets:/secrets \
  -e VAULT_ADDR=https://vault.example.com:8200 \
  hashicorp/vault agent -config=/etc/vault/agent.hcl

docker run -d \
  --name myapp \
  -v vault-secrets:/secrets:ro \
  myapp

# Application reads /secrets/db-password written by vault-agent

# AWS Secrets Manager — init container pattern
docker run --rm \
  -v app-secrets:/secrets \
  -e AWS_DEFAULT_REGION=us-east-1 \
  amazon/aws-cli secretsmanager get-secret-value \
    --secret-id prod/myapp/db --query SecretString \
    --output text > /secrets/db_password

docker run -d -v app-secrets:/secrets:ro myapp

9. Network Security

Internal Networks

# Create internal network — no external/internet access
docker network create --internal isolated_net

# Containers on internal networks can talk to each other
# but cannot reach the internet or be reached from outside
docker run -d --network isolated_net --name backend myapp
docker run -d --network isolated_net --name db postgres:16

# Multi-network pattern: frontend talks to internet AND backend
docker network create frontend_net
docker network create --internal backend_net

docker run -d --network frontend_net --name web nginx
docker network connect backend_net web                    # web on both networks
docker run -d --network backend_net --name api myapi      # api on internal only
docker run -d --network backend_net --name db postgres:16  # db on internal only

Encrypted Overlay Networks (Swarm)

# Create encrypted overlay (IPsec encryption for data plane)
docker network create --driver overlay --opt encrypted secure_overlay

# All traffic between nodes on this network is encrypted via IPsec
docker service create --network secure_overlay --name myservice myapp

TLS for Docker Daemon

# Generate CA and server certificates
openssl genrsa -aes256 -out ca-key.pem 4096
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem

openssl genrsa -out server-key.pem 4096
openssl req -new -key server-key.pem -subj "/CN=docker-host" -out server.csr

echo subjectAltName = DNS:docker-host,IP:192.168.1.100 > extfile.cnf
echo extendedKeyUsage = serverAuth >> extfile.cnf
openssl x509 -req -days 365 -sha256 -in server.csr \
  -CA ca.pem -CAkey ca-key.pem -CAcreateserial \
  -out server-cert.pem -extfile extfile.cnf

# Generate client certificates
openssl genrsa -out key.pem 4096
openssl req -new -key key.pem -subj "/CN=client" -out client.csr
echo extendedKeyUsage = clientAuth > extfile-client.cnf
openssl x509 -req -days 365 -sha256 -in client.csr \
  -CA ca.pem -CAkey ca-key.pem -CAcreateserial \
  -out cert.pem -extfile extfile-client.cnf

# Configure daemon for TLS
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "tls": true,
  "tlsverify": true,
  "tlscacert": "/etc/docker/certs/ca.pem",
  "tlscert": "/etc/docker/certs/server-cert.pem",
  "tlskey": "/etc/docker/certs/server-key.pem",
  "hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"]
}
EOF

# Client usage
docker --tlsverify \
  --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \
  -H=tcp://docker-host:2376 ps

Restricting Published Ports

# Bind to specific interface (not 0.0.0.0)
docker run -d -p 127.0.0.1:8080:8080 myapp           # localhost only
docker run -d -p 192.168.1.100:8080:8080 myapp        # specific interface

# Default daemon binding (affects all published ports)
sudo tee /etc/docker/daemon.json <<'EOF'
{
  "ip": "127.0.0.1"
}
EOF

# Docker bypasses iptables/firewalld for port publishing
# Use DOCKER-USER chain for firewall rules that survive restarts
sudo iptables -I DOCKER-USER -i eth0 -j DROP
sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 443 -j ACCEPT
sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 80 -j ACCEPT

10. CIS Docker Benchmark

The CIS Docker Benchmark provides consensus security configuration guidelines. Use docker-bench-security for automated checking.

Automated Scanning

# Run docker-bench-security
docker run --rm --net host --pid host --userns host --cap-add audit_control \
  -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
  -v /var/lib:/var/lib:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /usr/lib/systemd:/usr/lib/systemd:ro \
  -v /etc:/etc:ro \
  docker/docker-bench-security

# Note: older CIS benchmark versions include a "Content trust enabled"
# check (DOCKER_CONTENT_TRUST=1). DCT is retired (announced 2025) — treat
# that check as obsolete; satisfy the signing intent with cosign/Notation
# verification gates instead (see Section 2).

# Output JSON report
docker run --rm --net host --pid host --userns host --cap-add audit_control \
  -v /var/lib:/var/lib:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /etc:/etc:ro \
  -l docker_bench_security \
  docker/docker-bench-security -l /dev/stdout 2>/dev/null

Key CIS Recommendations

Host Configuration:

#RecommendationHow
1.1.1Use a separate partition for containersMount /var/lib/docker on dedicated partition
1.1.3Audit Docker daemonauditctl -w /usr/bin/dockerd -k docker
1.1.4Audit Docker filesauditctl -w /var/lib/docker -k docker
1.1.5Audit docker.sockauditctl -w /var/run/docker.sock -k docker
1.1.7Audit Docker configauditctl -w /etc/docker -k docker

Daemon Configuration:

#RecommendationHow
2.1Restrict network traffic between containers"icc": false in daemon.json
2.2Set logging level to info"log-level": "info" in daemon.json
2.3Allow Docker to make iptables changes"iptables": true (default)
2.5Do not use insecure registriesEnsure "insecure-registries" is empty
2.6Setup a centralized log driver"log-driver": "syslog" or "json-file" with rotation
2.8Enable user namespace support"userns-remap": "default"
2.11Use authorization plugin"authorization-plugins": ["auth-plugin"]
2.14Enable live restore"live-restore": true
2.17Do not use Swarm if not neededdocker swarm leave --force

Container Runtime:

#RecommendationHow
5.1Do not disable AppArmorNever use --security-opt apparmor=unconfined
5.2Verify SELinux if applicableUse :Z or :z on volumes
5.3Restrict Linux capabilities--cap-drop=ALL --cap-add=...
5.4Do not use privileged containersNever use --privileged
5.7Do not map privileged portsAvoid mapping to ports < 1024 unless necessary
5.10Limit memory--memory=512m
5.12Mount root filesystem read-only--read-only
5.15Do not share host process namespaceNever use --pid=host
5.16Do not share host IPC namespaceNever use --ipc=host
5.25Restrict container from gaining privileges--security-opt=no-new-privileges
5.26Check container healthDefine HEALTHCHECK in Dockerfile
5.28Use PIDs limit--pids-limit=100
5.31Do not mount Docker socketNever mount /var/run/docker.sock

daemon.json CIS-Aligned Configuration

{
  "icc": false,
  "log-level": "info",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5"
  },
  "storage-driver": "overlay2",
  "live-restore": true,
  "userland-proxy": false,
  "no-new-privileges": true,
  "userns-remap": "default",
  "seccomp-profile": "/etc/docker/seccomp-default.json",
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 65536,
      "Soft": 32768
    },
    "nproc": {
      "Name": "nproc",
      "Hard": 4096,
      "Soft": 2048
    }
  }
}

11. Runtime Security

Falco (Runtime Threat Detection)

# Run Falco in a container (OS-agnostic — recommended)
# For host-level installation, see ubuntu-docker-host or rhel-docker-host
docker run -d --name falco \
  --privileged \
  -v /var/run/docker.sock:/host/var/run/docker.sock:ro \
  -v /proc:/host/proc:ro \
  -v /dev:/host/dev \
  -v /etc:/host/etc:ro \
  falcosecurity/falco:latest

Custom Falco Rules

# /etc/falco/falco_rules.local.yaml

# Detect shell spawned in container
- rule: Shell Spawned in Container
  desc: Detect shell execution in a container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh, dash, ash, csh, ksh, fish)
  output: >
    Shell spawned in container
    (container=%container.name command=%proc.cmdline user=%user.name image=%container.image.repository)
  priority: WARNING

# Detect crypto mining processes
- rule: Detect Cryptominer
  desc: Detect known cryptominer process names
  condition: >
    spawned_process and container and
    (proc.name in (xmrig, minerd, minergate, cpuminer, ethminer) or
     proc.cmdline contains "stratum+tcp" or
     proc.cmdline contains "stratum+ssl" or
     proc.cmdline contains "cryptonight")
  output: >
    Cryptominer detected in container
    (container=%container.name command=%proc.cmdline image=%container.image.repository)
  priority: CRITICAL

# Detect sensitive file access
- rule: Read Sensitive File in Container
  desc: Detect reads to sensitive files
  condition: >
    open_read and container and
    fd.name in (/etc/shadow, /etc/sudoers, /root/.ssh/authorized_keys, /root/.bash_history)
  output: >
    Sensitive file read in container
    (container=%container.name file=%fd.name command=%proc.cmdline)
  priority: ERROR

Audit Logging

# Enable audit rules for Docker
sudo tee /etc/audit/rules.d/docker.rules <<'EOF'
-w /usr/bin/dockerd -p rwxa -k docker
-w /var/lib/docker -p rwxa -k docker
-w /etc/docker -p rwxa -k docker
-w /lib/systemd/system/docker.service -p rwxa -k docker
-w /lib/systemd/system/docker.socket -p rwxa -k docker
-w /var/run/docker.sock -p rwxa -k docker
-w /etc/default/docker -p rwxa -k docker
-w /etc/docker/daemon.json -p rwxa -k docker
EOF

sudo systemctl restart auditd

# Query audit logs
sudo ausearch -k docker --start today

Container Behavior Monitoring

# Monitor container events in real time
docker events --filter type=container

# Monitor with formatting
docker events --filter type=container \
  --format '{{.Time}} {{.Action}} {{.Actor.Attributes.name}} {{.Actor.Attributes.image}}'

# Monitor specific actions
docker events --filter event=start --filter event=die --filter event=oom

# Check for containers running as root
docker ps -q | xargs -I{} docker inspect --format '{{.Name}}: User={{.Config.User}}' {}

# Detect containers without resource limits
docker ps -q | xargs -I{} docker inspect \
  --format '{{.Name}}: Memory={{.HostConfig.Memory}} CPUs={{.HostConfig.NanoCpus}}' {}

12. Supply Chain Security

Trusted Base Images

# Use Docker Official Images (library/*)
docker pull nginx              # docker.io/library/nginx — Docker Official
docker pull python:3.12-slim   # Docker Official

# Use Docker Verified Publisher images
docker pull bitnami/postgresql

# Check image provenance/attestations (do NOT use `docker trust inspect` —
# DCT is retired and it fails for Docker Official Images since 2025-08)
docker buildx imagetools inspect nginx --format '{{json .Provenance}}'
docker scout quickview nginx

# Pin by digest regardless of signing tooling
docker inspect --format='{{index .RepoDigests 0}}' nginx

# Never pull from unverified third-party registries in production

Dockerfile Linting with Hadolint

# Run hadolint
docker run --rm -i hadolint/hadolint < Dockerfile

# With ignore rules
docker run --rm -i hadolint/hadolint \
  --ignore DL3008 --ignore DL3009 < Dockerfile

# .hadolint.yaml configuration
cat > .hadolint.yaml <<'EOF'
ignored:
  - DL3008    # Pin apt package versions
  - DL3009    # Delete apt lists

trustedRegistries:
  - docker.io
  - gcr.io
  - ghcr.io

override:
  error:
    - DL3001  # Do not use "last" tag
    - DL3002  # Do not use setuid/setgid in RUN
  warning:
    - DL3042  # Avoid cache dir with pip
EOF

docker run --rm -i -v $(pwd)/.hadolint.yaml:/.config/hadolint.yaml \
  hadolint/hadolint < Dockerfile

Key Hadolint Rules

RuleDescription
DL3000Use absolute WORKDIR
DL3001Do not use apt-get dist-upgrade
DL3002Last USER should not be root
DL3003Use WORKDIR instead of cd
DL3006Always tag image in FROM
DL3007Using latest is prone to errors
DL3008Pin versions in apt-get install
DL3018Pin versions in apk add
DL3025Use JSON notation for CMD/ENTRYPOINT
DL3059Multiple consecutive RUN — consider consolidation
DL4006Set SHELL option -o pipefail before RUN with pipe

CI Scanning Gates

# GitHub Actions — scan and gate
name: Container Security
on: push

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4  # pin to SHA in production

      - name: Lint Dockerfile
        uses: hadolint/hadolint-action@v3  # pin to SHA in production
        with:
          dockerfile: Dockerfile

      - name: Build image
        run: docker build -t myapp:${{ github.sha }} .

      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@v0.24.0  # pin to SHA in production
        with:
          image-ref: myapp:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v3  # pin to SHA in production
        if: always()
        with:
          sarif_file: 'trivy-results.sarif'
# GitLab CI — scan and gate
container_scan:
  stage: test
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t myapp:${CI_COMMIT_SHA} .
    - |
      docker run --rm \
        -v /var/run/docker.sock:/var/run/docker.sock \
        aquasec/trivy image \
        --exit-code 1 \
        --severity HIGH,CRITICAL \
        myapp:${CI_COMMIT_SHA}
  allow_failure: false

SLSA Provenance

# Build with SLSA provenance (BuildKit)
docker buildx build \
  --provenance=mode=max \
  --sbom=true \
  --push \
  -t registry.example.com/myapp:v1.0.0 .

# Verify provenance
docker buildx imagetools inspect registry.example.com/myapp:v1.0.0 \
  --format '{{json .Provenance.SLSA}}'

# Using SLSA GitHub Generator for container images
# Generates SLSA Level 3 provenance in GitHub Actions
# See: https://github.com/slsa-framework/slsa-github-generator

# Verify with slsa-verifier
slsa-verifier verify-image registry.example.com/myapp:v1.0.0 \
  --source-uri github.com/myorg/myapp \
  --source-tag v1.0.0

Quick Reference: Security Checklist

BUILD TIME:
  [ ] Multi-stage builds to minimize final image
  [ ] Distroless or minimal base image
  [ ] Image digest pinning in FROM
  [ ] Non-root USER instruction
  [ ] BuildKit secret mounts (no secrets in layers)
  [ ] COPY --chown for proper file ownership
  [ ] hadolint in CI pipeline
  [ ] Trivy/Scout/Snyk scan gate in CI

RUNTIME:
  [ ] --cap-drop=ALL with selective --cap-add
  [ ] --read-only with tmpfs where needed
  [ ] --security-opt=no-new-privileges
  [ ] --memory and --cpus limits set
  [ ] --pids-limit set
  [ ] --user (non-root) or USER in Dockerfile
  [ ] Health checks defined
  [ ] Seccomp profile active (default or custom)
  [ ] AppArmor/SELinux not disabled

INFRASTRUCTURE:
  [ ] Docker daemon TLS enabled (if remote API)
  [ ] icc=false or user-defined networks
  [ ] Published ports bound to specific interfaces
  [ ] Image signing with cosign or Notation + digest pinning (Docker Content Trust retired 2025 — unset DOCKER_CONTENT_TRUST)
  [ ] Centralized logging configured
  [ ] Audit rules for Docker files and socket
  [ ] Runtime monitoring (Falco or equivalent)
  [ ] docker-bench-security run regularly


Anti-Patterns

Anti-PatternWhy It FailsCorrect Approach
Running containers with --privileged flagDisables all security boundaries; container has full host access; equivalent to running on bare metalUse specific capabilities (--cap-add) for only what is needed; never use --privileged in production
Using images from untrusted registries without verificationSupply chain attacks; malicious layers; cryptominers in base imagesPin images by digest, not tag; sign/verify with cosign or Notation (Docker Content Trust retired 2025 — do not rely on it); scan with Trivy/Scout
Mounting Docker socket into containersAny container with socket access can create privileged containers; full host compromiseUse Docker-in-Docker (dind) with TLS for CI; or use socket proxy with read-only API access
Storing secrets in environment variablesVisible in docker inspect, process listing, and container logsUse Docker secrets, tmpfs-mounted files, or external secret managers (Vault, AWS SM)
Not setting read-only root filesystemAttackers can modify binaries, plant backdoors, or write malware inside the containerUse --read-only flag; mount specific writable directories as tmpfs for temp files

Related Skills

TopicSkill
Core Docker concepts, Dockerfile patterns, BuildKitdocker-fundamentals
Bridge, overlay, DNS, multi-host networkingdocker-networking
Volumes, bind mounts, storage driversdocker-storage
CI/CD pipelines, multi-platform builds, registriesdocker-cicd
Compose patterns and advanced orchestrationdocker-compose-patterns
Operational admin, cross-platform, AD mappingdocker-admin
Docker on Ubuntu 24.04ubuntu-docker-host
Podman/Docker on RHEL 9rhel-docker-host
Python authentication and security patternspython-auth-security
Skills enregistrées