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を併用し、手作業の修正はファイルに残す。