Passer au contenu principal

Docker Compose pour le développement : infrastructure locale

Docker Compose pour le développement local : PostgreSQL, Redis, Authentik, mise en réseau, vérifications de l'état, gestion des volumes et configurations spécifiques à l'environnement pour les monorepos TypeScript.

E
ECOSIRE Research and Development Team
|19 mars 202611 min de lecture2.0k Mots|

La différence entre une expérience d'intégration agréable (« cloner et exécuter pnpm dev:infra ») et une expérience pénible (« d'abord configurer PostgreSQL, puis configurer Redis, puis... ») dépend de la façon dont votre configuration Docker Compose capture les exigences de votre infrastructure.

La différence entre une expérience d'intégration agréable (« cloner et exécuter pnpm dev:infra ») et une expérience pénible (« d'abord configurer PostgreSQL, puis configurer Redis, puis... ») dépend de la façon dont votre configuration Docker Compose capture les exigences de votre infrastructure. A well-crafted docker-compose.dev.yml lets any developer on any machine have the exact same infrastructure running in minutes.

Ce guide couvre les modèles d'une pile de développement local de qualité production : configuration des services, vérifications de l'état, mise en réseau, gestion des volumes et intégration avec la séquence de démarrage de votre application.

Points clés à retenir

  • Use a non-default port for PostgreSQL locally (5433) to avoid conflicts with system installations
  • Les contrôles de santé sur les dépendances de service évitent les erreurs de démarrage « connexion refusée »
  • Named volumes persist database data between container restarts — bind mounts don't work reliably on Windows
  • Utilisez env_file pour charger les variables d'environnement de votre fichier .env.local dans des conteneurs
  • Separate docker-compose.dev.yml from docker-compose.prod.yml — they serve different purposes
  • The depends_on.condition: service_healthy pattern waits for actual readiness, not just container start
  • Use profiles to make optional services (email, monitoring) opt-in
  • Exécutez docker compose (v2) et non docker-compose (v1) — la syntaxe du plugin est à jour

La pile de développement complète

# infrastructure/docker-compose.dev.yml
name: ecosire-dev

services:
  # ─── PostgreSQL ─────────────────────────────────────────────────
  postgres:
    image: postgres:17-alpine
    container_name: ecosire-postgres
    environment:
      POSTGRES_DB: ecosire_dev
      POSTGRES_USER: ecosire
      POSTGRES_PASSWORD: dev_password_change_in_prod
    ports:
      - "5433:5432"  # 5433 externally — avoids conflicts with system PostgreSQL
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d  # Run SQL on first start
    command: >
      postgres
        -c shared_buffers=256MB
        -c effective_cache_size=1GB
        -c work_mem=16MB
        -c maintenance_work_mem=128MB
        -c checkpoint_completion_target=0.9
        -c wal_buffers=16MB
        -c max_connections=100
        -c log_min_duration_statement=100
        -c log_statement=ddl
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ecosire -d ecosire_dev"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: unless-stopped

  # ─── Redis ──────────────────────────────────────────────────────
  redis:
    image: redis:7-alpine
    container_name: ecosire-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: >
      redis-server
        --maxmemory 512mb
        --maxmemory-policy allkeys-lru
        --appendonly yes
        --appendfsync everysec
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  # ─── Authentik (Identity Provider) ──────────────────────────────
  authentik-server:
    image: ghcr.io/goauthentik/server:2024.12
    container_name: ecosire-authentik
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgres
      AUTHENTIK_POSTGRESQL__USER: ecosire
      AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
      AUTHENTIK_DISABLE_STARTUP_ANALYTICS: "true"
    volumes:
      - authentik_media:/media
      - authentik_certs:/certs
    ports:
      - "9000:9000"  # HTTP
      - "9443:9443"  # HTTPS
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "ak healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s  # Authentik takes time to initialize
    restart: unless-stopped

  authentik-worker:
    image: ghcr.io/goauthentik/server:2024.12
    container_name: ecosire-authentik-worker
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgres
      AUTHENTIK_POSTGRESQL__USER: ecosire
      AUTHENTIK_POSTGRESQL__PASSWORD: dev_password_change_in_prod
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_SECRET_KEY: dev-secret-key-change-in-production-32chars
    volumes:
      - authentik_media:/media
      - authentik_certs:/certs
      - /var/run/docker.sock:/var/run/docker.sock  # For Authentik's proxy
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  # ─── Mailpit (Email Testing) ─────────────────────────────────────
  mailpit:
    image: axllent/mailpit:latest
    container_name: ecosire-mailpit
    ports:
      - "1025:1025"  # SMTP
      - "8025:8025"  # Web UI
    environment:
      MP_MAX_MESSAGES: 200
      MP_SMTP_AUTH_ACCEPT_ANY: true
      MP_SMTP_AUTH_ALLOW_INSECURE: true
    restart: unless-stopped
    profiles:
      - email  # Optional — use `docker compose --profile email up`

  # ─── pgAdmin (Database GUI) ─────────────────────────────────────
  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: ecosire-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@local.dev
      PGADMIN_DEFAULT_PASSWORD: admin
      PGADMIN_CONFIG_SERVER_MODE: "False"
      PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
    ports:
      - "5050:80"
    volumes:
      - pgadmin_data:/var/lib/pgadmin
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped
    profiles:
      - tools  # Optional

