はじめに
こんにちは、かるめと申します。
Rails 8.1 + Docker Compose環境へRSpecのSystem Specを導入したところ、テスト自体は成功するものの、テスト終了後にwebコンテナまで停止する問題が発生しました。
今回の記事では、この問題を対処した方法について書いていきます。
開発環境
bin/dev + Procfile.devでPumaとTailwind CSS watcherをForemanから起動していました。
- Rails 8.1.3
- Ruby 3.4.10
- Docker
- RSpec
- Capybara
- Selenium
- Chromium / ChromeDriver
- Tailwind CSS v4
- Foreman
services:
web:
build:
context: .
dockerfile: "Dockerfile.dev"
ports:
- "3000:3000"
command: bash -c "rm -f tmp/pids/server.pid && ./bin/dev"
tty: true
stdin_open: true
environment:
TZ: Asia/Tokyo
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- bundle_data:/usr/local/bundle
db:
image: postgres:17
ports:
- "5432:5432"
environment:
TZ: Asia/Tokyo
POSTGRES_PASSWORD: password
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 20s
volumes:
- postgresql_data:/var/lib/postgresql/data
volumes:
bundle_data:
postgresql_data:
web: bin/rails server -b 0.0.0.0 -p 3000
css: bin/rails tailwindcss:watch
発生した問題
System Specを実行するとテスト自体は成功しましたが、実行後にDocker Composeで起動していたwebコンテナまで停止しました。ログを確認すると、Foremanから各プロセスへSIGTERMが送られ、Pumaも終了していました。
web-1 | 09:09:39 | exited with code 0
web-1 | 09:09:39 system | sending SIGTERM to all processes
web-1 | 09:09:39 | exited with code 0
web-1 | 09:09:39 web.1 | - Gracefully stopping, waiting for requests to finish
web-1 | 09:09:39 web.1 | Exiting
web-1 | 09:09:39 web.1 | terminated by SIGTERM
web-1 | 09:09:39 css.1 | exited with code 0
web-1 exited with code 0
最初の対処方法
Docker環境でSystem Spec実行時にコンテナが落ちる事例についてネット検索してみましたが、関連する内容が見当たりませんでした。
そこで、ChatGPTに協力してもらいながら、原因の切り分けを行いました。
すると、
System Specで追加されるブラウザ系プロセスのライフサイクルと、Foremanによるプロセス管理がDockerコンテナ内で干渉しているのではないか
という話になりました。
その後、以下のようにForemanのbin/dev + Procfile.devという既存構成ではなく、PumaとTailwind CSS watcherをDocker Composeの別サービスとして起動する方法で対処しました。
services:
web:
build:
context: .
dockerfile: "Dockerfile.dev"
ports:
- "3000:3000"
- command: bash -c "rm -f tmp/pids/server.pid && ./bin/dev"
+ command: bash -c "rm -f tmp/pids/server.pid && bin/rails server -b 0.0.0.0 -p 3000"
tty: true
stdin_open: true
environment:
TZ: Asia/Tokyo
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- bundle_data:/usr/local/bundle
+ css:
+ build:
+ context: .
+ dockerfile: "Dockerfile.dev"
+ command: bin/rails tailwindcss:watch
+ tty: true
+ stdin_open: true
+ environment:
+ TZ: Asia/Tokyo
+ volumes:
+ - .:/app
+ - bundle_data:/usr/local/bundle
db:
image: postgres:17
ports:
- "5432:5432"
environment:
TZ: Asia/Tokyo
POSTGRES_PASSWORD: password
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
start_period: 20s
volumes:
- postgresql_data:/var/lib/postgresql/data
volumes:
bundle_data:
postgresql_data:
再度System Specを実行すると、webコンテナが停止せずUpのままとなり、問題を解消できました。
追加調査
しかし、割と起こりそうな問題なのにネット検索で出てこなかったことや、既存の構成を変更することが気になり、追加で調査しました。
するとChatGPTが以下のようなtailwindcss-railsのIssueを見つけてくれました。
このIssueでは、Rails + Docker + Foreman + Tailwind CSSという、今回とよく似た構成で問題が報告されていました。
コメントを読むと、compose.yamlにinit: true を追加することで問題を解消できたという報告がありました。
init: trueとは
init: trueを指定すると、コンテナ内でinitプロセスがPID 1として動作します。Docker Composeの公式ドキュメントでは、このinitプロセスはシグナルの転送とプロセスの回収を行うと説明されています。
今回はbin/devからForemanを介してPumaとTailwind CSS watcherという複数のプロセスを起動しているため、コンテナ内のプロセス管理が関係してそうです。
init: trueを追加してみた
実際に先ほどのcompose.yamlの変更(commandの変更とcssサービスの追加)を元に戻して、init: true だけを追加してSystem Specを実行しました。
services:
web:
command: bash -c "rm -f tmp/pids/server.pid && ./bin/dev"
+ init: true
tty: true
stdin_open: true
すると実行後もwebコンテナがUpのままとなっていました。
おわりに
途中で試したwebとcssを別サービスに分ける構成でも問題は解消できました。その際には、PumaとTailwind CSS watcherの責務をサービス単位で分離できるというメリットもあります。
しかし、追加調査後の方法ではinit: trueを1行追加するだけで、既存のbin/dev + Procfile.dev構成を維持したまま問題を解消できました。変更範囲が小さく済むので、今回はこちらの方法を採用しました。
同じ問題が起きた方の参考になれば幸いです!