Home/Engineer/Docker / Containers/Container Security In Depth …
Docker / ContainersAdvancedUbuntuDebianCentOSRHELTrivySeccompAppArmor

Container Security In Depth — Image Scanning · Runtime Security · Least Privilege

Covers every layer of production container security — Trivy image vulnerability scanning, Seccomp and AppArmor runtime isolation, rootless containers, and Docker Content Trust.

Contents
  1. The Four Layers of Container Security
  2. 1. Image Security — Trivy Vulnerability Scanning
  3. 2. Least-Privilege Dockerfile
  4. 3. Runtime Security — Seccomp Profiles
  5. 4. Applying an AppArmor Profile
  6. 5. Rootless Docker
  7. 6. Docker Content Trust — Image Signature Verification
  8. 7. Runtime Anomaly Detection — Falco

The Four Layers of Container Security

Container security means locking down all four layers: image → runtime → orchestrator → network. If any one of them is left open, the entire stack is exposed.


1. Image Security — Trivy Vulnerability Scanning

Installing Trivy and Running a Basic Scan

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

# Scan an image
trivy image nginx:latest

# Show CRITICAL/HIGH only
trivy image --severity CRITICAL,HIGH nginx:latest

# JSON output (CI pipeline integration)
trivy image --format json --output result.json nginx:latest

# Scan a local Dockerfile (before build)
trivy config Dockerfile

# Filesystem scan (code repository)
trivy fs --severity HIGH,CRITICAL .

GitHub Actions CI Integration

YAML
# .github/workflows/security.yml
name: Container Security Scan

on: [push, pull_request]

jobs:
  trivy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Run Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: app:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: 1          # Fail the build if CRITICAL findings are detected

      - name: Upload to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

2. Least-Privilege Dockerfile

Dockerfile
# ❌ Anti-pattern
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl python3
COPY . /app
CMD ["python3", "/app/server.py"]
# Runs as root and includes unnecessary packages

# ✅ Production pattern
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.12-slim
# Create a dedicated unprivileged user
RUN groupadd -r appuser && useradd -r -g appuser -s /sbin/nologin appuser

WORKDIR /app
COPY --from=builder /install /usr/local
COPY --chown=appuser:appuser src/ .

# Remove write permissions
RUN chmod -R 555 /app

USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/healthz || exit 1
CMD ["python3", "server.py"]

Minimizing Image Size

Dockerfile
# Distroless: minimal image with no shell or package manager
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /app /app
WORKDIR /app
USER nonroot:nonroot
CMD ["server.py"]

# Alpine-based (Go binary)
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY go.* .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o server .

FROM scratch               # Empty image: no shell, no libraries
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER 65534:65534           # nobody
ENTRYPOINT ["/server"]

3. Runtime Security — Seccomp Profiles

Seccomp restricts the system calls a container can make using an allow-list.

JSON
// seccomp-profile.json (minimal allow-list profile)
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": [
        "read", "write", "open", "close", "stat", "fstat",
        "mmap", "mprotect", "munmap", "brk",
        "rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
        "ioctl", "access", "pipe", "select", "sched_yield",
        "mremap", "msync", "mincore", "madvise",
        "dup", "dup2", "pause", "nanosleep", "getitimer",
        "alarm", "setitimer", "getpid", "sendfile",
        "socket", "connect", "accept", "sendto", "recvfrom",
        "sendmsg", "recvmsg", "shutdown", "bind", "listen",
        "getsockname", "getpeername", "socketpair",
        "setsockopt", "getsockopt", "clone", "fork",
        "execve", "exit", "wait4", "kill", "uname",
        "fcntl", "flock", "fsync", "fdatasync", "truncate",
        "ftruncate", "getcwd", "chdir", "rename", "mkdir",
        "rmdir", "unlink", "symlink", "readlink", "chmod",
        "chown", "umask", "gettimeofday", "getrlimit",
        "getrusage", "sysinfo", "times", "getuid", "getgid",
        "setuid", "setgid", "geteuid", "getegid",
        "setpgid", "getppid", "getpgrp", "setsid",
        "getgroups", "arch_prctl", "futex", "set_tid_address",
        "restart_syscall", "exit_group", "epoll_wait",
        "epoll_ctl", "tgkill", "openat", "newfstatat",
        "set_robust_list", "get_robust_list", "epoll_create1",
        "accept4", "eventfd2", "pipe2", "prlimit64"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
Bash
# Apply a Seccomp profile
docker run --security-opt seccomp=seccomp-profile.json my-app

# Run with the default Docker profile (recommended)
docker run --security-opt seccomp=default my-app

# Running without a profile allows dangerous syscalls
# Never use --security-opt seccomp=unconfined

4. Applying an AppArmor Profile

Bash
# Create an AppArmor profile
sudo tee /etc/apparmor.d/docker-app << 'EOF'
#include <tunables/global>

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

  network inet tcp,
  network inet udp,

  file,
  deny /proc/sys/kernel/** w,
  deny /sys/firmware/** rwklx,

  # Block sensitive paths
  deny /etc/passwd w,
  deny /etc/shadow rwklx,
  deny /root/** rwklx,
}
EOF

sudo apparmor_parser -r /etc/apparmor.d/docker-app

# Apply the AppArmor profile to a Docker container
docker run --security-opt apparmor=docker-app my-app

5. Rootless Docker

In rootless mode, the Docker daemon itself runs with ordinary user privileges.

Bash
# Install rootless Docker
curl -fsSL https://get.docker.com/rootless | sh

# Set environment variables (add to ~/.bashrc)
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

# Start automatically on boot
systemctl --user enable docker
sudo loginctl enable-linger $USER

# Verify
docker info | grep "rootless"

6. Docker Content Trust — Image Signature Verification

Bash
# Enable Content Trust (only signed images can be pulled/pushed)
export DOCKER_CONTENT_TRUST=1

# Sign the image, then push
docker trust key generate my-signing-key
docker trust signer add --key my-signing-key.pub myuser my-registry.com/myapp
docker push my-registry.com/myapp:1.0   # signed automatically

# Inspect signature information
docker trust inspect --pretty my-registry.com/myapp:1.0

# Unsigned images are rejected on pull
docker pull my-registry.com/myapp:untagged   # Error: no trust data

7. Runtime Anomaly Detection — Falco

Bash
# Install Falco
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" | sudo tee /etc/apt/sources.list.d/falcosecurity.list
sudo apt update && sudo apt install -y falco
YAML
# /etc/falco/falco_rules.local.yaml — custom detection rules
- rule: Container Shell Spawned
  desc: A shell was spawned inside a running container (potential compromise signal)
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh, ksh) and
    not proc.pname in (containerd-shim, runc)
  output: >
    Shell spawned in container (user=%user.name cmd=%proc.cmdline
    container=%container.name image=%container.image.repository)
  priority: WARNING
  tags: [container, shell, mitre_execution]

- rule: Write to /etc in Container
  desc: Attempt to write to the /etc directory inside a container
  condition: >
    open_write and container and
    fd.name startswith /etc
  output: >
    Write to /etc in container (file=%fd.name container=%container.name)
  priority: ERROR
Bash
sudo systemctl enable --now falco

# Watch alerts in real time
sudo journalctl -u falco -f
#Trivy#Seccomp#AppArmor#rootless#Docker#Security
Editorial note

AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us

Topic-related official docs (auto-linked · not per-claim sources)Docker documentation ↗OWASP documentation ↗

Questions & answers

Ask a question about this guide. We'll review and reply.