networks:
  default:
    name: ecosire-dev-network

volumes:
  postgres_data:
    name: ecosire-postgres-data
  redis_data:
    name: ecosire-redis-data
  authentik_media:
    name: ecosire-authentik-media
  authentik_certs:
    name: ecosire-authentik-certs
  pgadmin_data:
    name: ecosire-pgadmin-data

Scripts Package.json

Câblez les commandes Docker Compose à vos scripts monorepo :

{
  "scripts": {
    "dev:infra": "docker compose -f infrastructure/docker-compose.dev.yml up -d",
    "dev:infra:down": "docker compose -f infrastructure/docker-compose.dev.yml down",
    "dev:infra:logs": "docker compose -f infrastructure/docker-compose.dev.yml logs -f",
    "dev:infra:reset": "docker compose -f infrastructure/docker-compose.dev.yml down -v && pnpm dev:infra",
    "dev:infra:email": "docker compose -f infrastructure/docker-compose.dev.yml --profile email up -d",
    "dev:infra:tools": "docker compose -f infrastructure/docker-compose.dev.yml --profile tools up -d"
  }
}

L'indicateur --profile permet aux services facultatifs (tests de courrier électronique avec Mailpit, interface graphique de base de données avec pgAdmin) de rester inactifs jusqu'à ce qu'ils soient explicitement demandés.


Scripts d'initialisation de base de données

Placez les fichiers SQL dans infrastructure/init-scripts/ — ils s'exécutent au premier démarrage du conteneur :

-- infrastructure/init-scripts/01-create-databases.sql
-- Create all databases Authentik needs separately from the app DB
CREATE DATABASE authentik;
GRANT ALL PRIVILEGES ON DATABASE authentik TO ecosire;

-- Create test database for CI
CREATE DATABASE ecosire_test;
GRANT ALL PRIVILEGES ON DATABASE ecosire_test TO ecosire;
-- infrastructure/init-scripts/02-extensions.sql
-- Enable PostgreSQL extensions
\c ecosire_dev;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";  -- Trigram search
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- Composite GIN indexes

\c ecosire_test;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

Les scripts d'initialisation s'exécutent par ordre alphabétique. The \c database_name psql metacommand switches the active database.


Intégration des variables d'environnement

Votre application lit à partir de .env.local dans la racine monorepo. The Docker services need to know how to connect to each other using service names (not localhost):

# .env.local (monorepo root)

# PostgreSQL — use 5433 externally (host) or 5432 internally (container network)
DATABASE_URL=postgresql://ecosire:dev_password_change_in_prod@localhost:5433/ecosire_dev

# Redis
REDIS_URL=redis://localhost:6379

# Authentik — use 9000 for external calls from your dev machine
AUTHENTIK_URL=http://localhost:9000
# Use service name for server-to-server calls within Docker network
AUTHENTIK_INTERNAL_URL=http://authentik-server:9000

