タグ: インフラ

  • 【エンジニア向け】n8n Docker Compose設定完全ガイド|環境変数・PostgreSQL・Queue Mode・Traefik連携

    【エンジニア向け】n8n Docker Compose設定完全ガイド|環境変数・PostgreSQL・Queue Mode・Traefik連携

    n8nをDockerで運用するエンジニアにとって、Docker Composeの設定は最も重要な基盤です。

    環境変数の設定ミス、ボリュームの永続化忘れ、ネットワーク設定の誤りなど、多くのトラブルはdocker-compose.ymlの設定に起因します。

    この記事では、n8nのDocker Compose設定について、環境変数の完全リファレンス、本番向け構成パターン、Queue Mode設定、Traefik連携まで、エンジニアが知っておくべき技術を網羅的に解説します。

    基本構成:最小限のdocker-compose.yml

    まず、ローカル開発用の最小構成を確認します。

    最小構成(SQLite + HTTP)


    version: "3.8"

    services:
    n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
    - "5678:5678"
    environment:
    - GENERIC_TIMEZONE=Asia/Tokyo
    - TZ=Asia/Tokyo
    volumes:
    - n8n_data:/home/node/.n8n

    volumes:
    n8n_data:

    この構成の特徴:

    • データベース:SQLite(デフォルト)
    • プロトコル:HTTP(ローカル開発向け)
    • ボリューム:Docker Named Volume

    起動コマンド


    # 起動
    docker compose up -d

    # ログ確認
    docker compose logs -f n8n

    # 停止
    docker compose down

    # ボリューム含めて削除
    docker compose down -v

    環境変数リファレンス

    n8nの挙動は環境変数で制御します。カテゴリ別に主要な環境変数を解説します。

    基本設定

    環境変数説明デフォルト
    N8N_HOSTn8nのホスト名localhostn8n.example.com
    N8N_PORTリッスンポート56785678
    N8N_PROTOCOLプロトコルhttphttps
    WEBHOOK_URLWebhook用外部URLhttps://n8n.example.com/
    N8N_EDITOR_BASE_URLエディタのベースURLhttps://n8n.example.com/
    GENERIC_TIMEZONEタイムゾーンAmerica/New_YorkAsia/Tokyo
    TZシステムタイムゾーンUTCAsia/Tokyo

    認証・セキュリティ

    環境変数説明デフォルト
    N8N_BASIC_AUTH_ACTIVEBasic認証の有効化false
    N8N_BASIC_AUTH_USERBasic認証ユーザー名
    N8N_BASIC_AUTH_PASSWORDBasic認証パスワード
    N8N_ENCRYPTION_KEY認証情報暗号化キー自動生成
    N8N_SECURE_COOKIESecure Cookieの有効化true
    N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS設定ファイル権限チェックfalse

    N8N_ENCRYPTION_KEYの重要性


    # 32バイト(64文字の16進数)を生成
    openssl rand -hex 32

    このキーは認証情報(Credentials)の暗号化に使用されます。設定しないと起動時に自動生成されますが、コンテナを再作成すると既存の認証情報が復号できなくなります。必ず明示的に設定し、安全にバックアップしてください。

    データベース設定

    環境変数説明デフォルト
    DB_TYPEデータベースタイプsqlite
    DB_POSTGRESDB_HOSTPostgreSQLホストlocalhost
    DB_POSTGRESDB_PORTPostgreSQLポート5432
    DB_POSTGRESDB_DATABASEデータベース名n8n
    DB_POSTGRESDB_USERユーザー名postgres
    DB_POSTGRESDB_PASSWORDパスワード
    DB_POSTGRESDB_SCHEMAスキーマ名public

    実行履歴(Executions)

    環境変数説明デフォルト
    EXECUTIONS_DATA_PRUNE実行履歴の自動削除true
    EXECUTIONS_DATA_MAX_AGE保持期間(時間)336(14日)
    EXECUTIONS_DATA_PRUNE_MAX_COUNT最大保持件数10000
    EXECUTIONS_DATA_SAVE_ON_ERRORエラー時の保存all
    EXECUTIONS_DATA_SAVE_ON_SUCCESS成功時の保存all
    EXECUTIONS_DATA_SAVE_ON_PROGRESS進行中の保存false
    EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS手動実行の保存true

    Queue Mode(Redis)

    環境変数説明デフォルト
    EXECUTIONS_MODE実行モードregular
    QUEUE_BULL_REDIS_HOSTRedisホストlocalhost
    QUEUE_BULL_REDIS_PORTRedisポート6379
    QUEUE_BULL_REDIS_PASSWORDRedisパスワード
    QUEUE_BULL_REDIS_DBRedisDB番号0
    QUEUE_HEALTH_CHECK_ACTIVEヘルスチェックfalse

    本番向け構成:PostgreSQL + HTTPS

    本番環境向けの推奨構成です。

    .envファイル


    # ドメイン設定
    DOMAIN_NAME=example.com
    SUBDOMAIN=n8n
    SSL_EMAIL=admin@example.com

    # PostgreSQL
    POSTGRES_USER=n8n
    POSTGRES_PASSWORD=your_strong_db_password
    POSTGRES_DB=n8n

    # n8n
    N8N_ENCRYPTION_KEY=your_64_char_hex_key_here
    N8N_BASIC_AUTH_USER=admin
    N8N_BASIC_AUTH_PASSWORD=your_admin_password

    # タイムゾーン
    GENERIC_TIMEZONE=Asia/Tokyo

    docker-compose.yml(PostgreSQL + Nginx)


    version: "3.8"

    services:
    postgres:
    image: postgres:15-alpine
    container_name: n8n-postgres
    restart: always
    environment:
    - POSTGRES_USER=${POSTGRES_USER}
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    - POSTGRES_DB=${POSTGRES_DB}
    volumes:
    - postgres_data:/var/lib/postgresql/data
    networks:
    - n8n-network
    healthcheck:
    test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
    interval: 10s
    timeout: 5s
    retries: 5

    n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    ports:
    - "5678:5678"
    environment:
    # データベース
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_PORT=5432
    - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
    - DB_POSTGRESDB_USER=${POSTGRES_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    # 認証
    - N8N_BASIC_AUTH_ACTIVE=true
    - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
    - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
    - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    # ホスト設定
    - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
    - N8N_PORT=5678
    - N8N_PROTOCOL=https
    - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
    - N8N_SECURE_COOKIE=true
    # タイムゾーン
    - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
    - TZ=${GENERIC_TIMEZONE}
    # 実行履歴
    - EXECUTIONS_DATA_PRUNE=true
    - EXECUTIONS_DATA_MAX_AGE=168
    - EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000
    # その他
    - NODE_ENV=production
    - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
    volumes:
    - n8n_data:/home/node/.n8n
    - ./local-files:/files
    networks:
    - n8n-network
    depends_on:
    postgres:
    condition: service_healthy

    volumes:
    postgres_data:
    n8n_data:

    networks:
    n8n-network:
    driver: bridge

    Traefik連携:自動SSL証明書

    Traefikを使用すると、Let’s EncryptのSSL証明書を自動取得・更新できます。

    docker-compose.yml(Traefik版)


    version: "3.8"

    services:
    traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: always
    command:
    - "--api.dashboard=true"
    - "--providers.docker=true"
    - "--providers.docker.exposedbydefault=false"
    - "--entrypoints.web.address=:80"
    - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
    - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
    - "--entrypoints.websecure.address=:443"
    - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
    - "--certificatesresolvers.letsencrypt.acme.email=${SSL_EMAIL}"
    - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
    - "80:80"
    - "443:443"
    volumes:
    - ./traefik_data:/letsencrypt
    - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
    - n8n-network

    postgres:
    image: postgres:15-alpine
    container_name: n8n-postgres
    restart: always
    environment:
    - POSTGRES_USER=${POSTGRES_USER}
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    - POSTGRES_DB=${POSTGRES_DB}
    volumes:
    - postgres_data:/var/lib/postgresql/data
    networks:
    - n8n-network
    healthcheck:
    test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
    interval: 10s
    timeout: 5s
    retries: 5

    n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    environment:
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_PORT=5432
    - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
    - DB_POSTGRESDB_USER=${POSTGRES_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    - N8N_BASIC_AUTH_ACTIVE=true
    - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
    - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
    - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
    - N8N_PROTOCOL=https
    - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
    - N8N_SECURE_COOKIE=true
    - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
    - TZ=${GENERIC_TIMEZONE}
    - NODE_ENV=production
    volumes:
    - n8n_data:/home/node/.n8n
    - ./local-files:/files
    networks:
    - n8n-network
    labels:
    - "traefik.enable=true"
    - "traefik.http.routers.n8n.rule=Host(${SUBDOMAIN}.${DOMAIN_NAME})"
    - "traefik.http.routers.n8n.entrypoints=websecure"
    - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
    - "traefik.http.services.n8n.loadbalancer.server.port=5678"
    depends_on:
    postgres:
    condition: service_healthy

    volumes:
    postgres_data:
    n8n_data:

    networks:
    n8n-network:
    driver: bridge

    Traefik用ディレクトリ準備


    mkdir -p traefik_data local-files
    touch traefik_data/acme.json
    chmod 600 traefik_data/acme.json

    Queue Mode構成:Redis + Worker

    大規模なワークフロー処理には、Queue Modeを使用してワーカーを分離します。

    Queue Modeのアーキテクチャ

    • Main(n8n):UI、API、Webhookを処理し、ジョブをRedisに投入
    • Redis:ジョブキューとして機能
    • Worker:Redisからジョブを取得して実行
    • PostgreSQL:ワークフロー定義、認証情報、実行結果を保存

    docker-compose.yml(Queue Mode)


    version: "3.8"

    services:
    postgres:
    image: postgres:15-alpine
    container_name: n8n-postgres
    restart: always
    environment:
    - POSTGRES_USER=${POSTGRES_USER}
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    - POSTGRES_DB=${POSTGRES_DB}
    volumes:
    - postgres_data:/var/lib/postgresql/data
    networks:
    - n8n-network
    healthcheck:
    test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
    interval: 10s
    timeout: 5s
    retries: 5

    redis:
    image: redis:7-alpine
    container_name: n8n-redis
    restart: always
    volumes:
    - redis_data:/data
    networks:
    - n8n-network
    healthcheck:
    test: ['CMD', 'redis-cli', 'ping']
    interval: 10s
    timeout: 5s
    retries: 5

    n8n:
    image: n8nio/n8n:latest
    container_name: n8n-main
    restart: always
    ports:
    - "5678:5678"
    environment:
    # データベース
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_PORT=5432
    - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
    - DB_POSTGRESDB_USER=${POSTGRES_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    # Queue Mode
    - EXECUTIONS_MODE=queue
    - QUEUE_BULL_REDIS_HOST=redis
    - QUEUE_BULL_REDIS_PORT=6379
    - QUEUE_HEALTH_CHECK_ACTIVE=true
    # 認証・セキュリティ
    - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    - N8N_BASIC_AUTH_ACTIVE=true
    - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
    - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}
    # ホスト設定
    - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
    - N8N_PROTOCOL=https
    - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
    - N8N_SECURE_COOKIE=true
    # タイムゾーン
    - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
    - TZ=${GENERIC_TIMEZONE}
    - NODE_ENV=production
    volumes:
    - n8n_data:/home/node/.n8n
    networks:
    - n8n-network
    depends_on:
    postgres:
    condition: service_healthy
    redis:
    condition: service_healthy

    n8n-worker:
    image: n8nio/n8n:latest
    container_name: n8n-worker
    restart: always
    command: worker
    environment:
    # データベース(Mainと同じ設定)
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_PORT=5432
    - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
    - DB_POSTGRESDB_USER=${POSTGRES_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    # Queue Mode
    - EXECUTIONS_MODE=queue
    - QUEUE_BULL_REDIS_HOST=redis
    - QUEUE_BULL_REDIS_PORT=6379
    # 暗号化キー(Mainと同じ)
    - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    # タイムゾーン
    - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
    - TZ=${GENERIC_TIMEZONE}
    - NODE_ENV=production
    volumes:
    - n8n_data:/home/node/.n8n
    networks:
    - n8n-network
    depends_on:
    - n8n
    - redis

    volumes:
    postgres_data:
    redis_data:
    n8n_data:

    networks:
    n8n-network:
    driver: bridge

    Workerのスケーリング


    # Worker数を増やす
    docker compose up -d --scale n8n-worker=3

    ボリュームとパーミッション

    Named Volume vs Bind Mount

    方式メリットデメリット
    Named VolumeDockerが管理、移植性が高い直接アクセスしにくい
    Bind Mount直接アクセス可能、バックアップ容易パーミッション管理が必要

    Bind Mountを使用する場合


    volumes:
    - ./n8n_data:/home/node/.n8n
    - ./postgres_data:/var/lib/postgresql/data

    パーミッション設定


    # n8nデータ(コンテナ内はnode:node = 1000:1000)
    sudo mkdir -p n8n_data
    sudo chown -R 1000:1000 n8n_data

    # PostgreSQLデータ(コンテナ内はpostgres = 999:999)
    sudo mkdir -p postgres_data
    sudo chown -R 999:999 postgres_data

    機密情報の管理(_FILE接尾辞)

    環境変数に直接パスワードを記載する代わりに、ファイルから読み込むことができます。

    Docker Secretsの使用


    # シークレットファイルを作成
    echo "your_db_password" > ./secrets/db_password.txt
    echo "your_encryption_key" > ./secrets/encryption_key.txt
    chmod 600 ./secrets/*


    services:
    n8n:
    environment:
    - DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/db_password
    - N8N_ENCRYPTION_KEY_FILE=/run/secrets/encryption_key
    secrets:
    - db_password
    - encryption_key

    secrets:
    db_password:
    file: ./secrets/db_password.txt
    encryption_key:
    file: ./secrets/encryption_key.txt

    ヘルスチェックとリソース制限

    ヘルスチェック設定


    services:
    n8n:
    healthcheck:
    test: ['CMD-SHELL', 'wget -q --spider http://localhost:5678/healthz || exit 1']
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 30s

    リソース制限


    services:
    n8n:
    deploy:
    resources:
    limits:
    cpus: '2'
    memory: 2G
    reservations:
    cpus: '0.5'
    memory: 512M

    アップデート手順

    イメージの更新

    # 最新イメージをプル docker compose pull # コンテナを再作成 docker compose up -d

    # 古いイメージを削除
    docker image prune -f

    バージョン固定(推奨)

    本番環境では、latestではなくバージョンを固定することを推奨します。


    services:
    n8n:
    image: n8nio/n8n:1.70.0

    トラブルシューティング

    よくある問題と解決方法

    問題原因解決方法
    認証情報が読めないN8N_ENCRYPTION_KEYの変更元のキーを復元
    PostgreSQLに接続できない起動順序の問題depends_on + healthcheckを設定
    SQLiteにフォールバックDB_TYPE未設定DB_TYPE=postgresdbを確認
    Webhookが動作しないWEBHOOK_URL未設定外部アクセス可能なURLを設定
    SSL証明書エラーDNS未反映Aレコードの設定を確認
    Permission deniedボリュームの権限UID/GIDを確認してchown

    ログ確認コマンド


    # 全サービスのログ
    docker compose logs -f

    # 特定サービスのログ
    docker compose logs -f n8n

    # 最新100行のみ
    docker compose logs --tail 100 n8n

    # タイムスタンプ付き
    docker compose logs -t n8n

    コンテナ内でのデバッグ


    # n8nコンテナに入る
    docker exec -it n8n /bin/sh

    # PostgreSQLに接続
    docker exec -it n8n-postgres psql -U n8n -d n8n

    # Redisに接続
    docker exec -it n8n-redis redis-cli

    よくある質問(FAQ)

    Q. latestタグを使っても大丈夫ですか?

    A. 開発環境では問題ありませんが、本番環境ではバージョンを固定することを強く推奨します。n8nは週次でリリースがあり、破壊的変更が含まれる可能性があります。

    Q. Named VolumeとBind Mountはどちらが良いですか?

    A. 一般的にはNamed Volumeが推奨されます。ただし、バックアップを容易にしたい場合やファイルに直接アクセスしたい場合はBind Mountが便利です。

    Q. Queue ModeではRedisは必須ですか?

    A. はい、Queue ModeではRedisが必須です。Redisがジョブキューとして機能し、MainプロセスとWorkerプロセス間の通信を仲介します。

    Q. Workerは何台必要ですか?

    A. ワークフローの実行頻度と複雑さによります。小〜中規模であれば1〜2台、大規模であれば3台以上を検討してください。CPUコア数に応じてスケールするのが目安です。

    Q. docker compose v1とv2の違いは?

    A. v2は「docker compose」(ハイフンなし)で実行し、v1は「docker-compose」(ハイフンあり)です。現在はv2が推奨されており、v1は非推奨です。

    まとめ

    この記事では、n8nのDocker Compose設定について詳しく解説しました。

    構成パターン

    • 最小構成:SQLite + HTTP(ローカル開発向け)
    • 本番構成:PostgreSQL + HTTPS(Nginx or Traefik)
    • スケール構成:Queue Mode + Redis + Worker

    重要な環境変数

    • N8N_ENCRYPTION_KEY:認証情報の暗号化(必ず固定・バックアップ)
    • DB_TYPE:データベースタイプ(本番はpostgresdb)
    • WEBHOOK_URL:Webhook用外部URL(HTTPS推奨)
    • EXECUTIONS_MODE:実行モード(スケール時はqueue)

    本番運用のポイント

    • バージョンを固定(latestは避ける)
    • healthcheckでサービス依存関係を管理
    • リソース制限でメモリ枯渇を防止
    • 機密情報は_FILE接尾辞でファイルから読み込み

    Docker Composeの設定を正しく理解することで、n8nの安定運用が実現できます。

  • Dockerの辛いところ|20年現場で見てきた「便利だけど困る」7つの落とし穴

    Dockerの辛いところ|20年現場で見てきた「便利だけど困る」7つの落とし穴

    「Dockerさえ入れれば全部解決する」

    そんな期待を胸にDockerを導入したものの、気づけば新たな悩みと格闘している…。開発の現場でこんな経験をしたことはないでしょうか。

    前回の記事でDockerのメリットをお伝えしましたが、20年以上エンジニアとして様々な現場を見てきた私としては、正直に言わなければならないこともあります。Dockerは確かに革命的な技術ですが、「銀の弾丸」ではありません。

    この記事では、Dockerを実際に使い込んでいく中で遭遇する「辛いところ」を率直にお伝えします。これからDockerを導入しようとしている方も、すでに使っていて困っている方も、事前に知っておくことで対策が立てやすくなるはずです。

    辛いところ1:Mac・Windowsだと動作が遅い

    Dockerの最大の落とし穴のひとつが、Mac・Windows環境での動作速度です。

    「軽量で高速」がDockerの売りのはずなのに、実際に使ってみると「なんか遅い…」と感じる方は多いのではないでしょうか。特に開発中にファイルを編集するたびに反映が遅かったり、ビルドに時間がかかったりすると、せっかくの開発効率が台無しです。

    なぜ遅くなるのか

    根本的な原因は、DockerがLinuxのカーネル機能を前提としているところにあります。

    LinuxマシンでDockerを動かす場合、コンテナはホストOSのカーネルをそのまま使うため、オーバーヘッドはほぼありません。しかしMacやWindowsの場合、Dockerを動かすためにLinux VMを内部で立ち上げており、その上でコンテナが動いています。

    特に問題になるのが、ホストマシンとコンテナ間のファイル同期です。開発時には自分のエディタでコードを編集し、それをコンテナ内のアプリケーションで動かすわけですが、このファイル同期処理が意外と重いのです。MacのファイルシステムとLinuxのファイルシステムを橋渡しするために、変換処理が都度走ることになります。

    対処法

    Windows環境の場合、WSL2(Windows Subsystem for Linux 2)を活用し、ソースコードをWSL2側のファイルシステムに置くことで大幅に改善できます。Linux側にファイルを置けば変換処理が不要になるからです。

    Mac環境では、Docker Desktopの設定でCPUやメモリの割り当てを増やす、あるいはボリュームマウント時に「cached」や「delegated」オプションを使うことで、多少の改善が見込めます。

    ただし、これらは根本解決ではありません。本当にパフォーマンスが必要な場面では、Linux環境での開発を検討するのも選択肢のひとつです。

    辛いところ2:ボリュームマウントの権限問題

    「Permission denied」

    Dockerを使っていると、このエラーに何度も遭遇することになります。特にLinux環境でDockerを使う場合、ボリュームマウント時の権限問題は避けて通れません。

    何が起きているのか

    ホストマシンのディレクトリをコンテナにマウントする際、ファイルの所有者情報(UID/GID)がそのまま引き継がれます。問題は、ホストで使っているユーザーとコンテナ内のユーザーのUID/GIDが一致しないケースです。

    たとえば、ホストでUID=1000のユーザーとして作業していて、コンテナ内ではrootユーザー(UID=0)で動作している場合、コンテナ内で作成されたファイルはホスト側から見るとrootの所有物になり、一般ユーザーでは編集できなくなってしまいます。

    興味深いことに、Docker Desktop for MacやWindowsではこの問題が発生しにくいです。内部でうまくハンドリングしてくれているようですが、Linux環境では自分で対処する必要があります。

    対処法

    よく使われる対処法は、docker run時に「-u」オプションでユーザーを指定するか、/etc/passwdと/etc/groupを読み取り専用でマウントしてホストのユーザー情報を共有する方法です。

    Dockerfileでユーザーを作成し、そのユーザーで実行するように設計しておくことも有効です。ただし、環境ごとにUID/GIDが異なる場合は、ビルド時に引数として渡すなどの工夫が必要になります。

    正直なところ、この問題に対する「これだ」という万能解はありません。プロジェクトの状況に応じて最適な方法を選ぶしかないのが現状です。

    辛いところ3:気づいたらディスクが満杯

    「なんかPCの容量がないな…」と思って調べてみたら、Dockerが数十GBを占有していた。そんな経験をした方も多いのではないでしょうか。

    なぜ容量を食うのか

    Dockerはイメージのレイヤー構造を採用しており、効率的に容量を節約できる仕組みになっています。しかし実際の運用では、様々な「ゴミ」が蓄積していきます。

    まず、使われなくなったイメージ(dangling image)です。同じタグで何度もビルドを繰り返すと、古いイメージはタグを失い「none」という状態で残り続けます。次に、停止したままのコンテナ。明示的に削除しない限り、ディスク上にデータが残ります。そしてビルドキャッシュ。ビルド高速化のためにキャッシュされたレイヤーが積み重なっていきます。

    さらにWindowsの場合、やっかいな問題があります。Dockerの仮想ディスク(ext4.vhdx)は自動的に拡張されますが、中身を削除しても自動的には縮小されません。つまり、一度膨らんだ仮想ディスクは手動で圧縮しない限りそのままなのです。

    対処法

    定期的なお掃除が必要です。まずは「docker system df」で現状を確認しましょう。「docker system prune」で未使用のコンテナ、ネットワーク、イメージを一括削除できます。ボリュームも含めて削除したい場合は「–volumes」オプションを付けます。

    Windowsの場合は、さらに仮想ディスクの圧縮も必要です。Docker Desktopを停止した状態で、PowerShellの「Optimize-VHD」コマンドを使って圧縮できます(Hyper-Vが必要)。

    私の経験では、月に一度くらいはお掃除の時間を取るようにしています。自動化したい場合は、CI/CDパイプラインに組み込むか、cronで定期実行するのも手です。

    辛いところ4:ネットワークとポートの罠

    Dockerのネットワーク周りは、初心者にとってかなりの鬼門です。「繋がらない」「意図せず公開されていた」など、トラブルの温床になりがちです。

    よくあるトラブル

    まず、EXPOSEに関する誤解。Dockerfile内でEXPOSEを書いても、それだけではポートは外部に公開されません。単なるドキュメント的な意味しかなく、実際に公開するには「docker run -p」で明示的に指定する必要があります。

    逆に危険なのが、ポートマッピングのデフォルト挙動です。「-p 8080:80」のように指定すると、デフォルトでは全てのインターフェース(0.0.0.0)に対してポートが公開されます。ファイアウォールで閉じているつもりでも、Dockerは独自のiptablesルールを設定するため、ufwなどの設定を迂回してしまいます。

    公衆WiFiに繋いだ状態で開発用コンテナを立ち上げていたら、LAN内の他の端末からアクセスできてしまった…という怖い話も実際にあります。

    対処法

    ローカル開発では「-p 127.0.0.1:8080:80」のように、必ずlocalhostを指定するようにしましょう。docker-compose.ymlでも「ports: “127.0.0.1:8080:80″」と書けます。

    また、依存サービスの起動順序にも注意が必要です。docker-composeのdepends_onは起動順序を制御しますが、サービスが「使える状態になったか」までは保証しません。アプリ起動時にデータベースがまだ準備中…というケースは珍しくありません。dockerizeなどのツールを使って、依存サービスの起動を待つ仕組みを入れることをおすすめします。

    辛いところ5:学習コストが意外と高い

    「Dockerは簡単」という評判を聞いて始めてみたものの、覚えることが多くて挫折しそうになる。これも多くの人が経験することです。

    覚えなければならないこと

    Dockerを使いこなすには、最低限以下の知識が必要になります。

    Dockerの基本概念(イメージ、コンテナ、レイヤー)、Dockerfileの書き方、docker-compose.ymlの記法、ネットワークの仕組み、ボリュームとデータ永続化、そしてLinuxの基礎知識。特にLinuxに馴染みがない方にとっては、コンテナ内でのデバッグ作業がかなりハードルが高く感じるでしょう。

    さらに本番環境での運用を考えると、セキュリティ設定、リソース制限、ログ管理、オーケストレーション(Kubernetes等)といった知識も必要になってきます。

    対処法

    一度に全部を覚えようとしないことが大切です。まずは「docker run」と「docker-compose up」だけで動かせる環境を用意し、実際に使いながら少しずつ理解を深めていきましょう。

    既存のdocker-compose.ymlを読み解くところから始めるのも良いアプローチです。先人が作った設定を眺めながら「なぜこう書いてあるんだろう」と調べていくと、実践的な知識が身につきます。

    辛いところ6:LinuxベースであることのしがらみがLinux以外では制限になる

    DockerはLinuxのカーネル機能をベースにした技術です。この事実が、いくつかの制限を生み出しています。

    異なるOS間の制約

    Linux上でWindowsコンテナを動かすことはできませんし、その逆もできません(Docker Desktop for Windowsでは切り替えは可能ですが、同時には使えません)。これは、コンテナがホストOSのカーネルを共有する仕組みだからです。

    また、仮想マシンと比較すると分離レベルが低いという特性があります。コンテナ同士、あるいはコンテナとホストの間で完全な分離ができているわけではありません。セキュリティ要件が厳しい環境では、この点が問題になることもあります。

    対処法

    マルチOSでのテストが必要な場合は、DockerではなくVMを併用する必要があります。

    セキュリティ面では、信頼できるベースイメージを使う、不要なパッケージを入れない、rootユーザーで実行しないといった基本的な対策を徹底することが重要です。より高い分離が必要な場合は、gVisorやKata Containersといったサンドボックス技術の導入も検討に値します。

    辛いところ7:「私のPCでは動きます」問題の変形版が起きる

    皮肉なことに、Dockerは「環境差異問題を解決する」ために生まれた技術なのに、Docker環境特有の「動かない」問題が新たに発生することがあります。

    Dockerならではの罠

    ローカルでは動くのにCI/CDで失敗する、開発環境では動くのに本番で動かない。原因を調べると、Dockerイメージのバージョン違い、ベースイメージの更新、ビルドキャッシュの有無、ネットワーク設定の違いなどが見つかります。

    また、Dockerを使いこなす知識がないまま複雑な構成を組むと、問題が発生したときの調査が困難になります。「どのコンテナで何が起きているのか」を追うのは、慣れていないとかなり大変です。

    対処法

    イメージのタグは「latest」を避け、明示的にバージョンを固定しましょう。CI/CDとローカルで同じDockerfileを使い、ビルド手順を統一することも重要です。

    そして何より、Dockerの仕組みをしっかり理解することです。ブラックボックスのまま使っていると、いざというときに手も足も出なくなります。

    まとめ:それでもDockerを使うべき理由

    この記事では、Dockerの「辛いところ」を7つ紹介しました。

    Mac・Windowsでの速度問題、権限周りのトラブル、ディスク容量の肥大化、ネットワークの罠、学習コスト、Linux依存の制約、そして新たな「動かない」問題。なかなか手強い課題が並んでいます。

    それでも、私は断言します。Dockerは使うべきです。

    なぜなら、これらの辛さを差し引いても、Dockerがもたらすメリットのほうが圧倒的に大きいからです。環境構築が数分で終わる、チーム全員が同じ環境で開発できる、本番環境との差異がなくなる。20年前の「手順書地獄」を知る身としては、これらの恩恵は何物にも代えがたいものです。

    そして、辛さの多くは「知っていれば対処できる」ものばかりです。この記事を読んだあなたは、すでに多くの落とし穴を知っています。それだけで、かなり有利なスタートを切れるはずです。

    まだDockerを触ったことがない方は、まずDocker Desktopをインストールして、「docker run hello-world」を実行するところから始めてみてください。すでに使っている方は、この記事で紹介した対処法をぜひ試してみてください。

    トラブルに遭遇するたびに少しずつ理解が深まり、気づけばDockerなしでは開発できない体になっているはずです。私がそうであったように。