メインコンテンツにスキップ

開発用の Docker Compose: ローカル インフラストラクチャ

ローカル開発用の Docker Compose: PostgreSQL、Redis、Authentik、ネットワーキング、ヘルス チェック、ボリューム管理、TypeScript モノリポジトリの環境固有の構成。

E
ECOSIRE Research and Development Team
|2026年3月19日6 分で読める1.2k 語数|

快適なオンボーディング エクスペリエンス (「pnpm dev:infra をクローンして実行する」) と苦痛なオンボーディング エクスペリエンス (「最初に PostgreSQL をセットアップし、次に Redis を構成し、その後...」) の違いは、Docker Compose セットアップがインフラストラクチャ要件をどの程度適切に把握しているかによって決まります。うまく作成された docker-compose.dev.yml を使用すると、開発者はどのマシンでも、まったく同じインフラストラクチャを数分で実行できるようになります。

快適なオンボーディング エクスペリエンス (「pnpm dev:infra をクローンして実行する」) と苦痛なオンボーディング エクスペリエンス (「最初に PostgreSQL をセットアップし、次に Redis を構成し、その後...」) の違いは、Docker Compose セットアップがインフラストラクチャ要件をどの程度適切に把握しているかによって決まります。うまく作成された docker-compose.dev.yml を使用すると、開発者はどのマシンでも、まったく同じインフラストラクチャを数分で実行できるようになります。

このガイドでは、サービス構成、ヘルスチェック、ネットワーキング、ボリューム管理、アプリケーションの起動シーケンスとの統合など、運用品質のローカル開発スタックのパターンについて説明します。

重要なポイント

  • システム インストールとの競合を避けるために、PostgreSQL のデフォルト以外のポート (5433) をローカルで使用します。
  • サービスの依存関係のヘルスチェックにより、「接続が拒否されました」という起動エラーを防止します
  • 名前付きボリュームはコンテナーの再起動間でデータベース データを保持します — Windows ではバインド マウントが確実に機能しません
  • env_file を使用して、環境変数を .env.local ファイルからコンテナにロードします
  • docker-compose.dev.yml を docker-compose.prod.yml から分離します - それぞれ異なる目的を果たします
  • depends_on.condition: service_healthy パターンは、コンテナーの開始だけでなく、実際の準備が完了するまで待機します
  • profiles を使用してオプション サービス (電子メール、モニタリング) をオプトインにします
  • docker-compose (v1) ではなく docker compose (v2) を実行します — プラグイン構文は最新のものです

完全な開発スタック

# 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

Package.json スクリプト

Docker Compose コマンドを 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"
  }
}

--profile フラグを使用すると、オプションのサービス (Mailpit による電子メール テスト、pgAdmin によるデータベース GUI) は、明示的に要求されるまで休止状態のままになります。


データベース初期化スクリプト

SQL ファイルを infrastructure/init-scripts/ に配置します。これらは最初のコンテナーの起動時に実行されます。

-- 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";

初期化スクリプトはアルファベット順に実行されます。 \c database_name psql メタコマンドは、アクティブなデータベースを切り替えます。


環境変数の統合

