Dockerボリュームマウント時のroot権限問題と解決策
docker 10 min read

Dockerボリュームマウント時のroot権限問題と解決策

avatar-m-1

karrinn

著者

Docker Composeで開発環境を構築したら、.venvnode_modulesがroot所有になってホストから削除できない...そんな経験ありませんか?
原因はDockerコンテナがデフォルトでrootとして実行されるからです。今回は非rootユーザーで実行する方法を解説します。

KEY TAKEAWAYS

この記事でわかること

  • ボリュームマウントでファイルがroot所有になる原因
  • Python/Node.jsのDockerfileで非rootユーザーを設定する方法
  • ビルド時間を短縮する最適化テクニック

用語の定義

TERM 01

UID / GID

Linuxでユーザーとグループを識別する数値ID。一般ユーザーは通常UID 1000から始まります。Dockerコンテナ内のUID/GIDとホストのUID/GIDが一致すると、ファイル所有権も一致します。

TERM 02

USER命令

Dockerfileでコンテナ実行時のユーザーを指定する命令です。USER 1000のように数値で指定するか、USER appuserのようにユーザー名で指定します。

TERM 03

ボリュームマウント

ホストのディレクトリをコンテナ内にマウントする機能です。./backend:/appのように指定すると、ホストのファイルがコンテナから読み書きできます。

TERM 04

COPY --chown

ファイルをコピーする際に所有者を指定するオプションです。COPY --chown=1000:1000 . .のように使い、非rootユーザー所有でファイルをコピーします。

Source: Understanding the Docker USER Instruction - Docker Blog

何が起きたのか

Docker Composeで開発環境を動かしていたら、こんなエラーに遭遇しました。

ホストから.venvを操作できない

コンテナでuv syncを実行すると.venvがroot所有で作成されます。ホスト側のUID 1000ユーザーからは書き込み権限がないため、削除も再作成もできません。

  • rm -rf .venv: Permission denied
  • uv run manage.py: failed to remove directory
  • DBファイルも同様に書き込みエラー
Terminal
$ ls -la backend/.venv/
drwxr-xr-x 4 root root 4096 ...

$ uv run manage.py migrate
error: failed to remove directory `.venv/lib`: Permission denied (os error 13)

$ rm -rf .venv
rm: '.venv/pyvenv.cfg' を削除できません: 許可がありません

$ sqlite3 backend/db.sqlite3
[ERROR] Failed to register: attempt to write a readonly database

なぜroot所有になるのか

原因はDockerコンテナがデフォルトでroot(UID 0)として実行されることです。

権限問題が発生する流れ

  1. docker-compose.yamlでホストディレクトリをマウント
    volumes: ["./backend:/app"]
  2. DockerfileでUSERを指定しない場合、コンテナはrootで実行
    → コンテナ内の全ての操作がroot(UID 0)として行われる
  3. コンテナ内でuv syncを実行 → .venvがroot所有で作成
    → Linuxのバインドマウントはホスト側にもそのまま反映
  4. ホストユーザー(UID 1000)からは書き込み権限がない
    → 削除も再作成もできない状態に

ポイント: Dockerはファイルのuid/gidマッピングを行いません。コンテナ内でroot(UID 0)が作成したファイルは、ホスト側でもUID 0として見えます。

問題のある設定(変更前)

USER命令がないため、全ての操作がrootで実行されます。uv syncで作成される.venvもroot所有になります。

backend/Dockerfile(変更前)
FROM python:3.11-slim

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

WORKDIR /app

COPY pyproject.toml .
RUN uv sync --no-dev

COPY . .

EXPOSE 8000

CMD ["sh", "-c", "uv run --no-dev python manage.py migrate && uv run --no-dev python manage.py runserver 0.0.0.0:8000"]

Source: Permission denied on accessing host directory in Docker - Stack Overflow

解決策: 非rootユーザーでコンテナを実行

解決策はホストユーザーと同じUID/GIDのユーザーをコンテナ内に作成し、そのユーザーで実行することです。

Python (uv) のDockerfile

変更のポイント

  • ARG UID=1000: ホストユーザーと同じUIDを指定
  • useradd -m: ホームディレクトリ付きでユーザー作成
  • chown /app: 作業ディレクトリの所有者を変更
  • USER ${UID}: 依存関係インストール前にユーザー切り替え
  • COPY --chown: ファイルを非rootユーザー所有でコピー
backend/Dockerfile
FROM python:3.11-slim

COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

