Docker Composeでバックエンドコンテナがunhealthyに?原因は意外な「あのコマンド」の不在だった!【生成AI執筆】
Docker Composeを使って開発環境を構築していると、時々コンテナが期待通りに起動せず、「unhealthy(不健康)」と表示されてしまうことがあります。特にバックエンドサービスがデータベースなどの他のサービスに依存している場合、この問題は連鎖的に他のコンテナの起動失敗を引き起こし、頭を抱える原因となりがちです。
先日、まさにこの「unhealthy」問題に遭遇し、試行錯誤の末に解決しました。今回はその経緯と解決策を、同じように困っている方々の助けになればと思い、記事にまとめます。
問題発生:バックエンドコンテナが起動しない!
開発中のプロジェクトで docker compose up -d
を実行したところ、以下のような出力と共にバックエンドコンテナの起動に失敗しました。
manntera@DESKTOP-5GE6CLF:~/works/product/enbis$ docker compose up -d
[+] Running 4/4
✔ Network enbis_default Created 0.3s
✔ Container enbis-db-1 Healthy 6.3s
✘ Container enbis-backend-1 Error 57.8s
✔ Container enbis-frontend-1 Created 0.1s
dependency failed to start: container enbis-backend-1 is unhealthy
enbis-backend-1
コンテナが Error
となり、最終的に unhealthy
と判断されています。そして、このバックエンドに依存している enbis-frontend-1
コンテナも起動できていません(Created
のまま)。
私の docker-compose.yml
における backend
サービスの設定は以下のようになっており、一般的なヘルスチェック (curl
を使用) を設定していました。
# docker-compose.yml (抜粋)
services:
# ... (dbサービスなど)
backend:
build:
context: ./backend
dockerfile: Dockerfile
# ... (environment, ports, depends_onなど)
restart: always
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:5555/health" ] # ← 問題のヘルスチェック
interval: 10s
timeout: 5s
retries: 3
start_period: 30s # 当初の設定
frontend:
# ...
depends_on:
backend:
condition: service_healthy # backendがhealthyになるのを待つ
# ...
バックエンドアプリケーション(Node.js + Hono)のログを確認すると (docker compose logs backend
)、一見サーバーは正常に起動しているように見えました。
backend-1 | > start
backend-1 | > node dist/index.cjs
backend-1 |
backend-1 | Server is running on port 5555
さらにややこしいことに、ホストOSのブラウザから http://localhost:5555/health
にアクセスすると、ちゃんと "OK" という文字が返ってきていたのです。
原因調査の道のり
「サーバーは動いてるっぽいのに、なぜ unhealthy なんだ…?」と、ここから原因調査が始まりました。
ステップ1:ヘルスチェック用エンドポイントのログ確認
バックエンドの /health
エンドポイントには、アクセスがあった際にコンソールにログを出すようにしていました。
// backend/src/entry_point.ts (抜粋)
app.get('/health', (c) => {
console.log('Health check endpoint accessed'); // このログが出るはず!
return c.text('OK', 200);
});
しかし、docker compose logs backend
の出力を見ても、この Health check endpoint accessed
というログは一向に現れませんでした。これは、ヘルスチェックの curl
コマンドがアプリケーションの該当エンドポイントに到達していない可能性を示唆していました。
ステップ2:start_period
の調整
アプリケーションの起動やマイグレーションに時間がかかり、ヘルスチェックが始まる前にタイムアウトしている可能性を考えました。そこで、docker-compose.yml
の healthcheck
にある start_period
(ヘルスチェックの失敗を許容する猶予期間)を 30s
から 60s
、さらには 120s
へと延ばしてみましたが、状況は変わりませんでした。
ステップ3:コンテナ内部での直接確認(これが決め手に!)
いよいよコンテナ内部で何が起きているのかを直接確認することにしました。
- まず、
docker-compose.yml
のbackend
サービスのhealthcheck
セクションを一時的にコメントアウトし、コンテナがunhealthy
で停止しないようにしました。 -
docker compose down && docker compose up -d --build backend
でバックエンドコンテナだけを再起動。 -
docker compose exec backend sh
コマンドで、起動したコンテナのシェルに入りました。
そして、コンテナ内部でヘルスチェックに使われている curl
コマンドの状態を確認してみると…
# (コンテナ内部のシェルで)
# curl --version
sh: 4: curl: not found
「sh: 4: curl: not found」
なんと、コンテナ内に curl
コマンドが存在していなかったのです!
curl
がコンテナイメージになかった
原因判明:ヘルスチェックに必要な 私のバックエンドコンテナは node:slim
という軽量なNode.jsイメージをベースにしていました。この slim
イメージは、容量を小さくするために多くの共通ツール(curl
も含む)がデフォルトではインストールされていません。
ヘルスチェックに curl
を指定していたものの、その実行ファイルが存在しないため、ヘルスチェックコマンド自体が失敗し、Dockerはコンテナを unhealthy
と判断していたのです。ホストOSのブラウザからアクセスできたのは、ポートフォワーディングは正常で、コンテナ内のアプリケーション自体は(curl
がなくても)起動していたためでした。
curl
のインストールを追加
解決策:Dockerfileに 原因が判明すれば解決は簡単です。backend/Dockerfile
に curl
をインストールするコマンドを追加しました。
# backend/Dockerfile
# (前略: ビルダーステージなど)
FROM node:slim@sha256:dfb18d8011c0b3a112214a32e772d9c6752131ffee512e974e59367e46fcee52
WORKDIR /app
ENV NODE_ENV=production \
PORT=5555
# ======== ↓↓↓ ここを追加! ========
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# ======== ↑↑↑ ここまで ========
COPY /app/node_modules ./node_modules
# (中略: 他のCOPY命令)
COPY package.json package-lock.json* ./
CMD ["sh", "-c", "npm run migrations:push && npm start"]
EXPOSE 5555
そして、docker-compose.yml
の healthcheck
設定のコメントアウトを解除し、再度イメージをビルドしてコンテナを起動しました。
docker compose down
docker compose up -d --build
すると…
manntera@DESKTOP-5GE6CLF:~/works/product/enbis$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
enbis-backend-1 enbis-backend "sh -c 'npm run mig…" backend About a minute ago Up About a minute (healthy) 0.0.0.0:5555->5555/tcp
enbis-db-1 postgres:15 "docker-entrypoint.s…" db About a minute ago Up About a minute (healthy) 0.0.0.0:5432->5432/tcp
enbis-frontend-1 enbis-frontend "/docker-entrypoint.…" frontend About a minute ago Up About a minute 0.0.0.0:3000->80/tcp
ついに enbis-backend-1
が healthy
に! そして、それに依存する enbis-frontend-1
も無事に起動しました。
まとめと教訓
今回の件から得られた教訓は以下の通りです。
unhealthy
の原因は様々だが、ヘルスチェックコマンド自体がコンテナ内に存在するかは基本中の基本。-
xxx:slim
やxxx:alpine
のような軽量イメージを使う際は、必要なツールがデフォルトで含まれていない可能性を常に意識する。 (curl
だけでなく、wget
,bash
,git
なども同様です) - 問題の切り分けとして、コンテナ内部に入って手動でコマンドを実行してみるのは非常に有効な手段。 ログだけでは見えない問題を発見できます。
- ホストOSからアクセスできることと、コンテナ内部から(特に
localhost
や他のサービスへ)アクセスできることは、必ずしもイコールではない。
もしあなたがDocker Composeでコンテナの unhealthy
問題に直面したら、アプリケーションのバグやリソース不足を疑う前に、まずはヘルスチェックに使っているコマンドがコンテナ内にちゃんと存在するかを確認してみてください。意外とあっさり解決するかもしれません。
この記事が、同じような問題で悩む誰かの一助となれば幸いです。
Discussion