アプリケーションはモノリポジトリのルートにある .env.local から読み取ります。 Docker サービスは、サービス名 (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

Docker 内で実行され、他のサービスと通信する必要があるアプリケーションの場合は、サービス名を使用します。ホスト マシン (開発モードの NestJS、Next.js) で実行されているアプリケーションの場合は、ホスト マップされたポートで localhost を使用します。


ヘルスチェックの詳細

ヘルスチェックにより、連鎖的な起動エラーが防止されます。 depends_on.condition: service_healthy は、コンテナーの開始だけでなく、実際の準備が完了するまで待機します。

# 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

独自のサービスのカスタム ヘルス チェック:

// 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

ボリューム管理

名前付きボリュームは、再起動間でデータを保持します。各ボリューム タイプをいつ使用するかを理解します。

タイプ永続性パフォーマンス用途
名前付きボリュームはい素晴らしいデータベースデータ
バインドマウントはい良い (Linux)、悪い (macOS)ソースコードのホットリロード
tmpfsいいえ素晴らしい一時ファイル、シークレット
# 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

Docker Desktop を備えた macOS では、バインド マウントは gRPC FUSE を使用しますが、これは Linux よりも大幅に遅くなります。 NestJS および Next.js 開発サーバーの場合は、(Docker ではなく) ホスト マシン上で直接実行して、ネイティブ ファイル システムのパフォーマンスを取得します。


プロダクション Docker Compose

本番構成ファイルは構造的に異なります。ローカル ポート、再起動ポリシー、本番リソース制限はありません。

# 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:

運用環境ではポートが外部に公開されません。アプリケーションは内部 Docker ネットワーク経由で接続します。 Nginx は外部トラフィックを処理します。


よくある落とし穴と解決策

落とし穴 1: ポートがシステム サービスと競合する

PostgreSQL、Redis、およびその他のサービスは、多くの場合、システム サービスとして実行されます。開発中は常に非標準ポートにマップします。

  • PostgreSQL: 5433:5432 (5432:5432 ではありません)
  • Redis: 6379:6379 を保持します (競合はほとんどありません)
  • lsof -i :5432 を実行してデフォルトのポートを使用しているものを確認します

落とし穴 2: Linux でのボリューム権限の問題

Linux 上の Docker ボリュームは、デフォルトで root 所有権を使用します。コンテナ ユーザーが root 以外の場合は、正しい所有権を設定します。

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

落とし穴 3: Authentik の初期化には 60 秒以上かかります

Authentik は初回起動時にデータベース移行を実行します。ヘルスチェックの start_period: 60s により時間が与えられます。 Authentik の準備が整う前に依存サービスが開始されると、サービスは失敗します。 service_healthy 条件を使用し、十分な start_period を与えます。

落とし穴 4: Mac における Docker デスクトップのリソース制限

デフォルトの Docker Desktop は 2 つの CPU と 2GB RAM を割り当てますが、PostgreSQL + Redis + Authentik を同時に実行するには十分ではありません。 [Docker デスクトップ設定] > [リソース] を少なくとも 4 つの CPU と 6GB RAM に増やします。

落とし穴 5: docker-compose と docker compose

古い docker-compose (v1、Python で記述) は非推奨になりました。 docker compose (v2、プラグイン) を使用します。バージョンを確認してください: docker compose version。 Docker Compose version v2.x.x が表示される場合は、v2 を使用しています。


よくある質問

開発中にアプリケーション サービス (NestJS、Next.js) を Docker で実行する必要がありますか?

通常はいいえ。アクティブな開発の場合は、ホットリロードを高速化し、デバッグを容易にするために、ホスト マシン上でアプリケーション サービスを実行します。 Docker は、安定していて頻繁に再起動する必要のないインフラストラクチャ サービス (データベース、キャッシュ、アイデンティティ プロバイダー) にのみ使用してください。例外は、アプリケーションに開発 OS と運用環境の間で異なるネイティブ依存関係がある場合です。

Docker Compose ワークフローでデータベース移行を処理するにはどうすればよいですか?

インフラストラクチャ pnpm dev:infra && pnpm db:migrate を開始した後、ホスト マシンから移行を実行します。開発中に Docker コンテナ内で移行を実行しないでください。Drizzle 移行を安全にする型チェックと IDE 統合が失われます。最初のデータベース作成には、Docker の initdb.d スクリプトを使用します。

ローカルの Docker ボリュームをバックアップおよび復元するにはどうすればよいですか?

docker run --rm -v postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data を使用してバックアップします。 tar xzf を使用して同じアプローチで復元します。開発の場合は、ポートが公開されているため、pg_dump でダンプし、psql で復元することもできます。

Docker Compose の状態を他のチーム メンバーと共有するにはどうすればよいですか?

Docker Compose ファイルは git 経由で共有されますが、ボリューム内のデータはローカルにあります。各開発者は空のデータベースから開始し、移行/シードを実行してデータベースにデータを追加します。シード スクリプト (リポジトリにコミットされている) を使用して、一貫したテスト データを作成します。共有 docker-compose.dev.yml により、全員が同じサービス バージョンと構成を使用できるようになります。

開発時に実際の電子メールの代わりに Mailpit を使用する理由は何ですか?

Mailpit は、すべての送信電子メールをキャプチャし、それらを表示するための Web UI を提供するローカル SMTP サーバーです。開発中に実際のユーザーに実際の電子メールを誤って送信することを防ぎ、SMTP 資格情報を必要とせず、受信トレイをチェックせずに電子メール テンプレートを検証できます。 SMTP_HOST=localhost SMTP_PORT=1025 を使用するようにアプリを構成し、http://localhost:8025 にアクセスしてキャプチャされたメールを表示します。


次のステップ

ローカル開発用によく練られた Docker Compose セットアップは、新しい開発者が参加したり、新しいマシンを起動したりするたびに、必ず利益をもたらす投資です。 ECOSIRE は、チーム全体のローカル開発のために PostgreSQL 17、Redis 7、および Authentik を Docker Compose で実行します。

ローカル開発インフラストラクチャの設計や、実稼働用のアプリケーションのコンテナ化についてサポートが必要ですか? 当社の DevOps サービスをご覧ください して、当社がどのようにお手伝いできるかをご確認ください。

E

執筆者

ECOSIRE Research and Development Team

テクニカルライティング

ECOSIRE のテクニカルライティングチームは、Odoo ERP、Shopify による EC、AI エージェント、Power BI による分析、GoHighLevel による自動化、そしてエンタープライズソフトウェアのベストプラクティスを扱っています。私たちのガイドは、企業が十分な情報に基づいてテクノロジーを判断できるよう支援します。

ECOSIRE

ECOSIRE でビジネスを成長させましょう

ERP、eコマース、AI、分析、自動化にわたるエンタープライズ ソリューション。