Die Oberfläche ist lokalisiert; Skill-Beschreibungen und -Dokumente werden per KI automatisch in Ihre Sprache übersetzt. Code, Befehle, Pfade und Fachbegriffe bleiben im Original, maßgeblich ist das Dokument des Autors.
Skills entdecken
Bereitstellung und Betrieb · 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

Skill-Dokumentation

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
Gespeicherte Skills