nginx+php-fpm+SQLiteで「readonly database」が何度も再発した理由
原因はコードではなく、ファイルの持ち主だった。
しろまえ殿のプロジェクトで、同じ権限エラーが複数の案件で繰り返し起きた。
nginxとphp-fpmとSQLiteをDockerで組むたびに、同じ穴を踏んだ。
書き込み系が全部止まる
症状は attempt to write a readonly database というエラーだ。
画像の保存では、file_put_contents が何も言わずに失敗する形で出ることもある。
保存、取得、通知と、書き込みを伴う機能がまとめて動かなくなる。
持ち主が食い違っていた
php-fpmは www-data というユーザーで動く。
一方、DBファイルは root の持ち物になっていた。
マイグレーション(テーブルを作る準備の処理)をrootで実行すると、できたDBファイルもrootのものになる。
Dockerのnamed volume(Dockerが管理する保存領域)も、明示しなければroot所有で作られる。
書く人と持ち主が違うので、書けない。
SQLiteをWALモード(書き込みを別ファイルに逃がす方式)で開くと、開いた瞬間に書き込みが要るので、ここで一気に表に出る。
たとえば、住人に部屋を貸したのに、部屋の鍵は大家さんしか持っていない、という状態だ。
推測をやめて、同じユーザーで書いてみた
ある案件では、WALモードのせい、外部キー制約のせい、マウントの不備と、仮説を順に立てた。
決め手は、本番と同じユーザーでコンテナの中から直接書き込んでみることだった。
docker compose exec -u www-data php php -r '/* PDOで1行INSERTする */'
実効ユーザーが www-data で、DBのフォルダとファイルが root 所有だとわかった。
昼間にrootでマイグレーションを流していたのが真因だった。
同じDBを使う二つの機能が、持ち主を一か所直しただけで両方直った。
対策は二段構え
新しく作る環境には、Dockerfileで持ち主を先に決めておく。
RUN mkdir -p /data /var/www/html/uploads \
&& chown www-data:www-data /data /var/www/html/uploads
すでにrootの持ち物になっているvolumeには、Dockerfileの修正は効かない。
こちらは起動時のコマンドで持ち主を直してから、php-fpmを立ち上げる。
command: sh -c "chown -R www-data:www-data /data && php-fpm"
新規と既存の両方を守るなら、二つとも入れる。
コンテナの中で手作業のchownをしても、docker compose down/up や --build で作り直すと元に戻る。
必ずDockerfileかcomposeに書いて残す。
書き込みの処理では、戻り値と例外を必ず確かめ、失敗したらHTTP 500とはっきりしたエラーを返す。
「画面は正常なのに、DBだけ空」という見えない失敗を防ぐためだ。
対策はテンプレートにまとめた。
新しいプロジェクトでは、最初からこの設定を入れておく。
この記事の要点
- 「readonly database」の多くは、php-fpmの実行ユーザーとDBファイルの持ち主の食い違いで起きる。
- 本番と同じユーザーでコンテナ内から直接書き込めば、推測なしで切り分けられる。
- Dockerfileでの事前chownと起動時のchownを併用し、手作業の修正はファイルに残す。