Docker Compose et .env
TaskFlow ne demande rien d'installé en local à part Node et Docker : PostgreSQL, sa base de test, Adminer et Redis tournent dans Docker Compose. Cette page présente le fichier docker-compose.yml, ses profils, les commandes courantes et les fichiers .env lus par l'application. Le fichier est créé au J1, Redis arrive au J9 et le service app au J11.
Le fichier docker-compose.yml
# docker compose up -d -> PostgreSQL + Adminer for `npm run start:dev`
# docker compose --profile queue up -d -> + Redis for the BullMQ queue (J9)
# docker compose --profile app up -d -> + the API itself, built from the Dockerfile (J11)
services:
db:
image: postgres:16-alpine
container_name: taskflow-db
environment:
POSTGRES_USER: taskflow
POSTGRES_PASSWORD: taskflow
POSTGRES_DB: taskflow
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U taskflow -d taskflow"]
interval: 5s
timeout: 3s
retries: 10
# Throwaway database for e2e tests: data lives in memory (tmpfs).
db-test:
image: postgres:16-alpine
container_name: taskflow-db-test
environment:
POSTGRES_USER: taskflow
POSTGRES_PASSWORD: taskflow
POSTGRES_DB: taskflow_test
ports:
- "5433:5432"
tmpfs:
- /var/lib/postgresql/data
adminer:
image: adminer
ports:
- "8081:8080"
depends_on:
- db
redis:
image: redis:7-alpine
profiles: [queue]
ports:
- "6379:6379"
app:
build: .
profiles: [app]
environment:
NODE_ENV: production
PORT: 3000
DB_HOST: db
DB_PORT: 5432
DB_USER: taskflow
DB_PASSWORD: taskflow
DB_NAME: taskflow
# Empty unless exported: the app then refuses to start (validateEnv), while
# the other services still work without it.
JWT_SECRET: ${JWT_SECRET:-}
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
volumes:
pgdata:Les services
| Service | Image | Port local | Profil | Rôle |
|---|---|---|---|---|
db | postgres:16-alpine | 5432 | (aucun) | Base de développement, données dans le volume pgdata |
db-test | postgres:16-alpine | 5433 | (aucun) | Base des tests e2e, données en mémoire (tmpfs) |
adminer | adminer | 8081 | (aucun) | Interface web pour consulter la base |
redis | redis:7-alpine | 6379 | queue | File BullMQ des notifications (optionnelle) |
app | construite depuis le Dockerfile | 3000 | app | L'API elle-même, conteneurisée |
db est la base utilisée par npm run start:dev. Ses données survivent aux redémarrages grâce au volume nommé pgdata. Le healthcheck lance pg_isready toutes les 5 secondes : il indique à Compose quand PostgreSQL accepte vraiment des connexions, ce dont dépend le service app.
db-test est une seconde instance PostgreSQL, sur le port 5433, avec une base taskflow_test. Son répertoire de données est monté en tmpfs, c'est-à-dire en mémoire : la base est rapide et repart vide à chaque arrêt du conteneur. Les tests e2e y appliquent les migrations puis vident les tables avant de s'exécuter (J10). C'est l'option « service Compose » du plan, à la place de Testcontainers.
adminer est une interface web d'administration de base de données, sur http://localhost:8081. Pour vous connecter, choisissez PostgreSQL et indiquez le serveur db (le nom du service sur le réseau Compose, pas localhost), l'utilisateur et le mot de passe taskflow, la base taskflow.
redis sert de stockage à la file BullMQ du J9. Il est optionnel, comme dans le plan : TaskFlow ne charge le module de file que si REDIS_URL est défini.
app construit l'image à partir du Dockerfile (J11) et la lance en NODE_ENV=production, connectée au service db. Trois détails :
JWT_SECRET: ${JWT_SECRET:-}: la valeur vient de votre shell (ou d'un fichier.envà côté dedocker-compose.yml), et vaut une chaîne vide sinon. Aucun secret n'est écrit dans le fichier, et les autres services n'en ont pas besoin :docker compose up -doudocker compose up -d db-testfonctionnent sans rien exporter.- Sans secret, c'est le conteneur
appqui refuse de démarrer : la validation de l'environnement (J7) l'arrête aussitôt sur « property JWT_SECRET has failed the following constraints: minLength, isString ». Exportez une vraie valeur avant le profilapp. depends_onaveccondition: service_healthyattend que lehealthcheckdedbréussisse avant de lancer l'API.
L'image n'applique pas les migrations au démarrage : c'est une étape séparée. npm run migration:run:prod exécute les migrations compilées (dist/) dans un conteneur ponctuel de la même image, avant le démarrage de l'API (séquence dans commandes courantes, détails au J11). Le conteneur app ne reçoit pas REDIS_URL : il fonctionne sans file, les notifications sont alors simplement journalisées.
Vérifié sur TaskFlow : l'image se construit, migration:run:prod crée toutes les tables sur une base vide, puis le conteneur démarre sous l'utilisateur node et /health répond ok. Le déploiement sur un serveur n'a pas été vérifié.
Un secret exporté vaut aussi pour npm run start:dev
Une variable exportée l'emporte sur les fichiers .env.* pour toute application lancée depuis ce shell (ordre de lecture) : le JWT_SECRET exporté pour le profil app remplace alors celui de .env.development.
Écarts avec le fichier du plan
Le fichier proposé par le plan contient db, db-test, adminer et redis, ce dernier marqué « Optionnel (J9) » mais démarré à chaque docker compose up. TaskFlow place redis derrière le profil queue et ajoute le service app (profil app), prévu par le J11 du plan (« Dockerfile et service app dans le Compose »).
Profils Compose
Un service qui déclare profiles ne démarre que si l'un de ses profils est activé (--profile), ou si vous le nommez explicitement sur la ligne de commande. Les services sans profil démarrent à chaque docker compose up qui ne nomme aucun service.
| Commande | Services démarrés |
|---|---|
docker compose up -d | db, db-test, adminer |
docker compose --profile queue up -d | les mêmes + redis |
docker compose --profile app up -d --build | les mêmes + app (image reconstruite ; JWT_SECRET exporté et migrations appliquées) |
docker compose --profile queue --profile app up -d | tout (même condition pour app) |
docker compose up -d db-test | db-test seul (pour lancer les e2e) |
Vérifié avec docker compose config --services : sans profil, la liste est db, adminer, db-test ; --profile queue y ajoute redis, --profile app y ajoute app.
Les profils Compose n'ont rien à voir avec les profils Spring ni avec NODE_ENV : ils choisissent quels conteneurs démarrer, pas comment l'application se configure. La configuration par environnement est traitée au J7.
Commandes courantes
docker compose up -d # démarre db, db-test et adminer en arrière-plan
docker compose ps # état des conteneurs (et du healthcheck de db)
docker compose logs -f db # suit les logs de PostgreSQL
docker compose exec db psql -U taskflow -d taskflow # console SQL sur la base de dev
docker compose stop # arrête les conteneurs, garde tout
docker compose down # supprime les conteneurs, garde le volume pgdataPour les tests e2e, avec un fichier .env.test (voir plus bas) :
docker compose up -d db-test
npm run test:e2ePour l'API conteneurisée (profil app), dans cet ordre :
export JWT_SECRET=$(openssl rand -hex 32) # exigé par le seul conteneur app
docker compose --profile app build
docker compose --profile app run --rm app npm run migration:run:prod
docker compose --profile app up -dPerte de données
docker compose down -v supprime aussi le volume pgdata, donc toute la base de développement. Après cela, relancez npm run migration:run puis npm run seed.
Fichiers .env
L'application lit sa configuration dans des variables d'environnement, validées au démarrage (J7). En développement et en test, elles viennent de fichiers .env.*. Le modèle versionné :
# Copy to .env.development (npm run start:dev) and .env.test (npm run test:e2e).
# Only this example file is versioned.
NODE_ENV=development
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=taskflow
DB_PASSWORD=taskflow
DB_NAME=taskflow
JWT_SECRET=change-me
JWT_EXPIRES_IN=15m
# typeorm (PostgreSQL) or memory (the J2-J3 in-memory store)
PROJECTS_STORE=typeorm
# Set to enable the BullMQ notifications queue (docker compose --profile queue up -d)
# REDIS_URL=redis://localhost:6379
# Comma-separated origins allowed by CORS; unset = no cross-origin access
# CORS_ORIGIN=http://localhost:5173Les variables
| Variable | Rôle | Contrôle au démarrage |
|---|---|---|
NODE_ENV | Environnement : choisit le fichier .env.* et le niveau de log | development, test ou production (défaut development) |
PORT | Port HTTP de l'API | Entier entre 1 et 65535 (défaut 3000) |
DB_HOST, DB_PORT | Adresse de PostgreSQL | Chaîne ; entier (défauts localhost, 5432) |
DB_USER, DB_PASSWORD, DB_NAME | Identifiants et base | Chaînes obligatoires |
JWT_SECRET | Clé de signature des tokens | Obligatoire, 8 caractères au moins |
JWT_EXPIRES_IN | Durée de vie de l'access token | Format 15m, 1h, 7d… (défaut 15m) |
PROJECTS_STORE | Implémentation du store de projets | typeorm ou memory (défaut typeorm) |
REDIS_URL | Active la file BullMQ des notifications | Optionnelle |
CORS_ORIGIN | Origines autorisées par CORS, séparées par des virgules | Lue directement dans app.setup.ts, hors schéma de validation ; absente = aucun accès cross-origin |
Le script de seed lit en plus SEED_ADMIN_PASSWORD (défaut admin-password-dev), absente de .env.example.
Quels fichiers, et lesquels versionner
| Fichier | Utilisé par | Versionné |
|---|---|---|
.env.example | Personne : c'est le modèle à copier | Oui, seul fichier .env* versionné |
.env.development | npm run start:dev, le CLI TypeORM (migrations) et le seed | Non |
.env.test | npm run test:e2e (Jest fixe NODE_ENV=test s'il n'est pas défini) | Non |
.env | Repli optionnel, lu après le fichier propre à l'environnement | Non |
Le .gitignore de TaskFlow (examples/.gitignore) ignore .env* sauf .env.example, et son .dockerignore fait de même : aucun fichier d'environnement local n'entre dans git ni dans l'image.
Pour créer vos fichiers locaux :
cp .env.example .env.development
cp .env.example .env.testDans .env.test, pointez vers la base de test : NODE_ENV=test, DB_PORT=5433, DB_NAME=taskflow_test.
Ordre de lecture
AppModule charge .env.${NODE_ENV} puis .env (envFilePath dans la configuration) :
- si une variable figure dans les deux fichiers, le premier l'emporte ;
- une variable déjà définie dans l'environnement du processus (shell, CI,
environmentde Compose) l'emporte sur les fichiers, comme une variable d'environnement l'emporte surapplication.ymldans Spring Boot.
C'est ce qui permet à la CI de TaskFlow et au service app de se passer de fichier : ils fournissent les variables directement. Le CLI TypeORM (src/database/data-source.ts), qui ne démarre pas Nest, lit le même fichier .env.${NODE_ENV} avec process.loadEnvFile().
Secrets
JWT_SECRET=change-me n'est qu'une valeur de développement. En production, le secret vient du gestionnaire de secrets ou des variables protégées de la CI, jamais d'un fichier versionné.