# Email (Mailpit SMTP)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false

# Application
NODE_ENV=development

Pour les applications exécutées dans Docker et qui doivent communiquer avec d’autres services, utilisez les noms de service. For applications running on your host machine (NestJS, Next.js in dev mode), use localhost with the host-mapped ports.


Analyse approfondie des contrôles de santé

Health checks prevent cascading startup failures. Le depends_on.condition: service_healthy attend la disponibilité réelle, pas seulement le démarrage du conteneur :

# Without health checks — can fail because PostgreSQL isn't ready
depends_on:
  - postgres

# With health checks — waits for PostgreSQL to accept connections
depends_on:
  postgres:
    condition: service_healthy

Contrôles de santé personnalisés pour vos propres services :

// apps/api/src/health/health.controller.ts
@Get()
@Public()
@HealthCheck()
async check() {
  return this.health.check([
    () => this.db.isHealthy('database'),
    () => this.redis.isHealthy('redis'),
  ]);
}
# If your API is also dockerized
api:
  image: ecosire-api:latest
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:3001/api/health"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 30s

Gestion des volumes

Les volumes nommés conservent les données entre les redémarrages. Comprenez quand utiliser chaque type de volume :

TapezPersistancePerformancesUtiliser pour
Volume nomméOuiExcellentDonnées de base de données
Lier le supportOuiBon (Linux), Mauvais (macOS)Rechargement à chaud du code source
tmpfsNonExcellentFichiers temporaires, secrets
# Use bind mounts for source code (enables hot reload)
volumes:
  - ./apps/api/src:/app/src  # Code changes reflected immediately

# Use named volumes for data
volumes:
  - postgres_data:/var/lib/postgresql/data

# Use tmpfs for ephemeral data
volumes:
  - type: tmpfs
    target: /tmp

Sur macOS avec Docker Desktop, les montages de liaison utilisent gRPC FUSE qui est nettement plus lent que sous Linux. Pour les serveurs de développement NestJS et Next.js, exécutez-les directement sur votre machine hôte (pas dans Docker) pour obtenir les performances natives du système de fichiers.


Production Docker Composer

Le fichier de composition de production est structurellement différent : pas de ports locaux, de politiques de redémarrage, de limites de ressources de production :

# infrastructure/docker-compose.prod.yml
name: ecosire-prod

services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    # No port mapping — only accessible within Docker network
    restart: always
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 512M

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 1gb
    volumes:
      - redis_data:/data
    restart: always
    # No port mapping — internal only

volumes:
  postgres_data:
  redis_data:

La production n'expose pas les ports en externe : les applications se connectent via le réseau Docker interne. Nginx gère le trafic externe.


Pièges courants et solutions

Piège 1 : Le port est en conflit avec les services système

PostgreSQL, Redis et d'autres services s'exécutent souvent en tant que services système. Mappez toujours vers des ports non standard en cours de développement :

  • PostgreSQL : 5433:5432 (et non 5432:5432)
  • Redis : conserver 6379:6379 (rarement en conflit)
  • Exécutez lsof -i :5432 pour vérifier ce qui utilise le port par défaut

Piège 2 : Problèmes d'autorisation de volume sous Linux

Les volumes Docker sous Linux utilisent la propriété root par défaut. Si l'utilisateur de votre conteneur n'est pas root, définissez la propriété correcte :

postgres:
  image: postgres:17-alpine
  user: "999:999"  # postgres user's UID:GID
  # Or use init container to fix permissions

Piège 3 : l'initialisation d'Authentik prend plus de 60 secondes

Authentik exécute les migrations de bases de données au premier démarrage. Le start_period: 60s dans le bilan de santé lui donne l'heure. Si les services dépendants démarrent avant qu’Authentik ne soit prêt, ils échoueront. Utilisez la condition service_healthy et donnez-lui suffisamment de start_period.

Piège 4 : limites des ressources Docker Desktop sur Mac

