Docker avec FastAPI
Ce chapitre vient après Tests avec FastAPI. Il montre comment emballer l’application dans un environnement reproductible avec Docker et Docker Compose, puis comment faire évoluer ce packaging vers une stack plus complète avec PostgreSQL, Redis et Celery.
Introduction
Jusqu’ici, on a construit une API FastAPI sérieuse :
- base CRUD,
- authentification,
- relations SQL,
- gestion d’erreurs,
- middleware,
- tests automatisés.
Mais tant que tout tourne seulement sur ta machine dans ton environnement Python local, tu gardes plusieurs fragilités :
- "chez moi ça marche" mais pas ailleurs,
- dépendances installées à la main,
- version de Python pas toujours identique,
- base locale qui ne ressemble pas forcément à un vrai environnement d’exécution,
- difficulté à lancer plusieurs services ensemble.
Docker sert précisément à réduire ce problème.
Ce que ce chapitre va accomplir
À la fin de ce chapitre, tu auras :
- un vrai
Dockerfilepour l’application FastAPI, - un
.dockerignorepropre, - un
docker-compose.ymlpour lancer :- l’API,
- PostgreSQL,
- une compréhension claire de :
- image,
- container,
- service,
- volume,
- réseau Docker,
- une variante plus proche des sources avec :
- Redis,
- Celery,
- une procédure de vérification avec :
docker build,docker compose up,curl,- logs.
Pourquoi Docker arrive maintenant
Docker devient vraiment utile après avoir stabilisé l’application.
Pourquoi ? Parce que sinon tu emballes trop tôt un projet encore en train de changer dans tous les sens.
Le bon ordre pédagogique est donc :
- construire l’API,
- la rendre plus réaliste,
- la tester,
- ensuite seulement la packager proprement.
C’est exactement la situation actuelle de notre wiki.
Sources d’inspiration réelles
Ce chapitre s’appuie principalement sur :
33-Docker/170-Dockerfile33-Docker/172-compose.yaml33-Docker/173-compose.yaml
Ces sources montrent :
- un
DockerfilePython simple, - une stack
api + db, - une version étendue avec
redisetcelery.
Choix pédagogique de ce chapitre
Les sources sont utiles, mais on ne va pas les recopier aveuglément.
Par exemple :
- la source utilise
python:3.13-alpine, - c’est compact,
- mais ce n’est pas toujours le choix le plus agréable pour débuter,
- surtout quand on finit par installer des dépendances Python avec extensions natives.
Pour ce wiki, on garde la logique de la source :
- image Python,
WORKDIR,- copie des dépendances,
- installation,
- copie du code,
- exécution de l’API,
- orchestration multi-services avec Compose.
Mais on adapte certains détails pour avoir une version :
- plus stable,
- plus explicite,
- plus pédagogique.
Architecture visée après ce chapitre
Après ce chapitre, le projet peut ressembler à ceci :
fastapi-base-api/
├── main.py
├── config.py
├── requirements.txt
├── .gitignore
├── .env
├── .env.example
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── core/
│ ├── __init__.py
│ ├── security.py
│ ├── exceptions.py
│ └── middleware.py
├── api/
│ ├── __init__.py
│ ├── router.py
│ ├── dependencies.py
│ ├── routers/
│ │ ├── __init__.py
│ │ ├── task.py
│ │ └── user.py
│ └── schemas/
│ ├── __init__.py
│ ├── task.py
│ └── user.py
├── database/
│ ├── __init__.py
│ ├── models.py
│ ├── session.py
│ └── redis.py # seulement si tu actives la variante Redis / logout robuste
├── services/
│ ├── __init__.py
│ ├── task.py
│ └── user.py
├── tests/
│ ├── __init__.py
│ ├── example.py
│ ├── conftest.py
│ ├── test_auth.py
│ └── test_task.py
└── worker/
└── tasks.py # seulement si tu ajoutes Celery plus tard
Pré-requis
Avant de continuer, vérifie que tu as :
- Docker installé,
- Docker Compose disponible via
docker compose, - un projet FastAPI déjà fonctionnel selon les chapitres précédents,
- un fichier
.envavec au minimum les variables PostgreSQL et JWT.
Exemple de .env côté projet :
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_DB=taskapi_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_real_password
JWT_SECRET=your_long_random_secret
JWT_ALGORITHM=HS256
Quand l’application tourne dans Docker Compose, on ne veut plus que POSTGRES_SERVER vaille localhost.
Dans le réseau Docker, le bon hostname sera db.
On va justement faire cette adaptation sans casser ton usage local hors Docker.
Repères utiles avant le code
Avant d’écrire les fichiers Docker, retiens ces cinq idées :
1. Une image n’est pas encore un container
Une image est un modèle prêt à être exécuté. Un container est une instance lancée à partir de cette image.
2. Docker Compose orchestre plusieurs services
Dans notre cas, on a au moins :
apidb
Et plus tard éventuellement :
rediscelery
3. Les services se parlent par leur nom
Dans Compose :
- l’API parlera à PostgreSQL via
db, - pas via
localhost.
4. Les volumes servent à conserver des données
Sans volume :
- tu supprimes le container,
- tu perds les données de la base.
Avec volume :
- tes données PostgreSQL survivent aux redémarrages.
5. depends_on ne remplace pas complètement l’attente de disponibilité
Un service peut être démarré sans être prêt. C’est pourquoi on va ajouter un healthcheck sur PostgreSQL.
Étape 1 — créer .dockerignore
Commence par éviter d’envoyer des fichiers inutiles dans le contexte de build.
Crée .dockerignore :
__pycache__/
*.pyc
*.pyo
*.pyd
.pytest_cache/
.mypy_cache/
.venv/
venv/
.env
.git/
.gitignore
postgres_data/
redis_data/
Pourquoi ce fichier est important ?
Il évite de copier dans l’image :
- les caches Python,
- l’environnement virtuel local,
- les secrets du
.env, - les dossiers de données locaux,
- des fichiers Git inutiles pour l’exécution.
Résultat :
- build plus rapide,
- image plus propre,
- moins de surprises.
Étape 2 — créer le Dockerfile
La source montre un Dockerfile très minimal. On garde l’idée, mais on choisit ici une base plus confortable : python:3.12-slim.
Crée Dockerfile :
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Explication ligne par ligne
FROM python:3.12-slim
On part d’une image Python officielle légère mais plus simple à vivre qu’Alpine pour un projet pédagogique.
PYTHONDONTWRITEBYTECODE=1
Évite la génération de fichiers .pyc inutiles dans le container.
PYTHONUNBUFFERED=1
Rend les logs Python plus lisibles en temps réel.
WORKDIR /app
Tous les chemins suivants sont relatifs à /app.
COPY requirements.txt .
On copie d’abord uniquement requirements.txt.
Pourquoi ? Parce que Docker peut réutiliser le cache si le code change mais pas les dépendances.
RUN pip install ...
On installe les dépendances du projet.
COPY . .
On copie le reste du code applicatif.
EXPOSE 8000
On documente le port prévu pour l’application.
CMD [...]
On lance Uvicorn sur 0.0.0.0.
Dans un container, 127.0.0.1 ne suffit pas.
Il faut écouter sur 0.0.0.0 pour que le port exposé soit réellement accessible depuis l’extérieur du container.
Étape 3 — tester le build seul
Avant d’orchestrer plusieurs services, tu peux déjà vérifier que l’image se construit.
docker build -t fastapi-base-api .
Si tout se passe bien, tu dois obtenir une image prête à lancer.
Tu peux vérifier :
docker images | grep fastapi-base-api
Étape 4 — comprendre le vrai besoin Compose
Le build seul est utile, mais il ne suffit pas.
Notre API dépend d’une base PostgreSQL. Donc si tu lances seulement :
docker run --rm -p 8000:8000 fastapi-base-api
l’application ne saura pas où se connecter si la base n’existe pas dans le même environnement.
C’est pour cela qu’on passe maintenant à Docker Compose.
Étape 5 — créer un docker-compose.yml propre pour api + db
Ici, on construit une version adaptée à notre projet.
Crée docker-compose.yml :
services:
api:
build: .
env_file:
- .env
environment:
POSTGRES_SERVER: db
POSTGRES_PORT: 5432
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: your_real_password
POSTGRES_DB: taskapi_db
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d taskapi_db"]
interval: 5s
timeout: 5s
retries: 10
volumes:
postgres_data:
Pourquoi cette version est adaptée à notre wiki
Côté api
On charge .env, puis on override seulement ce qui change dans Docker :
POSTGRES_SERVER: dbPOSTGRES_PORT: 5432
Résultat :
- en local hors Docker, ton
.envpeut rester aveclocalhost, - en Docker Compose, l’API parle au service
dbsans casser le reste de ton setup.
Côté db
On crée une base PostgreSQL standard.
Port 5433:5432
On expose PostgreSQL sur 5433 côté machine hôte pour éviter les collisions avec un éventuel PostgreSQL local déjà installé.
Volume postgres_data
Les données persistent même si tu redémarres les containers.
Healthcheck
L’API attend une base vraiment prête, pas juste un container lancé.
Étape 6 — garder les secrets cohérents
Dans l’exemple ci-dessus, la base reçoit :
POSTGRES_PASSWORD: your_real_password
POSTGRES_DB: taskapi_db
POSTGRES_USER: postgres
Il faut que cela corresponde à ce que ton application utilise réellement.
Donc ton .env local doit rester cohérent avec ces valeurs :
POSTGRES_SERVER=localhost
POSTGRES_PORT=5432
POSTGRES_DB=taskapi_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_real_password
JWT_SECRET=your_long_random_secret
JWT_ALGORITHM=HS256
Le plus important à comprendre ici n’est pas juste la valeur du mot de passe.
Le plus important, c’est que l’application et PostgreSQL doivent partager exactement les mêmes identifiants, et que seul le hostname change entre local (localhost) et Docker (db).
Étape 7 — lancer la stack
Lance ensuite :
docker compose up --build
Que va faire cette commande ?
- construire l’image de l’API,
- télécharger l’image PostgreSQL si nécessaire,
- créer le réseau Compose,
- démarrer la base,
- attendre son état healthy,
- démarrer l’API.
Étape 8 — vérifier que tout tourne
Dans un autre terminal :
docker compose ps
Tu dois voir au minimum :
apidb
Ensuite :
curl http://localhost:8000/docs
Ou, si tu veux voir les headers et le statut :
curl -i http://localhost:8000/docs
Tu peux aussi tester une route applicative réelle :
curl -i http://localhost:8000/task/
Selon ton implémentation exacte :
- soit tu obtiens une liste vide,
- soit tu obtiens une erreur métier attendue,
- soit tu dois d’abord t’authentifier pour certains endpoints.
L’important ici est surtout de vérifier que :
- l’API répond,
- la connexion DB fonctionne,
- les logs ne montrent pas d’erreur de connexion PostgreSQL.
Étape 9 — lire les logs correctement
Pour suivre les logs de l’API :
docker compose logs api -f
Pour PostgreSQL :
docker compose logs db -f
Ces logs sont précieux pour diagnostiquer :
- mauvais mot de passe,
- mauvais nom de base,
- problème de startup,
- import Python cassé,
- dépendance manquante.
Étape 10 — arrêter proprement
Pour arrêter :
docker compose down
Pour arrêter et supprimer aussi le volume de données :
docker compose down -v
docker compose down -v supprime le volume PostgreSQL.
Donc tu perds les données de la base de ce compose.
Utilise-le seulement si c’est ce que tu veux vraiment.
Étape 11 — variante plus proche des sources : ajouter Redis et Celery
La source 173-compose.yaml pousse la stack plus loin avec :
rediscelery
Dans notre wiki, cette variante est logique si tu veux :
- un logout robuste avec blacklist Redis,
- des tâches asynchrones plus tard,
- préparer un futur chapitre Celery.
Voici une version étendue du compose :
services:
api:
build: .
env_file:
- .env
environment:
POSTGRES_SERVER: db
POSTGRES_PORT: 5432
REDIS_HOST: redis
REDIS_PORT: 6379
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: your_real_password
POSTGRES_DB: taskapi_db
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d taskapi_db"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7
command: redis-server --appendonly yes
ports:
- "6379:6379"
volumes:
- redis_data:/data
celery:
build: .
env_file:
- .env
environment:
POSTGRES_SERVER: db
POSTGRES_PORT: 5432
REDIS_HOST: redis
REDIS_PORT: 6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: ["celery", "-A", "worker.tasks", "worker", "--loglevel=info"]
volumes:
postgres_data:
redis_data:
Très important sur la variante Celery
Le service :
command: ["celery", "-A", "worker.tasks", "worker", "--loglevel=info"]
est seulement un exemple adapté.
Il faudra que le chemin après -A corresponde à ton vrai module Celery.
Par exemple, plus tard, ce pourrait être :
worker.tasksworker.celery_appcore.celery_app
selon la structure que tu choisiras réellement dans le chapitre Celery.
Autrement dit :
api + db= prêt maintenant,redis + celery= prêt conceptuellement, mais dépendra du futur code réellement écrit.
Étape 12 — erreurs fréquentes et comment les comprendre
Erreur 1 — connection to server at "localhost" failed
Cause fréquente :
- ton application tourne dans Docker,
- mais elle essaie encore de parler à PostgreSQL sur
localhost.
Correction :
- dans Compose, assure-toi d’avoir :
environment:
POSTGRES_SERVER: db
POSTGRES_PORT: 5432
Erreur 2 — password authentication failed
Cause fréquente :
- le mot de passe côté API ne correspond pas à celui du service PostgreSQL.
Correction :
- aligne
.envet le blocdb.environment.
Erreur 3 — ModuleNotFoundError
Cause fréquente :
- dépendance oubliée dans
requirements.txt.
Correction :
- mets à jour
requirements.txt, - relance :
docker compose up --build
Erreur 4 — l’API démarre trop tôt
Cause fréquente :
- PostgreSQL n’est pas encore prêt.
Correction :
- garde le
healthcheck, - garde
depends_onaveccondition: service_healthy.
Erreur 5 — les changements de code ne sont pas visibles
Cause fréquente :
- tu n’as pas rebuild l’image.
Correction :
docker compose up --build
Étape 13 — ce que ce chapitre ne fait pas encore
Ce chapitre ne couvre pas encore :
- un déploiement production complet,
- un reverse proxy
nginx, - Alembic,
- multi-stage build,
- optimisation fine de taille d’image,
- stratégie de secrets en production.
Et c’est normal.
Le but ici est de t’apprendre :
- à dockeriser proprement une API FastAPI,
- à la lancer avec PostgreSQL,
- à comprendre comment la stack peut s’étendre ensuite.
Bonnes pratiques à retenir
1. Ne copie pas ton .env dans l’image
On l’injecte au runtime via Compose.
2. Préfère une première version claire à une version ultra-optimisée
Un Dockerfile compréhensible vaut mieux qu’une image trop "maligne" mais opaque.
3. Garde un compose minimal tant que le projet n’a pas besoin de plus
Commence avec :
apidb
Puis ajoute seulement ensuite :
rediscelery
4. Différencie bien : local hors Docker vs réseau Docker
- hors Docker :
localhost - dans Compose : nom du service (
db,redis)
5. Les logs sont ton meilleur outil de diagnostic
Quand quelque chose ne marche pas, commence presque toujours par :
docker compose logs api -f
docker compose logs db -f
Checklist de validation
À la fin, tu dois pouvoir cocher tout ceci :
- j’ai créé un
.dockerignore - j’ai créé un
Dockerfile - mon image se build sans erreur
- j’ai créé un
docker-compose.yml - l’API parle bien à PostgreSQL via
db -
docker compose up --builddémarre correctement la stack -
curl http://localhost:8000/docsrépond - je sais lire les logs
apietdb - je comprends la différence entre la version minimale et la version étendue avec Redis / Celery
Résumé final
À ce stade, ton projet FastAPI n’est plus seulement un projet Python qui marche sur ta machine. Il commence à devenir une application exécutable dans un environnement reproductible.
C’est une étape importante, parce qu’elle rapproche ton projet de plusieurs réalités :
- partage avec d’autres développeurs,
- exécution cohérente sur d’autres machines,
- préparation au déploiement,
- montée vers une stack plus complète.
Le plus important à retenir est simple :
- Docker ne remplace pas une bonne architecture,
- Docker emballe une bonne architecture,
- et Docker Compose te permet de faire vivre ensemble les services dont ton application dépend.
Pour aller plus loin
- Parcours de lecture — pour replacer Docker dans le bon ordre de lecture du wiki
- FAQ, erreurs fréquentes et conseils pratiques — pour retrouver les erreurs d’environnement les plus probables
- Advanced FastAPI — pour garder la vue d’ensemble du bloc avancé
- Réponses personnalisées avec FastAPI — pour continuer ensuite sur l’enrichissement de la couche de sortie