【Docker】ファイル権限問題を解決したい
Docker開発を進めているとこのようなファイル権限の問題によく遭遇します。
$ git pull
error: unable to unlink old 'app/data.txt': Permission denied
$ npm install
Error: EACCES: permission denied, open 'package.json'
今回はDocker公式ドキュメントに基づいて、Dockerで頻繁に発生する代表的な問題の一つであるこのような権限問題について、原因から解決策まで解説していきます。
問題の根本原因
なぜこのような権限問題が起こるのか
Docker volumeでは、ファイル権限がホストとコンテナで同一に見えますが、重要なのはUID(ユーザーID)とGID(グループID)のみです。
問題が発生する流れ
- コンテナ内作業:root(UID 0)でファイル作成
- ホスト環境:一般ユーザー(UID 1000)でアクセス試行
- 結果:権限エラー発生
# コンテナ内でファイル作成
docker exec -it myapp touch /app/data/newfile.txt
# ホストで確認
ls -la app/data/
# -rw-r--r-- 1 root root 0 Dec 15 10:00 newfile.txt
# ↑ ↑
# UID 0 GID 0
# ホストユーザー(UID 1000)からは編集不可
問題発生の確認と診断
Step 1: 権限状況の確認
まず、現在の状況を把握しましょう。 ホスト側のユーザー情報とファイルの所有者を確認します。
# 現在のユーザー情報を確認
whoami
id
# 出力例
uid=1000(developer) gid=1000(developer)
# ファイル・ディレクトリの権限確認
ls -la /path/to/volume/
# 出力例
# 権限 リンク 所有者 グループ サイズ 日時 ファイル名
# drwxr-xr-x 2 root root 4096 Dec 15 10:07 app_data
# -rw-r--r-- 1 root root 256 Dec 15 10:07 config.json
上記の出力例を見ると、現在のホストユーザーのUIDは1000ですが、ファイルの所有者は「root root」(UID 0、GID 0)となっています。ここで重要なのは、ホストユーザーのUID(1000)と、ファイルの所有者(root/UID 0)が異なることです。この不一致が権限問題の原因となります。
Step 2: コンテナ内の状況確認
次に、コンテナ内でのユーザー情報も確認してみましょう。
# コンテナ内でのユーザー確認
docker exec -it container_name id
# 出力例
uid=0(root) gid=0(root)
# コンテナ内でのファイル確認
docker exec -it container_name ls -la /app/data/
コンテナ内ではroot(UID 0)で動作しているため、作成されるファイルもroot所有になることが分かります。この違いが権限問題の原因です。
応急処置的な解決策
権限の修正
以下のようなエラーが発生した場合の解決方法です。
# よくあるエラー例
error: unable to unlink old 'file.txt': Permission denied
Error: EACCES: permission denied, open 'package.json'
rm: cannot remove 'app.log': Permission denied
Note 安全性について:この操作はローカル環境のファイル所有者のみを変更し、他の開発メンバーやGitリポジトリには影響しません。Gitはファイルの内容のみを管理し、所有者情報(UID/GID)は追跡しないためです。
# 所有者をホストユーザーに変更
sudo chown -R "$(whoami)":"$(whoami)" /path/to/volume/
# 確認
ls -la /path/to/volume/
# 出力例
# 権限 リンク 所有者 グループ サイズ 日時 ファイル名
# drwxr-xr-x 2 developer developer 4096 Dec 15 10:07 app_data
# -rw-r--r-- 1 developer developer 256 Dec 15 10:07 config.json
根本的な解決策
1. 非rootユーザーでのコンテナ実行(推奨)
Docker公式では、サービスが特権なしで動作可能な場合、USERを使用して非rootユーザーに変更することを推奨しています。ほとんどのWebアプリケーションはroot権限を必要としないため、非rootユーザーで実行することでセキュリティを向上できます。
Dockerfile設計
FROM node:18-alpine
# 非rootユーザー作成
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
# アプリケーションディレクトリの準備
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# ファイル所有者を変更
COPY --chown=appuser:appgroup . .
# 非rootユーザーに切り替え
USER appuser
CMD ["npm", "start"]
2. 動的UID/GID設定
これが現場で最も実用的なアプローチです。 ホストのUID/GIDを環境変数として取得し、docker-compose経由でコンテナに渡します。 これにより、開発者ごとに異なるUID/GIDでも自動的に対応でき、チーム開発での権限問題を防げます。
まず、docker-compose.ymlでUID/GIDを動的に設定します。
docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
args:
UID: ${UID:-1000}
GID: ${GID:-1000}
user: "${UID:-1000}:${GID:-1000}"
volumes:
- ./app:/app
environment:
- NODE_ENV=development
Dockerfile(ARG対応版)
次に、ARGでUID/GIDを受け取るDockerfileを作成します。
FROM node:18-alpine
# ビルド引数でUID/GIDを受け取り
ARG UID=1000
ARG GID=1000
# 動的にユーザー作成
RUN addgroup -g ${GID} appgroup && \
adduser -u ${UID} -G appgroup -s /bin/sh -D appuser
WORKDIR /app
COPY --chown=appuser:appgroup package*.json ./
RUN npm ci
USER appuser
CMD ["npm", "start"]
環境設定(.env)
UID/GIDを固定値で設定する場合は、.envファイルを使用します。
# 現在のユーザーのUID/GIDを設定
UID=1000
GID=1000
実行コマンド
そして、実際の実行方法は以下の通りです。
# 現在のユーザーUID/GIDで実行
UID=$(id -u) GID=$(id -g) docker-compose up
# または.envファイルを使用
echo "UID=$(id -u)" > .env
echo "GID=$(id -g)" >> .env
docker-compose up
3. エントリーポイントスクリプトによる動的調整
Docker公式イメージで実際に使用されているパターンで、より柔軟な権限管理が可能です。 この方法では、コンテナ起動時にスクリプトを実行してUID/GIDを動的に変更し、既存のファイル権限も調整します。最も高度なアプローチですが、複雑な権限要件がある場合に有効です。
まず、権限調整を行うエントリーポイントスクリプトを作成します。
entrypoint.sh
#!/bin/bash
set -e
# 実行時にUID/GIDを調整
if [ -n "$LOCAL_UID" ]; then
usermod -u "$LOCAL_UID" appuser
fi
if [ -n "$LOCAL_GID" ]; then
groupmod -g "$LOCAL_GID" appgroup
fi
# ボリュームの権限を調整
chown -R appuser:appgroup /app/data
# 非rootユーザーでコマンド実行
exec gosu appuser "$@"
Dockerfile
FROM node:18-alpine
# shadowパッケージをインストール(usermod/groupmod使用のため)
RUN apk add --no-cache gosu shadow
# ユーザー作成
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
WORKDIR /app
COPY --chown=appuser:appgroup . .
ENTRYPOINT ["/entrypoint.sh"]
CMD ["npm", "start"]
実行
docker run -e LOCAL_UID=$(id -u) -e LOCAL_GID=$(id -g) myapp
開発チーム向けベストプラクティス
1. チーム統一設定
チーム全体で統一したDocker環境を構築するため、共通設定ファイルを作成します。 docker-compose.override.ymlは自動的に読み込まれるため、開発者ごとの個別設定が可能です。
開発環境用docker-compose.override.yml
version: '3.8'
services:
app:
user: "${UID:-1000}:${GID:-1000}"
volumes:
- .:/app
- node_modules:/app/node_modules # 依存関係は分離
2. 権限チェックスクリプト
権限問題が発生していないかを簡単にチェックできるスクリプトを作成しましょう。 このスクリプトは問題を発見した場合、修正コマンドも提示してくれます。
check-permissions.sh
#!/bin/bash
echo "=== Docker権限チェック ==="
# 現在のユーザー情報
echo "ホストユーザー: $(whoami) (UID: $(id -u), GID: $(id -g))"
# 問題のあるroot所有ファイルをチェック
ROOT_FILES=$(find . -user root -type f 2>/dev/null | head -5)
if [ -n "$ROOT_FILES" ]; then
echo "⚠️ root所有のファイルが見つかりました:"
echo "$ROOT_FILES"
echo ""
echo "修正コマンド:"
echo "sudo chown -R \$(whoami):\$(whoami) ."
else
echo "✅ 権限問題は見つかりませんでした"
fi
3. Makefile統合
よく使うコマンドをMakefileにまとめることで、チームメンバーが簡単にDocker環境を起動できるようになります。
.PHONY: setup check-permissions fix-permissions
setup:
@echo "UID=$(shell id -u)" > .env
@echo "GID=$(shell id -g)" >> .env
@echo "環境設定完了"
check-permissions:
@./scripts/check-permissions.sh
fix-permissions:
sudo chown -R "$(shell whoami)":"$(shell whoami)" .
@echo "権限修正完了"
dev: setup
UID=$(shell id -u) GID=$(shell id -g) docker-compose up
使い方👇
# 環境設定
make setup
# 権限チェック
make check-permissions
# 権限修正
make fix-permissions
# 開発環境起動
make dev
セキュリティ考慮事項
1. 最小権限の原則
コンテナをnon-rootユーザーで実行することで、container→host特権昇格のリスクを大幅に軽減できます。
# rootで実行、権限が緩すぎる(セキュリティリスク)
USER root # rootで実行(危険)
RUN chmod 777 /app/data # 全員がファイル変更可能(危険)
# 一般ユーザーで実行、最小権限(おすすめ)
USER appuser # 一般ユーザーで実行(安全)
RUN chmod 755 /app/data # 必要最小限の権限のみ(安全)
rootユーザーでの実行は、コンテナが侵害された際にホストシステムへの影響が大きくなります。 また、777の権限設定は誰でもファイルを変更できるため、セキュリティリスクが高まります。
2. UID範囲の考慮
UID 10000未満はセキュリティリスクとなる可能性があるため、本番環境では高いUID値を使用すると安全です。低いUID値はシステムユーザーと重複する可能性があり、意図しない管理者権限を持ってしまう場合があります。
# 本番環境推奨設定
ARG UID=10000
ARG GID=10001
3. gosu使用時の注意点
gosuは主にコンテナの起動時(ENTRYPOINTでの使用)に設計されています。コンテナの初期化処理でrootから一般ユーザーに権限を下げる際に使用するのが本来の目的です。それ以外の用途での使用は、潜在的なセキュリティ脆弱性を引き起こす可能性があります。適切なコンテナ環境以外での使用は避けるのが良いでしょう。
Note gosuとは gosuは、コンテナ内でユーザーを切り替えるためのツールです。sudoに似ていますが、コンテナ環境に特化して設計されており、コンテナの停止や再起動がより正常に動作します。
予防策とチーム運用
1. 開発ワークフロー統合
Git pullの後に権限問題を自動検出するフックスクリプトを設定できます。
Git hooks設定(.git/hooks/post-merge)
#!/bin/bash
# pull後の権限チェック
if [ -d "app_data" ] && [ "$(stat -c %U app_data)" = "root" ]; then
echo "⚠️ 権限問題を検出しました"
echo "実行してください: sudo chown -R \$(whoami):\$(whoami) app_data"
fi
スクリプト作成後、以下のコマンドで実行権限を付与します。
chmod +x .git/hooks/post-merge
2. CI/CD設定
CI/CD環境でも権限問題を防ぐための設定例です。
GitHub Actions例
name: Docker Build Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up environment
run: |
echo "UID=$(id -u)" >> .env
echo "GID=$(id -g)" >> .env
- name: Build and test
run: |
UID=$(id -u) GID=$(id -g) docker-compose build
UID=$(id -u) GID=$(id -g) docker-compose run --rm app npm test
- name: Check permissions
run: |
# 権限問題がないことを確認
find . -user root -type f | grep -v ".git" && exit 1 || true
まとめ
Docker環境での権限問題は、多くの方々が経験する悩みの一つだと思います。 しかし適切に理解して対策をすれば、解決・予防することは可能です。
アプローチまとめ
- 一時的な解決:
sudo chown -R "$(whoami)":"$(whoami)"
で権限修正 - 環境変数での対応:
.env
ファイルでUID/GID設定 - 根本的な解決: 非rootユーザー実行 + 動的UID/GID設定
- チーム全体: 統一された開発環境設定
ポイント
- UID/GIDのみが権限決定に重要
- 非rootユーザー実行が公式推奨
- ローカルの権限修正は他のメンバーに影響しない
- 予防策の導入でチーム全体の開発効率向上
この記事が同じような問題に悩む方々のヒントになれば幸いです。 皆さんのDocker開発がより安全でたのしく効率的になることを願っています🐳