D-OPEN

Comment configurer un environnement de developpement Python securise avec Docker en 7 etapes

William

William

DevOps engineer & contributrice open source · 29 juin 2026 · 14 min de lecture

TL;DR

  • Utilisez python:3.12-slim comme image de base — le meilleur compromis taille/securite/compatibilite face a alpine et bookworm.
  • Configurez un build multi-stage avec pip-tools pour separer compilation et runtime, reduire la surface d attaque et epingler chaque dependance avec verification par hash.
  • Executez vos conteneurs en utilisateur non-root, isolez les secrets avec Docker secrets et scannez chaque image avec Trivy avant deploiement.
  • Automatisez le tout avec docker-compose en dev et GitHub Actions en CI/CD pour un pipeline reproductible et securise de bout en bout.

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.

COUCHES DE SECURITE — ENVIRONNEMENT PYTHON DOCKER7. CI/CDGitHub Actions + scan automatise + build reproductible6. Scan vulnerabilitesTrivy + Grype sur chaque build5. Reseau & SecretsDocker networks isoles + secrets chiffres4. Utilisateur non-rootuseradd + USER + permissions minimales3. Dependancespip-tools + hash + contraintes epinglees2. Multi-stage buildBuilder separe du runtime1. Image de basepython:3.12-slim (Debian)

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.

ImageTaillePackages systemeCompatibiliteCVE connues
python:3.12~1.0 Gogcc, make, curl, git...Excellente200+ (surface large)
python:3.12-slim~150 MoMinimum viableExcellente30-50 (surface reduite)
python:3.12-alpine~55 MoPresque rienProblematique10-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.

DOCKER MULTI-STAGE BUILD — SEPARATION BUILDER / PRODUCTIONSTAGE 1 : BUILDERpython:3.12-slim + outils de compilationapt-get install gcc libpq-devpip install --prefix=/install -r req.txtCompilation wheels C (psycopg2, etc.)NE PASSE PAS EN PRODUCTIONgcc, make, dpkg-dev, headers, pip cacheCOPIE VERS PRODUCTION/install/lib/python3.12/site-packages/STAGE 2 : PRODUCTIONpython:3.12-slim (propre, minimal)COPY --from=builder /install /usr/localCOPY ./app /appUSER appuser (non-root)CMD ["gunicorn", "app:create_app()"]IMAGE FINALE~180 Mo au lieu de ~1.2 GoPas de gcc, pas de pip, pas de cache

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.txt sont 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=/install installe les packages dans un repertoire dedie, ce qui permet un COPY --from=builder propre 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.txt

Le 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 hashes

Ensuite 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 packages

Etape 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:appgroup pour 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:8000 au 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-dir dans 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.txt

Pour 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/" >> .gitignore
ISOLATION RESEAU DOCKER — SEGMENTATION FRONTEND / BACKENDInternet / ClientRESEAU : frontend (bridge)API Python (FastAPI):8000 — USER appuserRESEAU : backend (internal: true — PAS d acces internet)PostgreSQL 16:5432 — secret: db_password🔒 Pas d acces direct depuis InternetRedis 7:6379 — secret: redis_password/run/secrets/

L 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 24h

Etape 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.json

Trivy 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.txt
WORKFLOW CI/CD SECURISE — DU CODE AU DEPLOIEMENT1. CODEgit pushDockerfile + app2. BUILDdocker buildmulti-stage + hashes3. SCANTrivy + Grype--exit-code 14. TESTpytest + mypydans le conteneur5. DEPLOYdocker pushregistry + tagCVE detecteeBuild bloqueChaque etape est automatisee dans GitHub Actions — un echec bloque le deploiement

Voici 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.

Besoin d'un developpeur pour votre projet open source ?

D-Open met a votre disposition des developpeurs experimentes pour vos projets web et IA.

Parlons de votre projet