docker『Cannot Start Service』の原因と対処法

docker『Cannot Start Service』の原因と対処法

Docker/Composeでサービスが起動できず停止・再試行を繰り返す状態。典型原因は「コンテナの実行失敗(ENTRYPOINT/CMD/権限)」「ポート競合」「ボリューム/バインドマウントの不整合」「依存サービス未起動」「リソース不足(メモリ/ディスク/ulimit)」「設定値の不正(環境変数・ネットワーク・デバイス)」など。まずエラーメッセージと直前ログを取り、構成・実行条件・外部要因の順に切り分ける。

最短切り分け(イベント/ログ/設定を同時に確認)

# 直近のエラーとイベント
docker compose ps
docker compose logs --no-color --tail=200 <service>
docker inspect <container_or_image> --format '{{json .State}}' | jq .

# 詳細デバッグ(Compose)
docker compose --verbose up <service>

# 単体で起動→ENTRYPOINT/CMD切り分け
docker compose run --rm --entrypoint sh <service>

コンテナ自体が即終了(ENTRYPOINT/CMD/実行権限/依存ファイル)

# イメージのエントリ/コマンドを確認
docker image inspect <image>:<tag> --format '{{json .Config}}' | jq '.Entrypoint,.Cmd'

# exec形式CMDにする(シグナル伝播/引数処理が安定)
# docker-compose.yml(例)
services:
  app:
    image: node:20-alpine
    command: ["node","server.js"]   # ← exec形式
    # shell形式だと"/bin/sh -c ..."経由

# 実行権限不足で126 → 実行ビット付与して再ビルド
RUN chmod +x /usr/local/bin/start.sh

ポート競合(すでに使用中 / 予約済み)

# 競合確認(Linux/macOS)
lsof -iTCP -sTCP:LISTEN -nP | grep :8080 || true

# Composeのポート定義を見直し
services:
  web:
    image: nginx
    ports:
      - "8080:80"   # ホスト8080が使用中なら他へ変更

ボリューム/バインドマウントの失敗(パス/権限/SELinux)

# 典型:ソースが存在しない・権限不足で起動失敗
services:
  app:
    image: alpine
    volumes:
      - /abs/path/data:/app/data  # ソースは事前に mkdir -p で用意

# SELinuxがある場合は :z/:Z を付ける(RHEL系)
volumes:
  - /abs/path/data:/app/data:Z

依存サービス未起動/未到達(depends_on/ヘルスチェック)

# healthcheck と depends_on のconditionsで待つ
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL","pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10
  app:
    image: my/app:1.0
    depends_on:
      db:
        condition: service_healthy


# 外部依存に対しては起動時スクリプトで待機(例:wait-for-it.sh)
command: ["sh","-lc","./wait-for-it.sh db:5432 -- ./start.sh"]

リソース不足(メモリ/OOM・ディスク/ENOSPC・ulimit)

# OOMKilledの有無
docker inspect <container> --format '{{json .State}}' | jq '.OOMKilled,.ExitCode'

# メモリ/スワップ制限を緩める
services:
  app:
    deploy:
      resources:
        limits:
          memory: 1g

# ulimit不足(ファイル数)を増やす例
services:
  app:
    ulimits:
      nofile:
        soft: 65536
        hard: 65536


# ディスク不足はまず掃除
docker system df -v
docker system prune -af --volumes

環境変数・設定ファイル不足(Exit 1の定番)

# 必須変数をComposeで明示&デフォルト
services:
  app:
    environment:
      - DB_HOST=${DB_HOST?set in .env}
      - DB_USER=${DB_USER:-app}

# 起動前チェック用エントリスクリプト(例)
#!/bin/sh
set -eu
: "${DB_HOST:?required}"
exec /usr/local/bin/app

ネットワーク/名前解決/プロキシ(DNSやHTTP_PROXYの不整合)

# DNSサーバを明示(企業ネットワーク等)
services:
  app:
    dns:
      - 8.8.8.8
      - 1.1.1.1

# プロキシをアプリ/ベースイメージに合わせて設定
environment:
  - HTTP_PROXY=http://proxy:8080
  - NO_PROXY=localhost,127.0.0.1,db

権限・セキュリティ(ユーザー/SELinux/AppArmor/Capability)

# 実行ユーザーを固定し、必要なファイル権限を準備
services:
  app:
    user: "1000:1000"
    volumes:
      - /abs/path/data:/app/data

# 一部機能でcapが必要な場合(最小限で)
cap_add:
  - NET_ADMIN

デバイス/GPU/特権が必要なサービス

# GPUコンテナの例(NVIDIA)
services:
  worker:
    image: nvidia/cuda:12.3.2-runtime-ubuntu22.04
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: ["gpu"]

# デバイスマッピング
devices:
  - /dev/ttyUSB0:/dev/ttyUSB0

Composeの構文・バージョン不整合(未知のフィールド/古いCompose)

# バージョンと実装の差異を回避(compose-spec準拠)
docker compose version
docker compose config   # 展開後の最終形を検証

Docker Desktop/WSL2/macOS/Windowsの特有ポイント

# DesktopのFile Sharingにホストパスを追加(macOS/Windows)
# WSL2は /mnt/c/... か Linux側/home配下のパスを使う
# WSL再起動で改善することも
wsl --shutdown

最小再現テンプレ(「起動そのもの」を検証)

# 同じイメージをシェルで起動 → 依存欠落/権限/PATHを確認
docker run --rm -it --entrypoint sh <image> -lc '
  id && env | sort | sed -n "1,10p"
  which app || exit 127
  test -r /app/config.yml || exit 2
  app --version || exit 1
'

NG→OK 早見表(クイック修正)

# NG: shell形式CMDでシグナル/引数問題 → OK: exec形式
CMD ["node","server.js"]

# NG: ポート競合 → OK: ホスト側ポート変更 or 競合プロセス停止
ports: ["18080:8080"]

# NG: バインド元が存在しない → OK: 事前作成 & SELinuxは :z/:Z
volumes: ["/abs/path/data:/app/data:Z"]

# NG: 依存DB起動待ちなし → OK: healthcheck + depends_on.condition
depends_on: { db: { condition: service_healthy } }

# NG: OOM/ulimit不足 → OK: memory/ulimit を増やす
ulimits: { nofile: { soft: 65536, hard: 65536 } }

# NG: 必須ENV不足 → OK: .env と required記法
environment: ["DB_HOST=${DB_HOST?set}"]

チェックリスト(上から順に潰す)

1) docker compose logs / –verbose で「直前に何が失敗したか」を読む
2) ENTRYPOINT/CMD/実行権限・依存ファイルの有無を確認
3) ポート競合を除去(lsof/netstat)し、portsマッピングを調整
4) バインド/ボリュームの存在・権限・SELinux/AppArmorを確認
5) depends_on + healthcheck で依存起動順を保証
6) メモリ/ディスク/ulimitの不足を解消
7) 環境変数・設定ファイル・プロキシ/DNSの整合を取る
8) Desktop/WSL2/macOS/Windows固有の共有設定・パスを見直す
9) それでも不明なら、同イメージをshで起動して最小再現