Docker est devenu l outil standard pour containeriser les applications Python, mais un Dockerfile mal configure peut introduire des vulnerabilites critiques : execution en root, dependances non epinglees, secrets exposes dans les layers, images de plusieurs gigaoctets truffees de paquets inutiles. En 2026, avec la multiplication des attaques supply chain ciblant les ecosystemes Python (PyPI, pip), securiser votre environnement Docker n est plus optionnel — c est une necessite pour tout projet professionnel.
Ce guide vous accompagne a travers 7 etapes concretes pour construire un environnement de developpement Python avec Docker qui respecte les bonnes pratiques de securite : du choix de l image de base au scan automatise des vulnerabilites en passant par la gestion des secrets et l integration CI/CD. Chaque etape inclut des fichiers de configuration complets et des commandes directement utilisables.
Que vous travailliez sur une API FastAPI, une application Django, un pipeline de data science ou un service de machine learning, ces pratiques s appliquent a tous les projets Python containerises. L objectif : une image de production legere, sans vulnerabilites connues, avec des dependances verifiees et un utilisateur non-root — le tout automatise dans votre pipeline CI/CD.
Etape 1 : Choisir la bonne image de base Python
Le choix de l image de base est la premiere decision de securite que vous prenez pour votre conteneur Python. Trois options principales s offrent a vous : python:3.12 (image complete basee sur Debian Bookworm), python:3.12-slim (version allegee de Bookworm), et python:3.12-alpine (basee sur Alpine Linux avec musl libc). Chacune a des implications directes sur la taille, la securite et la compatibilite de votre image.
| Image | Taille | Packages systeme | Compatibilite | CVE connues |
|---|---|---|---|---|
python:3.12 | ~1.0 Go | gcc, make, curl, git... | Excellente | 200+ (surface large) |
python:3.12-slim | ~150 Mo | Minimum viable | Excellente | 30-50 (surface reduite) |
python:3.12-alpine | ~55 Mo | Presque rien | Problematique | 10-20 (mais faux sentiment) |
Notre recommandation : python:3.12-slim. Cette image offre le meilleur compromis pour la quasi-totalite des projets Python. Elle est basee sur Debian Bookworm mais ne contient que les paquets essentiels au runtime Python : glibc, OpenSSL, ca-certificates. Elle elimine les outils de build (gcc, make, dpkg-dev), les pages de documentation, et les fichiers de localisation inutiles. Resultat : une surface d attaque reduite de 85% par rapport a l image complete.
Pourquoi eviter Alpine pour Python ? Malgre sa taille seduisante de 55 Mo, Alpine utilise musl libc au lieu de glibc. Cela provoque des incompatibilites avec de nombreuses extensions C compilees : numpy, pandas, scipy, psycopg2, cryptography, Pillow. Ces packages doivent etre recompiles depuis les sources sur Alpine, ce qui allonge le build de 5 a 20 minutes et produit parfois des binaires avec des comportements subtils differents (precision numerique, gestion des locales). En 2026, la communaute Python recommande clairement slim pour la production.
# Verifier la taille de chaque image
docker pull python:3.12
docker pull python:3.12-slim
docker pull python:3.12-alpine
docker images python --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# REPOSITORY TAG SIZE
# python 3.12 1.02GB
# python 3.12-slim 155MB
# python 3.12-alpine 56.2MB
# Scanner les CVE de chaque image avec Trivy
trivy image python:3.12 --severity HIGH,CRITICAL --quiet
# python:3.12 (debian 12.8)
# Total: 47 (HIGH: 38, CRITICAL: 9)
trivy image python:3.12-slim --severity HIGH,CRITICAL --quiet
# python:3.12-slim (debian 12.8)
# Total: 8 (HIGH: 7, CRITICAL: 1)
trivy image python:3.12-alpine --severity HIGH,CRITICAL --quiet
# python:3.12-alpine (alpine 3.20.3)
# Total: 0💡 Notre avis d'expert
Ne vous laissez pas seduire par les “0 CVE” d Alpine. Ce chiffre reflete simplement le fait que les bases de vulnerabilites (NVD, OSV) ont une couverture moins complete pour Alpine/musl que pour Debian/glibc. Le nombre reel de vulnerabilites non repertoriees dans musl est difficile a estimer. Avec slim, vous avez une base solide, bien auditee par la communaute Debian, et compatible avec 100% de l ecosysteme Python.
Etape 2 : Configurer un Dockerfile multi-stage
Le build multi-stage est la technique fondamentale pour produire des images Docker Python legeres et securisees. Le principe : utiliser une premiere image (le “builder”) pour installer les dependances et compiler les extensions C, puis copier uniquement les fichiers necessaires dans une seconde image (la “production”) qui ne contient que le runtime. Les outils de compilation (gcc, make, headers de developpement) ne finissent jamais dans l image de production.
Voici un Dockerfile multi-stage complet pour un projet Python typique (API FastAPI avec PostgreSQL) :
# ============================================
# Stage 1 : Builder — compilation des dependances
# ============================================
FROM python:3.12-slim AS builder
# Installer les outils de compilation necessaires
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
libffi-dev \
&& rm -rf /var/lib/apt/lists/*
# Creer un repertoire pour les packages installes
WORKDIR /build
# Copier les fichiers de dependances en premier (cache Docker)
COPY requirements.txt .
# Installer les dependances dans un prefix isole
RUN pip install --no-cache-dir --prefix=/install \
--require-hashes -r requirements.txt
# ============================================
# Stage 2 : Production — image finale minimale
# ============================================
FROM python:3.12-slim AS production
# Installer uniquement les librairies runtime (pas les -dev)
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copier les packages Python depuis le builder
COPY --from=builder /install /usr/local
# Creer un utilisateur non-root
RUN useradd --create-home --shell /bin/bash appuser
# Copier le code source
WORKDIR /app
COPY --chown=appuser:appuser ./app .
# Basculer vers l utilisateur non-root
USER appuser
# Healthcheck
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Port et commande de demarrage
EXPOSE 8000
CMD ["python", "-m", "gunicorn", "main:app", \
"--bind", "0.0.0.0:8000", \
"--workers", "4", \
"--worker-class", "uvicorn.workers.UvicornWorker"]Les points cles de ce Dockerfile multi-stage :
- Cache Docker optimise : les fichiers
requirements.txtsont copies avant le code source. Tant que les dependances ne changent pas, Docker reutilise le layer en cache, meme si le code applicatif est modifie. Cela divise le temps de build par 5 a 10 lors du developpement iteratif. - Prefix isole :
--prefix=/installinstalle les packages dans un repertoire dedie, ce qui permet unCOPY --from=builderpropre sans emporter le cache pip ni les outils de build. - rm -rf /var/lib/apt/lists/* : supprime le cache apt pour reduire la taille du layer. Chaque megaoctet compte en production.
- --no-install-recommends : n installe que les dependances strictement requises, pas les paquets recommandes par Debian.
Etape 3 : Gerer les dependances avec pip-tools et verification par hash
L une des vulnerabilites les plus exploitees dans l ecosysteme Python est la supply chain attack via des dependances non epinglees. Un requirements.txt avec requests>=2.28 peut installer une version differente demain si un attaquant publie un package malveillant. La solution : pip-tools avec verification par hash.
pip-tools se compose de deux commandes : pip-compile (qui resout et epingle les dependances) et pip-sync (qui installe exactement ce qui est dans le fichier). Voici le workflow complet :
# Installer pip-tools
pip install pip-tools
# Creer requirements.in avec vos dependances directes
cat > requirements.in << 'EOF'
fastapi==0.115.6
uvicorn[standard]==0.34.0
gunicorn==23.0.0
psycopg2-binary==2.9.10
sqlalchemy==2.0.36
pydantic==2.10.3
pydantic-settings==2.7.1
httpx==0.28.1
python-multipart==0.0.19
alembic==1.14.1
redis==5.2.1
celery==5.4.0
EOF
# Compiler avec hashes pour verification d integrite
pip-compile requirements.in \
--generate-hashes \
--output-file requirements.txt \
--strip-extras \
--no-annotate
# Le fichier genere contient TOUTES les dependances transitives
# avec leurs versions exactes et hashes SHA-256
head -20 requirements.txtLe fichier requirements.txt genere ressemble a ceci :
# Genere automatiquement par pip-compile — NE PAS MODIFIER
anyio==4.7.0 \
--hash=sha256:ea60c2f4e391a5ced8b1d2e12e74... \
--hash=sha256:2f834749c602c5343...
certifi==2024.12.14 \
--hash=sha256:1275f7a45be9464e... \
--hash=sha256:b650d30f370c275...
fastapi==0.115.6 \
--hash=sha256:3e5f3ae... \
--hash=sha256:27e45...
# ... toutes les dependances transitives epinglees avec hashesEnsuite dans votre Dockerfile, utilisez le flag --require-hashes pour forcer la verification :
# Dans le stage builder du Dockerfile
COPY requirements.txt .
RUN pip install --no-cache-dir --require-hashes -r requirements.txt
# Si un hash ne correspond pas, pip REFUSE l installation :
# ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM requirements.txt
# requests==2.32.3 from https://files.pythonhosted.org/...
# Expected sha256 abc123...
# Got sha256 def456...💡 Notre avis d'expert
Le flag --require-hashes est votre derniere ligne de defense contre les attaques supply chain. Meme si un attaquant compromet PyPI et remplace un package par une version malveillante, le hash SHA-256 ne correspondra pas et pip refusera l installation. Combinez cela avec un audit regulier via pip-audit (de la Python Packaging Authority) pour detecter les dependances avec des CVE connues.
Pour mettre a jour une dependance de maniere controlee :
# Mettre a jour une dependance specifique
pip-compile requirements.in --generate-hashes --upgrade-package fastapi
# Mettre a jour TOUTES les dependances
pip-compile requirements.in --generate-hashes --upgrade
# Verifier les dependances avec des CVE connues
pip install pip-audit
pip-audit -r requirements.txt
# Found 0 known vulnerabilities in 47 packagesEtape 4 : Creer un utilisateur non-root dans le conteneur
Par defaut, les processus dans un conteneur Docker s executent en tant que root (UID 0). C est un risque majeur : si un attaquant parvient a exploiter une vulnerabilite dans votre application Python (injection de commande, SSRF, deserialisation non securisee), il obtient un acces root dans le conteneur. Avec certaines configurations Docker (privileged mode, mauvais montages de volumes), cet acces root peut mener a une evasion du conteneur et un acces a l hote.
La solution est simple mais souvent negligee : creer un utilisateur dedie et executer l application sous cet utilisateur.
# Dans votre Dockerfile (stage production)
# Creer un groupe et un utilisateur systeme dedie
RUN groupadd --gid 1001 appgroup && \
useradd --uid 1001 --gid appgroup --shell /bin/bash \
--create-home appuser
# Creer les repertoires necessaires avec les bonnes permissions
RUN mkdir -p /app/logs /app/data && \
chown -R appuser:appgroup /app
# Copier le code source avec les bonnes permissions
COPY --chown=appuser:appgroup ./app /app/
# IMPORTANT : basculer vers l utilisateur non-root
# Toutes les instructions suivantes s executent en tant que appuser
USER appuser
# Verifier (debug uniquement)
# RUN whoami # appuser
# RUN id # uid=1001(appuser) gid=1001(appgroup)Les erreurs courantes a eviter avec l utilisateur non-root :
- Permissions sur les fichiers : utilisez
COPY --chown=appuser:appgrouppour chaque instruction COPY, sinon les fichiers appartiennent a root et appuser ne peut pas les lire. - Ports privilegies : un utilisateur non-root ne peut pas binder sur les ports inferieurs a 1024. Utilisez un port superieur (8000, 8080) et mappez-le avec
-p 80:8000au niveau de Docker ou de votre reverse proxy. - Repertoires temporaires : si votre application ecrit des fichiers temporaires, assurez-vous que le repertoire cible appartient a appuser. Creez les repertoires avant l instruction
USER. - Pip cache : utilisez toujours
--no-cache-dirdans le stage builder pour eviter les problemes de permissions et reduire la taille de l image.
# Verifier que le conteneur s execute bien en non-root
docker run --rm mon-app:latest whoami
# appuser
docker run --rm mon-app:latest id
# uid=1001(appuser) gid=1001(appgroup) groups=1001(appgroup)
# Tenter d ecrire dans un repertoire systeme (doit echouer)
docker run --rm mon-app:latest touch /etc/test
# touch: cannot touch '/etc/test': Permission denied💡 Notre avis d'expert
Allez plus loin avec la directive --read-only au runtime : docker run --read-only --tmpfs /tmp mon-app:latest. Cela monte le filesystem du conteneur en lecture seule, empechant tout ecriture sauf dans /tmp. Combine avec un utilisateur non-root, cela rend l exploitation d une vulnerabilite applicative beaucoup plus difficile — l attaquant ne peut ni ecrire de fichiers persistants ni escalader ses privileges.
Etape 5 : Configurer le reseau et les secrets Docker
Deux erreurs classiques dans les environnements Docker de developpement : tous les services sur le meme reseau par defaut, et les credentials en dur dans les variables d environnement (visibles avec docker inspect). Voici comment isoler proprement le reseau et gerer les secrets.
Commencez par creer des reseaux Docker dedies pour isoler les communications entre services :
# docker-compose.yml — Reseaux isoles et secrets
version: "3.9"
services:
api:
build:
context: .
dockerfile: Dockerfile
target: production
ports:
- "8000:8000"
networks:
- frontend
- backend
secrets:
- db_password
- redis_password
- jwt_secret
environment:
- DATABASE_HOST=postgres
- DATABASE_PORT=5432
- DATABASE_NAME=myapp
- DATABASE_USER=myapp
# Le mot de passe est lu depuis /run/secrets/db_password
- REDIS_HOST=redis
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: postgres:16-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- backend # Accessible UNIQUEMENT depuis le backend
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myapp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: >
redis-server
--requirepass_file /run/secrets/redis_password
--maxmemory 256mb
--maxmemory-policy allkeys-lru
networks:
- backend # Pas accessible depuis l exterieur
secrets:
- redis_password
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # Pas d acces internet depuis ce reseau
volumes:
postgres_data:
secrets:
db_password:
file: ./secrets/db_password.txt
redis_password:
file: ./secrets/redis_password.txt
jwt_secret:
file: ./secrets/jwt_secret.txtPour lire les secrets dans votre code Python, creez un helper qui lit depuis /run/secrets/ :
# config.py — Lecture securisee des secrets Docker
import os
from pathlib import Path
def read_secret(name: str, default: str = "") -> str:
"""Lit un secret Docker depuis /run/secrets/ ou une variable d env."""
secret_path = Path(f"/run/secrets/{name}")
if secret_path.exists():
return secret_path.read_text().strip()
# Fallback vers variable d environnement (dev local sans Docker)
return os.getenv(name.upper(), default)
# Utilisation
DATABASE_PASSWORD = read_secret("db_password")
REDIS_PASSWORD = read_secret("redis_password")
JWT_SECRET = read_secret("jwt_secret")
DATABASE_URL = (
f"postgresql://{os.getenv('DATABASE_USER', 'myapp')}"
f":{DATABASE_PASSWORD}"
f"@{os.getenv('DATABASE_HOST', 'localhost')}"
f":{os.getenv('DATABASE_PORT', '5432')}"
f"/{os.getenv('DATABASE_NAME', 'myapp')}"
)Creez les fichiers de secrets pour le developpement local (et ajoutez le dossier secrets/ a votre .gitignore) :
# Creer le dossier secrets
mkdir -p secrets
# Generer des mots de passe forts
openssl rand -base64 32 > secrets/db_password.txt
openssl rand -base64 32 > secrets/redis_password.txt
openssl rand -base64 64 > secrets/jwt_secret.txt
# IMPORTANT : ne JAMAIS committer ces fichiers
echo "secrets/" >> .gitignoreL utilisation de internal: true sur le reseau backend est cruciale : elle empeche les conteneurs connectes a ce reseau d acceder a Internet. Meme si un attaquant compromet votre API, il ne pourra pas exfiltrer des donnees directement depuis PostgreSQL ou Redis vers un serveur externe. Seuls les conteneurs explicitement connectes au reseau backend peuvent communiquer entre eux.
Un projet de developpement open source ?
D-Open vous accompagne dans la conception, le developpement et le deploiement de vos projets web et IA.
Recevez votre devis en 24hEtape 6 : Scanner les vulnerabilites avec Trivy et Grype
Construire une image securisee ne suffit pas — vous devez verifier qu elle ne contient pas de vulnerabilites connues. Deux outils open source dominent le marche du scan de conteneurs en 2026 : Trivy (de Aqua Security) et Grype (de Anchore). Les deux analysent les packages systeme (Debian, Alpine) et les packages applicatifs (pip, npm, go modules) a la recherche de CVE repertoriees dans les bases NVD, OSV et des bases proprietaires.
# === TRIVY ===
# Installer Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Scanner une image Docker
trivy image mon-app:latest
# Scanner uniquement les vulnerabilites HIGH et CRITICAL
trivy image --severity HIGH,CRITICAL mon-app:latest
# Sortie typique :
# mon-app:latest (debian 12.8)
# =============================
# Total: 3 (HIGH: 2, CRITICAL: 1)
#
# ┌───────────────┬────────────────┬──────────┬───────────┬─────────────────┐
# │ Library │ Vulnerability │ Severity │ Installed │ Fixed Version │
# ├───────────────┼────────────────┼──────────┼───────────┼─────────────────┤
# │ libssl3 │ CVE-2024-9143 │ CRITICAL │ 3.0.13-1 │ 3.0.14-1 │
# │ zlib1g │ CVE-2024-8901 │ HIGH │ 1:1.2.13 │ 1:1.2.13.1 │
# │ python3.12 │ CVE-2024-12345 │ HIGH │ 3.12.4 │ 3.12.5 │
# └───────────────┴────────────────┴──────────┴───────────┴─────────────────┘
# Scanner avec code de sortie non-zero si vulnerabilites critiques
# (utile pour CI/CD)
trivy image --severity CRITICAL --exit-code 1 mon-app:latest
# Scanner le Dockerfile lui-meme (misconfigurations)
trivy config --severity HIGH,CRITICAL ./Dockerfile
# === GRYPE ===
# Installer Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Scanner une image
grype mon-app:latest
# Filtrer par severite
grype mon-app:latest --only-fixed --fail-on critical
# Generer un rapport JSON pour traitement automatise
grype mon-app:latest -o json > scan-report.jsonTrivy peut egalement scanner votre fichier requirements.txt directement, sans construire l image Docker :
# Scanner les dependances Python sans construire l image
trivy fs --scanners vuln .
# Scanner un repertoire contenant requirements.txt, poetry.lock, etc.
trivy fs --severity HIGH,CRITICAL --exit-code 1 .
# Sortie :
# requirements.txt (pip)
# =====================
# Total: 1 (HIGH: 1)
#
# ┌───────────────┬────────────────┬──────────┬───────────┬─────────────────┐
# │ Library │ Vulnerability │ Severity │ Installed │ Fixed Version │
# ├───────────────┼────────────────┼──────────┼───────────┼─────────────────┤
# │ cryptography │ CVE-2024-26130 │ HIGH │ 42.0.4 │ 42.0.5 │
# └───────────────┴────────────────┴──────────┴───────────┴─────────────────┘💡 Notre avis d'expert
Ne vous contentez pas de scanner au moment du build. Planifiez des scans reguliers sur vos images deployees (par exemple via un cron Kubernetes ou un workflow GitHub Actions schedule). De nouvelles CVE sont publiees quotidiennement — une image propre aujourd hui peut avoir 5 vulnerabilites critiques la semaine prochaine. Trivy supporte le mode --server pour centraliser les scans et les rapports dans un dashboard.
Etape 7 : Automatiser avec docker-compose et CI/CD
La derniere etape reunit toutes les precedentes dans un workflow automatise. En developpement, docker-compose orchestre vos services localement. En CI/CD, GitHub Actions (ou GitLab CI) reproduit le meme environnement pour les tests, le scan de securite et le deploiement.
Voici un docker-compose.yml complet pour le developpement local avec hot reload :
# docker-compose.dev.yml — Developpement local avec hot reload
version: "3.9"
services:
api:
build:
context: .
dockerfile: Dockerfile
target: production
ports:
- "8000:8000"
volumes:
# Hot reload : le code local est monte dans le conteneur
- ./app:/app:ro
environment:
- PYTHONDONTWRITEBYTECODE=1
- PYTHONUNBUFFERED=1
- DATABASE_HOST=postgres
- DATABASE_PORT=5432
- DATABASE_NAME=myapp_dev
- DATABASE_USER=myapp
secrets:
- db_password
networks:
- frontend
- backend
depends_on:
postgres:
condition: service_healthy
command: >
python -m uvicorn main:app
--host 0.0.0.0 --port 8000
--reload --reload-dir /app
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 15s
timeout: 5s
retries: 3
start_period: 10s
postgres:
image: postgres:16-alpine
volumes:
- postgres_dev_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
networks:
- backend
environment:
POSTGRES_DB: myapp_dev
POSTGRES_USER: myapp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myapp -d myapp_dev"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
networks:
frontend:
backend:
internal: true
volumes:
postgres_dev_data:
secrets:
db_password:
file: ./secrets/db_password.txtVoici le workflow GitHub Actions complet pour automatiser le build, le scan et les tests :
# .github/workflows/docker-secure.yml
name: Build & Scan Docker Image
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build-scan-test:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # Pour upload SARIF
steps:
- uses: actions/checkout@v4
# Build l image multi-stage
- name: Build Docker image
run: |
docker build \
--target production \
--tag mon-app:${{ github.sha }} \
--tag mon-app:latest \
.
# Scanner avec Trivy
- name: Scan avec Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: mon-app:latest
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: 1
# Upload les resultats dans GitHub Security
- name: Upload scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
# Scanner les dependances Python
- name: Audit dependances Python
run: |
pip install pip-audit
pip-audit -r requirements.txt --fix --dry-run
# Tests dans le conteneur
- name: Run tests
run: |
docker run --rm \
-e DATABASE_URL=sqlite:///test.db \
mon-app:latest \
python -m pytest tests/ -v --tb=short
# Verifier que l utilisateur est non-root
- name: Verify non-root user
run: |
USER=$(docker run --rm mon-app:latest whoami)
if [ "$USER" = "root" ]; then
echo "ERREUR: Le conteneur s execute en root !"
exit 1
fi
echo "OK: Le conteneur s execute en tant que $USER"
# Push vers le registry (uniquement sur main)
- name: Push to registry
if: github.ref == 'refs/heads/main'
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker tag mon-app:latest ghcr.io/${{ github.repository }}:latest
docker tag mon-app:${{ github.sha }} ghcr.io/${{ github.repository }}:${{ github.sha }}
docker push ghcr.io/${{ github.repository }}:latest
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}Les elements essentiels de ce pipeline :
- Build unique, scan avant deploy : l image est construite une seule fois, scannee par Trivy, et uniquement poussee vers le registry si le scan est propre. Un seul CVE CRITICAL bloque le pipeline.
- SARIF upload : les resultats du scan sont uploades dans l onglet Security de GitHub, offrant un historique consultable et des alertes automatiques.
- Verification non-root : le pipeline verifie explicitement que le conteneur ne s execute pas en root. C est un garde-fou contre les regressions accidentelles.
- pip-audit : audit complementaire des dependances Python contre les bases de vulnerabilites connues (PyPI Advisory Database, OSV).
- Tags immutables : chaque image est taguee avec le SHA du commit Git en plus du tag
latest, permettant un rollback precis en cas de probleme.
Pour le developpement au quotidien, quelques commandes utiles :
# Demarrer l environnement de dev
docker compose -f docker-compose.dev.yml up -d
# Voir les logs en temps reel
docker compose -f docker-compose.dev.yml logs -f api
# Executer les tests dans le conteneur
docker compose -f docker-compose.dev.yml exec api pytest tests/ -v
# Rebuild apres modification du Dockerfile ou des dependances
docker compose -f docker-compose.dev.yml up -d --build api
# Scanner l image locale
trivy image mon-app:latest --severity HIGH,CRITICAL
# Arreter et nettoyer
docker compose -f docker-compose.dev.yml down -v💡 Notre avis d'expert
Ajoutez un scan planifie (cron) dans votre CI pour re-scanner vos images deployees chaque semaine. Utilisez schedule: - cron: '0 8 * * 1' dans GitHub Actions pour un scan chaque lundi matin. Les nouvelles CVE sont publiees quotidiennement — une image propre au moment du build peut devenir vulnerable en quelques jours. C est la difference entre une securite ponctuelle et une securite continue.
Questions frequentes
Quelle image de base Python choisir pour Docker en production ?▼
Pour la majorite des projets Python, python:3.12-slim est le meilleur choix. Elle pese environ 150 Mo (contre 1 Go pour l image complete) tout en conservant la compatibilite avec glibc, ce qui garantit le fonctionnement de toutes les extensions C (numpy, pandas, psycopg2, cryptography). Evitez Alpine pour Python en production : musl libc provoque des incompatibilites subtiles et allonge considerablement les temps de build. Utilisez l image complete (python:3.12) uniquement dans le stage builder si vous devez compiler des extensions C complexes.
Pourquoi utiliser un build multi-stage pour Python avec Docker ?▼
Le build multi-stage separe la phase de compilation (installation de gcc, headers de developpement, compilation des wheels C) de l image de production finale. L image de production ne contient que le runtime Python et les packages installes, sans les outils de build. Les avantages sont triples : reduction de la taille de l image de 40 a 60%, reduction de la surface d attaque en eliminant les compilateurs et outils de developpement, et meilleur cache Docker car les layers de build sont independants du code applicatif.
Comment scanner les vulnerabilites d une image Docker Python ?▼
Utilisez Trivy (trivy image mon-app:latest) ou Grype (grype mon-app:latest). Les deux outils analysent les packages systeme Debian et les packages Python installes. Pour bloquer automatiquement un build avec des vulnerabilites critiques, utilisez trivy image --severity CRITICAL --exit-code 1 dans votre pipeline CI/CD. Completez avec pip-audit pour un audit specifique des dependances Python. Planifiez des scans reguliers sur les images deployees car de nouvelles CVE sont publiees chaque jour.
Est-il possible d utiliser Docker secrets avec docker-compose en developpement ?▼
Oui. Declarez vos secrets dans la section secrets de votre docker-compose.yml avec file: ./secrets/db_password.txt pour chaque secret. Dans votre code Python, lisez le contenu depuis /run/secrets/db_password au lieu d une variable d environnement. Cette approche est plus securisee que les variables d environnement (qui apparaissent dans docker inspect) et vous prepare au deploiement en Docker Swarm ou Kubernetes. N oubliez pas d ajouter le dossier secrets/ a votre .gitignore.