ARG UID=1000
ARG GID=1000
RUN groupadd -g ${GID} appuser 2>/dev/null || true && \
useradd -m -u ${UID} -g ${GID} appuser 2>/dev/null || \
useradd -m -u ${UID} appuser 2>/dev/null || true

WORKDIR /app
RUN chown ${UID}:${GID} /app

USER ${UID}

COPY --chown=${UID}:${GID} pyproject.toml .
RUN uv sync --no-dev

COPY --chown=${UID}:${GID} . .

EXPOSE 8000
CMD ["sh", "-c", "uv run --no-dev python manage.py migrate && uv run --no-dev python manage.py runserver 0.0.0.0:8000"]

Node.js のDockerfile

node:22-slimの特徴

node:22-slimイメージには既にUID 1000のnodeユーザーが存在します。新しくユーザーを作成する必要はありません。

  • 既存のnodeユーザー(UID 1000)を使用
  • npm ciの前にUSER nodeで切り替え
  • node_modulesが最初からnode所有で作成される
frontend/Dockerfile
FROM node:22-slim

WORKDIR /app
RUN chown node:node /app

USER node

COPY --chown=node:node package*.json ./
RUN npm ci

COPY --chown=node:node . .

EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

確認コマンド:docker run --rm node:22-slim id nodeuid=1000(node) gid=1000(node) groups=1000(node)

なぜUID 1000なのか

Linuxでは最初に作成される一般ユーザーのUIDは通常1000です。コンテナ内のユーザーUIDとホストのUIDが一致すれば、ファイル所有権も一致します。USER 1000で実行すると、作成されるファイルもUID 1000所有になり、ホスト側のユーザーから操作できます。

Source: Understanding the Docker USER Instruction - Docker Blog

追加の問題: AWS認証情報が読めなくなる

非rootユーザーに切り替えた後、別の問題が発生することがあります。

AWSプロファイルが見つからない

原因

docker-compose.yamlでAWS認証情報を/root/.awsにマウントしていた場合、コンテナ内ユーザーがappuserになると参照されません。

  • root前提のパス: /root/.aws
  • appuserのホーム: /home/appuser
エラーメッセージ
Error: Internal server error: The config profile (default) could not be found

解決策

マウント先をappuserのホームディレクトリに変更します。HOME環境変数も明示的に設定しておくと安心です。

docker-compose.yaml
services:
backend:
volumes:
- ~/.aws:/home/appuser/.aws:ro
environment:
- HOME=/home/appuser

ビルド時間の最適化

非rootユーザー対応でビルドが遅くなった?それはchown -Rの順番が原因かもしれません。

遅いパターン vs 速いパターン

パターンやり方chown対象ビルド時間
遅いパターンuv syncchown -R.venv内の数万ファイル+60秒以上
速いパターンUSERuv sync空の/appディレクトリのみ一瞬

最適化のポイント

依存関係インストール前にUSERを切り替えることで、.venvnode_modulesが最初から非rootユーザー所有で作成されます。後からchownする必要がありません。

応急処置: 既に権限が壊れている場合

今すぐ動かしたい場合の応急処置です。

root所有ファイルを修正

sudo chownでホストユーザー所有に戻します。その後、Dockerfileを修正して再ビルドすれば、今後は問題が発生しません。

  • コンテナを停止
  • 権限を修正(sudo必要)
  • Dockerfile修正後に再ビルド
Terminal
# コンテナを停止
docker compose down

# 権限を修正
sudo chown -R $(whoami):$(whoami) backend/.venv backend/db.sqlite3

# Dockerfileを修正後、再ビルド
docker compose build --no-cache

よくある質問

Docker DesktopがUID/GIDマッピングを自動的に行っているためです。Linux上のDocker CEではバインドマウントがそのまま反映されるため、この問題が発生します。開発環境がLinuxの場合は対策が必要です。

docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g)でホストのUID/GIDを渡せます。チーム開発の場合は、全員のUIDを統一するか、エントリーポイントスクリプトで動的に対応する方法もあります。

はい、セキュリティ上必須です。Docker公式も非rootユーザーでの実行を推奨しています。本番環境ではUID 10000以上を使うことで、より安全になります。権限昇格攻撃を防ぐための基本的な対策です。

検証環境

この記事の内容は以下の環境で検証しています。

環境情報
OS: Ubuntu 24.04.3 LTS x86_64
Docker: 29.1.1
Docker Compose: v2.40.3

参考リンク

関連トピック

コメント (0)

まだコメントはありません。最初のコメントを残しませんか?

コメントを投稿

メールアドレスが公開されることはありません。必須項目には * が付いています