1. Construire de bonnes images #
Petites, reproductibles et sûres.
Vue d'ensemble #
Dockerfile optimisé #
# syntax=docker/dockerfile:1
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download # couche de dépendances mise en cache
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/app"]# .dockerignore : réduire le contexte de build et éviter les fuites
.git
node_modules
*.log
.env
**/*.pemReproductibilité #
- Épinglez les versions :
python:3.12.7-slim, jamaislatest; idéalement le digest (image@sha256:...). - Un processus par conteneur, journaux sur
stdout/stderr. - Regroupez les
RUNliés et nettoyez dans la même couche (apt-get ... && rm -rf /var/lib/apt/lists/*). - Ordonnez du moins changeant (dépendances) au plus changeant (code) pour exploiter le cache.
docker buildx build --platform linux/amd64,linux/arm64 -t registre.exemple.fr/shop/api:1.4.2 --push .
docker image inspect nginx:1.27 --format '{{index .RepoDigests 0}}' # récupérer le digest
docker history --no-trunc registre.exemple.fr/shop/api:1.4.2 | head # tailles par couche
docker run --rm wagoodman/dive:latest registre.exemple.fr/shop/api:1.4.2 # explorer les couches2. Exécuter avec le moindre privilège #
Réduire la surface d'attaque.
Un conteneur durci #
docker run -d --name api \
--user 10001:10001 \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m \
--cap-drop ALL --security-opt no-new-privileges:true \
--memory 512m --memory-swap 512m --cpus 1.0 --pids-limit 200 \
--restart unless-stopped \
--health-cmd "wget -qO- http://localhost:8080/health || exit 1" --health-interval 30s \
-p 127.0.0.1:8080:8080 \
registre.exemple.fr/shop/api:1.4.2| Option | Effet |
|---|---|
--user | Ne jamais exécuter en root |
--read-only | Système de fichiers en lecture seule (+ tmpfs si besoin) |
--cap-drop ALL | Retire toutes les capabilities Linux |
no-new-privileges | Interdit l'élévation de privilèges |
--memory / --cpus | Évite qu'un conteneur monopolise l'hôte |
-p 127.0.0.1:... | N'expose qu'en local derrière un reverse proxy |
Erreur Courante : Ne montez jamais/var/run/docker.sockdans un conteneur applicatif et n'utilisez--privilegedqu'en dernier recours : cela équivaut à donner root sur l'hôte.
Mode rootless #
dockerd-rootless-setuptool.sh install # Docker sans privilèges root
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker info --format '{{.SecurityOptions}}' # doit contenir rootless3. Sécuriser la chaîne d'approvisionnement #
Secrets, scan et signature.
Secrets #
# ❌ À éviter : secret dans l'image
ENV DB_PASSWORD=secret
# ✅ Secrets de build (non conservés dans l'image)
docker build --secret id=npmrc,src=$HOME/.npmrc .
# Dockerfile : RUN --mount=type=secret,id=npmrc cp /run/secrets/npmrc .npmrc && npm ci
# ✅ Secrets à l'exécution (Compose / Swarm)
services:
api:
secrets: [db_password]
secrets:
db_password: { file: ./secrets/db_password.txt }Analyse et signature #
trivy image --exit-code 1 --severity CRITICAL registre.exemple.fr/shop/api:1.4.2 # échoue la CI
trivy config . # Dockerfile / manifestes
cosign sign --key cosign.key registre.exemple.fr/shop/api:1.4.2 # signer
cosign verify --key cosign.pub registre.exemple.fr/shop/api:1.4.2 # vérifier avant déploiement
hadolint Dockerfile # linter de Dockerfile4. Compose et exploitation #
Du poste de dev à la production.
Compose bien écrit #
services:
api:
image: registre.exemple.fr/shop/api:1.4.2
env_file: [.env]
depends_on: { db: { condition: service_healthy } }
deploy: { resources: { limits: { cpus: "1", memory: 512M } } }
logging: { driver: json-file, options: { max-size: "10m", max-file: "5" } } # rotation des logs
healthcheck: { test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"], interval: 30s, retries: 3 }
networks: [backend]
db:
image: postgres:16.4
volumes: [pgdata:/var/lib/postgresql/data]
networks: [backend]
networks: { backend: { internal: true } } # pas d'accès Internet direct pour la base
volumes: { pgdata: {} }docker compose config # valider et afficher la configuration finaleSupervision et nettoyage #
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
docker events --filter event=die --since 1h
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' api # OOM ?
docker system df -v
docker system prune -af --filter "until=168h" # supprimer ce qui est inutilisé depuis 7 joursBonne Pratique : Collectez les logs avec Alloy ou Fluent Bit plutôt qu'avec docker logs et exposez les métriques des conteneurs avec cAdvisor pour Prometheus.