Permission denied(can't create ...: Permission denied)は、コンテナの中で動くユーザーの UID が、ホストのフォルダーに書き込める UID と違うときに起きます。 bind mount(-v ./data:/app/data)では、ホストのフォルダーの持ち主・権限が、そのままコンテナの中に見えます。コンテナの中のユーザー名ではなく、数字の UID で判定されます。

状況 直し方
イメージのユーザー(例:UID 10001)が、ホストのフォルダー(UID 1001)に書けない --user "$(id -u):$(id -g)" で、ホストと同じ UID で動かす
毎回同じ UID で使う(開発用のイメージ) Dockerfile で UID を引数にしてユーザーを作る
ホストに無いフォルダーを -v で指定した Docker が root の持ち主で作るので、先に自分で mkdir
逆に、ホストでファイルを編集・削除できない コンテナが root で書いた。--user で動かす

確認環境:Docker Engine 29.4(Linux)、Docker Compose v5.1.3。ホストのユーザーは UID 1001・GID 1002(確認日 2026年10月11日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Docker bind mountのPermission deniedの直し方」

再現

FROM (ベースのイメージ)
RUN useradd -u 10001 app && mkdir -p /app/data && chown app:app /app/data
USER app
WORKDIR /app
CMD ["sh", "-c", "id && echo hello > /app/data/out.txt && echo written"]
$ mkdir data
$ docker run --rm -v "$PWD/data:/app/data" app11:1
uid=10001(app) gid=10001(app) groups=10001(app)
sh: can't create /app/data/out.txt: Permission denied

Dockerfile で /app/data を app の持ち主にしていても、bind mount すると、ホストの data フォルダーに置き換わります。

まず確認:ホストとコンテナの UID

$ id                                   ← ホストのユーザー
uid=1001(dev) gid=1002(dev)

$ ls -lnd data                         ← ホストのフォルダー(-n で数字の UID)
drwxrwxr-x 2 1001 1002 4096 ... data

$ docker run --rm -v "$PWD/data:/app/data" app11:1 ls -lnd /app/data     ← コンテナから見たフォルダー
drwxrwxr-x    2 1001     1002          4096 ... /app/data
確認すること 値
コンテナで動くユーザー(id) UID 10001
フォルダーの持ち主(ls -ln) UID 1001、権限 rwxrwxr-x(その他の人は書けない)

コンテナの中でも、フォルダーの持ち主はホストと同じ 1001 のままです。UID 10001 は「その他の人」なので書けません。ls -l(-n なし)だと、コンテナの中では別の名前や数字に見えて分かりにくいので、ls -ln で数字を比べます。

直し方1:--user でホストと同じ UID で動かす

docker run --rm --user "$(id -u):$(id -g)" -v "$PWD/data:/app/data" app11:1
uid=1001 gid=1002 groups=1002
written

$ ls -ln data
-rw-r--r-- 1 1001 1002 6 ... out.txt      ← ホストの自分の持ち主で作られた

書き込めて、作られたファイルもホストの自分の持ち主になりました。 ホストでそのまま編集・削除できます。

注意 確認環境での結果
コンテナの中に、その UID のユーザーが無い whoami: unknown uid 1001、HOME=/

ユーザー名やホームフォルダーを使うアプリ(ホームにキャッシュを書くツールなど)は、ここで別のエラーになることがあります。その場合は直し方2にします。

直し方2:Dockerfile で UID を合わせる

ARG UID=1000
ARG GID=1000
RUN groupadd -g "$GID" app && useradd -u "$UID" -g "$GID" app \
 && mkdir -p /app/data && chown app:app /app/data
USER app
docker build -t app11:uid --build-arg UID="$(id -u)" --build-arg GID="$(id -g)" .
docker run --rm -v "$PWD/data:/app/data" app11:uid
uid=1001(app) gid=1002(app) groups=1002(app)
written

ビルドのときにホストの UID を渡すと、名前のあるユーザーとして、同じ UID で動きました。 開発用のイメージに向いています(本番用のイメージに、開発する人の UID を埋め込むのは避けます)。ベースのイメージによって、ユーザーを作るコマンド(useradd・adduser)の書き方が違います。

docker compose の落とし穴:UID が空だと root で動く

services:
  app:
    image: app11:1
    user: "${UID}:${GID}"
    volumes:
      - ./data:/app/data
$ docker compose run --rm app
WARN The "UID" variable is not set. Defaulting to a blank string.
WARN The "GID" variable is not set. Defaulting to a blank string.
uid=0(root) gid=0(root) groups=0(root)       ← root で動いた
written
$ ls -ln data
-rw-r--r-- 1 0 0 6 ... out.txt                ← root の持ち主のファイル

UID は bash では環境変数として渡されないため、compose から見ると空でした。 user: ":" になり、イメージの USER app も無視して root で動きました。エラーにならず書き込めてしまうので、気づきにくい形です。さらに、bash では UID は読み取り専用の変数なので、UID=$(id -u) docker compose run ... と書いても UID: readonly variable で設定できませんでした。

.env ファイルに書くと、確認環境では正しく動きました。

printf "UID=%s\nGID=%s\n" "$(id -u)" "$(id -g)" > .env
docker compose run --rm app
uid=1001 gid=1002 groups=1002
written

.env を使わないなら、HOST_UID のように別の名前の変数にして、export HOST_UID=$(id -u) で渡す方法もあります。

ホストに無いフォルダーを -v で指定したとき

$ docker run --rm -v "$PWD/nohost:/app/data" app11:1
sh: can't create /app/data/out.txt: Permission denied

$ ls -lnd nohost
drwxr-xr-x 2 0 0 4096 ... nohost          ← root の持ち主で作られた

-v は、ホストに無いフォルダーを Docker が自動で作ります(Docker 公式ドキュメント)。Docker のデーモンが root で動いているため、root の持ち主のフォルダーになり、コンテナのユーザーも、ホストの自分も書けません。

書き方 ホストに無いとき
-v ./nohost:/app/data root の持ち主で自動作成
--mount type=bind,src=./nohost,dst=/app/data エラー:bind source path does not exist

先に mkdir しておくか、間違いに気づきやすい --mount を使います。

逆パターン:ホストでファイルを編集できない

(コンテナを root で動かして、bind mount に書き込んだあと)
$ ls -ln data
-rw-r--r-- 1    0    0 2 ... root.txt
$ echo x >> data/root.txt
bash: data/root.txt: Permission denied

コンテナが root で作ったファイルは、ホストでも root の持ち主になり、編集できませんでした(フォルダーが自分の持ち主なら、削除はできました)。sudo chown -R $(id -u):$(id -g) data で持ち主を戻し、以降は --user で動かします。

なぜ chmod 777 を避けるのか

$ chmod 777 data
$ docker run --rm -v "$PWD/data:/app/data" app11:1
written                                      ← 書けるようになった
$ ls -ln data
-rw-r--r-- 1 10001 10001 6 ... out.txt       ← 持ち主は UID 10001
$ echo edit >> data/out.txt
Permission denied                            ← ホストの自分が編集できない

chmod 777 でエラーは消えますが、作られたファイルの持ち主は UID 10001 のままで、ホストでは編集できませんでした。誰でも書き込めるフォルダーになるというセキュリティの問題もあります。UID を合わせるほうが、両方の問題を解決します。

named volume との違い

$ docker run --rm -v vol11:/app/data app11:1
uid=10001(app) gid=10001(app) groups=10001(app)
written

named volume(-v 名前:/パス)は、空のときにイメージの中身がコピーされ(Docker 公式ドキュメント)、確認環境では、Dockerfile で chown app:app したフォルダーにそのまま書き込めました。ホストのファイルを直接編集する必要が無い(DB のデータなど)なら、named volume にすると UID の問題が起きにくくなります。

種類 持ち主 向いている用途
bind mount(./data:/app/data) ホストのフォルダーの持ち主 ソースコード、ホストで編集するファイル
named volume(vol:/app/data) 空のときイメージの中のフォルダーの持ち主 DB のデータ、キャッシュ

イメージに無いパスに named volume を付けたとき(-v vol12:/work)は、root の持ち主のフォルダーになり、touch: /work/a: Permission denied になりました。Dockerfile で、そのフォルダーを作って chown しておきます。

似たエラー:Read-only file system

$ docker run --rm -v "$PWD/data:/app/data:ro" --user "$(id -u):$(id -g)" app11:1
sh: can't create /app/data/out.txt: Read-only file system

:ro(読み取り専用)で付けると、UID が合っていても書けません。 Permission denied とは別の原因です。

確認の手順(チェックリスト)

順番 確認すること 方法
1 ホストの UID id
2 フォルダーの持ち主 ls -lnd フォルダー(-n で数字)
3 コンテナで動く UID docker run ... id
4 合わせる --user "$(id -u):$(id -g)"、または Dockerfile の ARG UID
5 compose なら user: "${UID}:${GID}" の値が空になっていないか(.env)
6 フォルダーが root の持ち主 -v が自動で作った。先に mkdir
7 :ro が付いていないか Read-only file system なら読み取り専用

よくある質問

Q. Docker Desktop(Mac・Windows)でも同じですか。 A. 確認環境は Linux の Docker Engine です。Docker Desktop は、ホストとのファイル共有の仕組みが違うため、同じ結果にならないことがあります。同じ手順(id と ls -ln)で、コンテナから見た UID と持ち主を確かめます。

Q. SELinux の環境で Permission denied になります。 A. UID が合っていても、SELinux のラベルで拒否されることがあります。Docker 公式ドキュメントでは、-v の z・Z オプションで SELinux のラベルを設定できるとされています。確認環境(SELinux 無し)では試していないので、使う前に公式ドキュメントで違いを確かめます。

まとめ

  • コンテナで動く UID と、ホストのフォルダーの持ち主の UID が違うために起きる
  • id と ls -ln で数字を比べる
  • --user "$(id -u):$(id -g)" か、Dockerfile で UID を合わせる
  • compose の user: "${UID}:${GID}" は、変数が空だと root で動く。.env に書く
  • -v はホストに無いフォルダーを root の持ち主で作る。先に mkdir
  • chmod 777 は、ホストで編集できないファイルが増える。UID を合わせる

参考・出典

確認日はいずれも 2026年10月11日です。

  • Docker Docs「Bind mounts」:https://docs.docker.com/engine/storage/bind-mounts/
  • Docker Docs「Volumes」:https://docs.docker.com/engine/storage/volumes/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Docker bind mountのPermission deniedの直し方」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?