こうこく
作 ▸

DockerでSQLiteのDBファイルをマウントすると書き込み時に「attempt to write a readonly database」エラー

SQLiteのDBファイルのあるディレクトリをコンテナにマウントして、コンテナから当該ファイルへアクセスさせた際に出る attempt to write a readonly database エラーについて。

安全なコンテナイメージを作る時、コンテナ内の実行ユーザーは非rootにするのがよいと言われているが、非rootユーザーだと前述のエラーに遭遇する。対処方法を改めて確認してみた。

Docker 24.0.7

結果から

  • コンテナ側の実行ユーザーがrootである場合、読み書きとも制限なく行える。
  • コンテナ側の実行ユーザーが非rootである場合、ホスト側でDBファイルとそのディレクトリのパーミッションを緩めるか、DBファイルの所有者をコンテナ側の実行ユーザーに合わせなければ書き込みは行えない。(行えない場合に attempt to write a readonly database エラーが出る)

以下、確認方法。

準備

あらかじめデータの入った SQLite データベースファイルを用意し、特定のディレクトリ (=コンテナにマウントするディレクトリ) に入れておく。

以下コマンドでディレクトリ作成 -> データベースファイルを作成&開き、初期データ投入。

mkdir data
sqlite3 ./data/db.sqlite
初期データ
CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL
);
INSERT INTO users (name, email) VALUES ('キリウ君', 'kiriu-kun@example.com');

完了したら対話型インターフェースを .exit で抜ける。

root ユーザーの場合

以下の Dockerfile を作成する。これはコマンドラインから sqlite3 を使えて、かつ落ちずに起動し続けるだけのコンテナ。

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y sqlite3
CMD ["tail", "-f", "/dev/null"]

この Dockerfile があるディレクトリで、以下コマンドでコンテナイメージをビルド。

docker build -t sqlite-with-root .

そして、ホスト側のDBファイルがあるディレクトリ ./data をコンテナ側にマウントしながら起動。

docker run -d --rm -v "./data:/var/lib/data" --name sqlite-with-root sqlite-with-root

ちなみに、同じ内容で compose.yaml を作る場合は以下の通り。

compose.yaml (※ここでは使いません)
services:
  sqlite-with-root:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./root/data:/var/lib/data

コンテナが起動したら、 docker exec で中に入ってDBファイルを読み書きしてみる。

ホスト側
docker exec -it sqlite-with-root sh
コンテナ側 (root)
sqlite3 /var/lib/data/db.sqlite "INSERT INTO users (name, email) VALUES ('ユコちゃん', 'yuko-chan@example.com')"
sqlite3 /var/lib/data/db.sqlite "SELECT * FROM users"
コンテナ側結果
1|キリウ君|kiriu-kun@example.com
2|ユコちゃん|yuko-chan@example.com

INSERT SELECT ともエラーにはならず、追加したレコードが表示される。

なお、このときコンテナ側から見たDBファイルおよびそのディレクトリのパーミッションは、以下の通りになっている。

コンテナ側
# ls /var/lib/data -la
total 20
drwxr-xr-x 2 1000 1000  4096 Mar 30 20:03 .
drwxr-xr-x 1 root root  4096 Mar 30 20:03 ..
-rw-r--r-- 1 1000 1000 12288 Mar 30 20:03 db.sqlite

この uid/gid 1000 は、ホスト側でこの作業を行っているユーザーのものである。コンテナ内に該当するユーザーがいないため、uid/gid がそのまま表示されている。

非 root ユーザーの場合

今度は以下の Dockerfile を作成する。こちらはアプリ用のユーザー appuser (uid/gid 1099) を作成し、コンテナ内の実行ユーザーもそれになるようにしてある。

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y sqlite3

# Add a non-root user
RUN groupadd -g 1099 appuser && \
    useradd -r -u 1099 -g appuser appuser
USER appuser

CMD ["tail", "-f", "/dev/null"]

同様にコンテナイメージをビルドして実行。

docker build -t sqlite-with-non-root .
docker run -d --rm -v "./data:/var/lib/data" --name sqlite-with-non-root sqlite-with-non-root

同様に、起動したコンテナに docker exec で入ってDBファイルを読み書き。

