---
title: "Docker — Hardening & Sécurité"
domain: devops
subdomain: docker
type: checklist
tags: [docker, hardening, securite, conteneurs, devsecops]
difficulty: intermediate
status: stable
updated: "Sat May 10 2025 00:00:00 GMT+0000 (Coordinated Universal Time)"
---
## Audit de sécurité Docker

```bash vars=CONTAINER
# Audit avec Docker Bench Security
docker run --rm --net host --pid host --userns host --cap-add audit_control \
  -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
  -v /etc:/etc:ro \
  -v /usr/bin/containerd:/usr/bin/containerd:ro \
  -v /usr/bin/runc:/usr/bin/runc:ro \
  -v /usr/lib/systemd:/usr/lib/systemd:ro \
  -v /var/lib:/var/lib:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  docker/docker-bench-security

# Audit image avec Trivy
trivy image {{IMAGE}}

# Audit image avec Grype
grype {{IMAGE}}

# Inspect container
docker inspect {{CONTAINER}}
docker stats {{CONTAINER}} --no-stream
```

## Dockerfile sécurisé

```dockerfile
# ✅ Bon exemple — image minimale + non-root
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM node:20-alpine AS runner

# Créer user non-root
RUN addgroup -g 1001 -S appgroup && \
    adduser -S -u 1001 -G appgroup appuser

WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --chown=appuser:appgroup . .

# Basculer sur user non-root
USER appuser

# Ne pas exposer port root (<1024)
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"

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

## docker-compose.yml sécurisé

```yaml vars=SERVICE
version: '3.8'
services:
  {{SERVICE}}:
    image: myapp:latest
    # Limites ressources
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
    # Sécurité
    security_opt:
      - no-new-privileges:true
    read_only: true
    # Tmpfs pour /tmp
    tmpfs:
      - /tmp:noexec,nosuid,size=64m
    # Capabilities minimales
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Seulement si nécessaire
    # User non-root
    user: "1001:1001"
    # Variables env depuis fichier
    env_file:
      - .env
    # Réseau isolé
    networks:
      - internal
    # Pas de port host sauf nécessaire
    expose:
      - "3000"

networks:
  internal:
    driver: bridge
    internal: true  # Pas d'accès internet sauf via proxy
```

<Checklist
  title="Checklist hardening Docker"
  storageKey="docker-hardening"
  items={[
    { id: "nonroot", label: "Container tourne en non-root (USER != 0)", critical: true },
    { id: "readonly", label: "Filesystem read-only (read_only: true)" },
    { id: "caps", label: "Capabilities réduites (cap_drop: ALL + whitelist)" },
    { id: "resources", label: "Limites CPU/RAM définies" },
    { id: "noprivesc", label: "no-new-privileges activé", critical: true },
    { id: "network", label: "Réseau isolé (pas de --network host)" },
    { id: "secrets", label: "Secrets via env_file ou Docker secrets (pas en dur)" },
    { id: "image", label: "Image de base minimale (alpine, distroless)" },
    { id: "scan", label: "Scan image avec Trivy avant deploy" },
    { id: "socket", label: "Socket Docker pas monté dans les containers" },
    { id: "bench", label: "Docker Bench Security passé" },
    { id: "logs", label: "Logs centralisés (pas de log sensible)" }
  ]}
/>

## Commandes d'audit

```bash vars=CONTAINER
# Processus dans le container
docker exec {{CONTAINER}} ps aux

# User courant
docker exec {{CONTAINER}} whoami
docker exec {{CONTAINER}} id

# Fichiers SUID/SGID dans l'image
docker run --rm --entrypoint sh {{IMAGE}} -c "find / -perm /6000 -type f 2>/dev/null"

# Réseaux Docker
docker network ls
docker network inspect bridge

# Volumes montés
docker inspect {{CONTAINER}} | jq '.[].HostConfig.Binds'

# Variables d'environnement (attention aux secrets !)
docker inspect {{CONTAINER}} | jq '.[].Config.Env'
```