Docker Desktop par défaut alloue 2 processeurs et 2 Go de RAM, ce qui n'est pas suffisant pour que PostgreSQL + Redis + Authentik s'exécutent simultanément. Augmentation des paramètres de Docker Desktop > Ressources à au moins 4 processeurs et 6 Go de RAM.

Piège 5 : docker-compose contre docker compose

L'ancien docker-compose (v1, écrit en Python) est obsolète. Utilisez docker compose (v2, le plugin). Vérifiez votre version : docker compose version. Si vous voyez Docker Compose version v2.x.x, vous utilisez la version v2.


Questions fréquemment posées

Dois-je exécuter mes services d'application (NestJS, Next.js) dans Docker pendant le développement ?

Généralement non : pour un développement actif, exécutez vos services d’application sur votre machine hôte pour un rechargement à chaud plus rapide et un débogage plus facile. Utilisez Docker uniquement pour les services d'infrastructure (bases de données, caches, fournisseurs d'identité) qui sont stables et ne nécessitent pas de redémarrages fréquents. L'exception est si votre application possède des dépendances natives qui diffèrent entre votre système d'exploitation de développement et l'environnement de production.

Comment gérer les migrations de bases de données dans le workflow Docker Compose ?

Exécutez les migrations depuis votre machine hôte après avoir démarré l'infrastructure : pnpm dev:infra && pnpm db:migrate. N'exécutez pas de migrations dans un conteneur Docker pendant le développement : vous perdez la vérification de type et l'intégration IDE qui garantissent la sécurité des migrations Drizzle. Pour la création initiale de la base de données, utilisez les scripts initdb.d de Docker.

Comment sauvegarder et restaurer mes volumes Docker locaux ?

Utilisez docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data pour sauvegarder. Restaurez avec la même approche en utilisant tar xzf. Pour le développement, vous pouvez également effectuer un dump avec pg_dump et restaurer avec psql puisque le port est exposé.

Comment partager l'état de Docker Compose avec d'autres membres de l'équipe ?

Le fichier Docker Compose est partagé via git, mais les données en volumes sont locales. Chaque développeur commence avec une base de données vide et exécute des migrations/graines pour la remplir. Utilisez des scripts de départ (engagés dans le dépôt) pour créer des données de test cohérentes. Le docker-compose.dev.yml partagé garantit que tout le monde utilise les mêmes versions et configurations de service.

Pourquoi utiliser Mailpit au lieu d'un véritable e-mail en développement ?

Mailpit est un serveur SMTP local qui capture tous les e-mails sortants et fournit une interface utilisateur Web pour les afficher. Il évite l'envoi accidentel de vrais e-mails à de vrais utilisateurs pendant le développement, ne nécessite pas d'informations d'identification SMTP et vous permet de vérifier les modèles d'e-mails sans consulter votre boîte de réception. Configurez votre application pour utiliser SMTP_HOST=localhost SMTP_PORT=1025 et visitez http://localhost:8025 pour voir les e-mails capturés.


Prochaines étapes

Une configuration Docker Compose bien conçue pour le développement local est un investissement qui rapporte des dividendes chaque fois qu'un nouveau développeur rejoint ou que vous démarrez une nouvelle machine. ECOSIRE exécute PostgreSQL 17, Redis 7 et Authentik dans Docker Compose pour le développement local dans toute l'équipe.

Besoin d'aide pour concevoir votre infrastructure de développement local ou conteneuriser votre application pour la production ? Découvrez nos services DevOps pour voir comment nous pouvons vous aider.

E

Rédigé par

ECOSIRE Research and Development Team

Rédaction technique

L'équipe de rédaction technique d'ECOSIRE traite d'Odoo ERP, de l'e-commerce avec Shopify, des agents IA, de l'analytique avec Power BI, de l'automatisation avec GoHighLevel et des bonnes pratiques du logiciel d'entreprise. Nos guides aident les entreprises à prendre des décisions technologiques éclairées.

ECOSIRE

Développez votre entreprise avec ECOSIRE

Solutions d'entreprise pour l'ERP, le commerce électronique, l'IA, l'analyse et l'automatisation.