ホスト側
docker exec -it sqlite-with-non-root sh
コンテナ側 (appuser)
sqlite3 /var/lib/data/db.sqlite "INSERT INTO users (name, email) VALUES ('ユコちゃん', 'yuko-chan@example.com')"
コンテナ側結果
Error: stepping, attempt to write a readonly database (8)

INSERT 時にエラーとなった。ただし下記のように、 SELECT はエラーとならず成功した。

コンテナ側 (appuser)
sqlite3 /var/lib/data/db.sqlite "SELECT * FROM users"
コンテナ側結果
1|キリウ君|kiriu-kun@example.com

ファイルとディレクトリのパーミッションを変えると OK

前々項で書いたように、コンテナ側から見たDBファイルおよびそのディレクトリは、所有者以外に書き込みができないパーミッションとなっている。

コンテナ側
$ ls /var/lib/data -la
total 20
drwxr-xr-x 2 1000 1000  4096 Mar 30 20:01 .
drwxr-xr-x 1 root root  4096 Mar 30 20:04 ..
-rw-r--r-- 1 1000 1000 12288 Mar 30 20:01 db.sqlite

そこで、ホスト側でこのファイルおよびディレクトリのパーミッションを 777 に変更してみる。(※ディレクトリも変更しないと最初と同じエラーが出てダメ。)

ホスト側
chmod 777 -R ./data

以下がコンテナ側から再度確認したパーミッション。所有者以外も書き込めるようになっている。

コンテナ側
$ ls /var/lib/data -l
total 20
drwxrwxrwx 2 1000 1000  4096 Mar 30 20:01 .
drwxr-xr-x 1 root root  4096 Mar 30 20:04 ..
-rwxrwxrwx 1 1000 1000 12288 Mar 30 20:01 db.sqlite

この状態で再度書き込みを行ってみると、今度は成功する。

コンテナ側 (appuser)
sqlite3 /var/lib/data/db.sqlite "INSERT INTO users (name, email) VALUES ('ユコちゃん', 'yuko-chan@example.com')"
sqlite3 /var/lib/data/db.sqlite "SELECT * FROM users"
コンテナ側結果
1|キリウ君|kiriu-kun@example.com
2|ユコちゃん|yuko-chan@example.com

次項に進む前に、本項で変更した DB ファイルとそのディレクトリのパーミッションを元に戻しておく。(ここでは単にホスト側からディレクトリごと削除 -> 再作成を行った。また、マウントしたディレクトリを消してしまうとコンテナ側から認識されなくなるので、併せてコンテナの再起動も行った。)

ファイルとディレクトリの所有者をコンテナ側の実行ユーザーに変えると OK

パーミッションを緩めたくない場合、ファイルとディレクトリの所有者をコンテナ側の実行ユーザーに変更することでも、同様にDBファイルへの書き込みを許可できる。

まず、ホスト側にコンテナ側と同じ uid/gid のユーザーを作成する。

ホスト側
sudo groupadd -g 1099 appuser1
sudo useradd -r -u 1099 -g appuser1 appuser1

uid/gid が合ってさえいればよくて、名称は違っていてもOK。また、ホームディレクトリはいらないので useradd にオプション -r をつけている。

そしたら、ホスト側でDBファイルとそのディレクトリの所有者をユーザー appuser1 に変更する。(※パーミッションの場合と同様に、ディレクトリも変更しないと当初のエラーが出てダメ。)

ホスト側
sudo chown appuser1:appuser1 -R ./data

以下がコンテナ側から再度確認したパーミッション。uid/gid 通りにコンテナ側の名称が所有者として表示されている。

コンテナ側
$ ls /var/lib/data -la
total 20
drwxr-xr-x 2 appuser appuser  4096 Mar 30 20:20 .
drwxr-xr-x 1 root    root     4096 Mar 30 20:20 ..
-rw-r--r-- 1 appuser appuser 12288 Mar 30 20:20 db.sqlite

この状態で再度書き込みを行ってみると、パーミッションを 777 に変更した場合と同様に成功する。

コンテナ側 (appuser)
sqlite3 /var/lib/data/db.sqlite "INSERT INTO users (name, email) VALUES ('ユコちゃん', 'yuko-chan@example.com')"
sqlite3 /var/lib/data/db.sqlite "SELECT * FROM users"
コンテナ側結果
1|キリウ君|kiriu-kun@example.com
2|ユコちゃん|yuko-chan@example.com

以上。

この記事に何かあればこちらまで (非公開)