はじめに
Docker/DevContainerの構成を試行錯誤している際に、特定のディレクトリが突然read-only(読み取り専用)になってしまう問題に遭遇しました。この記事では、その原因と対処方法について解説します。
対象とする環境
- macOS 15.7.2
- Docker Desktop for Mac 4.51.0
- DevContainer (Visual Studio Code) 0.431.1
対象とする読者
- Docker/DevContainer初心者
- macOS環境でDockerを使用している方
- Dockerのファイルシステムの仕組みを詳しく知らない方
発生する問題
症状 : ディレクトリが読み取り専用になる
DevContainerの構成を試行錯誤していたところ、ホストOSからマウントされた特定のディレクトリが、コンテナー内でのみread-onlyになる現象が発生しました。ホストOS側では従来通り読み書きが可能な状態でした。
原因の調査
調査の結果、以下のような手順で問題が発生することがわかりました。
- Dockerfileやdevcontainer.jsonの
postStartCommandなどでchmodコマンドを実行 - パーミッションは正常に変更される
- 設定したパーミッション変更コマンドを削除する
- しかし、削除したにもかかわらずパーミッションが永続化している
拡張属性の確認
macOSではxattrコマンドでファイルやディレクトリの拡張属性を確認できます。
xattr -l .devcontainer
このコマンドを実行すると、以下のような拡張属性が見つかりました:
com.docker.grpcfuse.ownership: {"UID":-1,"GID":-1,"mode":555}
原因 : Docker Desktop のファイル共有メカニズムと拡張属性
Docker Desktop for Macは、ホストとコンテナー間のファイル共有に複数の方式を提供しています。現在のデフォルトはVirtioFSですが、gRPC FUSEやosxfs(レガシー)も選択可能です。コンテナー内からchmodコマンドでパーミッションを変更すると、Dockerはその変更を拡張属性として永続化します。
この拡張属性com.docker.grpcfuse.ownershipは、gRPC FUSEで導入されたメカニズムですが、VirtioFSでも同じ属性名が使用されています(おそらく後方互換性のため)。この拡張属性が設定されると、コンテナー内でのみディレクトリのパーミッションがその属性値で上書きされてしまいます。上記の例では"mode":555(読み取り+実行のみ)が設定されているため、書き込みができなくなっています。
検証実験
この原因を確認するため、以下の3つのテストケースで検証実験を行いました。
dockerコマンドの-vオプションについて
-vオプションで、ホストのディレクトリをコンテナー内にマウントします。
-v <ホストのパス>:<コンテナー内のパス>[:<オプション>]
-
.: ホストの現在のディレクトリ -
:: ホストのパスとコンテナー内のパスの区切り文字 -
ro: read-only(読み取り専用)オプション
例: -v .:/workspace:ro は「現在のディレクトリを/workspaceに読み取り専用でマウント」という意味です。
テスト1 : 通常のバインドマウント
docker run -v .:/workspace <image>
# コンテナー内の/workspace/.devcontainerを確認
結果 : .devcontainerディレクトリに拡張属性は付与されない(問題なし)
テスト2 : 読み取り専用マウント
docker run -v ./.devcontainer:/workspace/.devcontainer:ro <image>
# .devcontainerディレクトリを読み取り専用でマウント
結果 : 読み取り専用マウントでも.devcontainerディレクトリに拡張属性は付与されない(問題なし)
テスト3 : コンテナー内での chmod 実行
通常のバインドマウント後、コンテナー内で.devcontainerディレクトリに対してchmodを実行:
chmod 755 /workspace/.devcontainer
結果 : com.docker.grpcfuse.ownership 拡張属性が追加される(問題発生)
この検証により、読み取り専用マウント設定が原因ではなく、コンテナー内でのchmod実行が原因であることが明確になりました。
技術的背景 : Docker Desktop のファイル共有メカニズム
Docker Desktop for Macは、LinuxカーネルをベースとしたVM上でDockerを動作させています。ホストのmacOSとVM内のLinux間でファイルを共有するために、以下の3つの方式から選択できます。
-
VirtioFS(デフォルト、Docker Desktop 4.6以降)
- 高速なファイル共有を実現
- macOS 12.5以降で利用可能
-
gRPC FUSE
- VirtioFS以前の実装
- 拡張属性メカニズムを導入
-
osxfs(レガシー)
- 初期の実装
拡張属性メカニズム
com.docker.grpcfuse.ownership拡張属性は、gRPC FUSEで導入されたメカニズムです。この仕組みによって:
- ホストOSのファイルシステムにおける所有権情報を破壊せずに、コンテナー内の所有権を永続化できる
- パーミッション変更などの操作は拡張属性として記録される
実験結果から確認できたこと :
- VirtioFS使用時でも
com.docker.grpcfuse.ownership拡張属性が作成される - 属性名に"grpcfuse"が含まれているが、VirtioFSでも同じ拡張属性が使用されている
これは、VirtioFSがgRPC FUSEの後継として開発された際に、実装の共通性や後方互換性のために同じ拡張属性メカニズムを採用したものと推測されます。
つまり、コンテナー内でのchmod操作の結果が、使用しているファイル共有方式にかかわらず、ホスト側のファイルシステムに拡張属性として残り続けることになります。
解決方法
拡張属性の削除
macOS上で以下のコマンドを実行し、問題の拡張属性を削除します:
# 拡張属性の確認
xattr -l .devcontainer
# 拡張属性の削除
xattr -d com.docker.grpcfuse.ownership .devcontainer
確認方法
削除後、再び拡張属性を確認します:
xattr -l .devcontainer
何も表示されなければ、拡張属性が正常に削除されています。
この問題から理解できること
1. 拡張属性の永続化メカニズム
コンテナー内でマウントされたディレクトリに対してchmodを実行すると、拡張属性が永続化されることを理解しておきましょう。コンテナー内のパーミッションが意図せず変更されている場合は、xattrコマンドで拡張属性を確認してください。
2. devcontainer.jsonで読み取り専用マウントを指定
特定のディレクトリを、mountsオプションで読み取り専用にできます
{
"mounts": [
"source=${localWorkspaceFolder}/.devcontainer,target=/workspace/.devcontainer,type=bind,readonly"
]
}
読み取り専用マウントにすることで、chmodを使わずにread-onlyを実現でき、拡張属性を変更せずに済みます。
まとめ
- Docker Desktop for Macでは、コンテナー内での
chmod実行結果が拡張属性として永続化される - 拡張属性
com.docker.grpcfuse.ownershipがコンテナー内でのみディレクトリのパーミッションを上書きする - 解決方法は
xattr -dコマンドで拡張属性を削除すること - パーミッションが意図せず変更されている場合は
xattrコマンドで拡張属性を確認する - 読み取り専用マウントを使用することで拡張属性の永続化を防げる
macOS + Docker環境で開発する際は、この挙動を理解しておくことが重要です。
参考リンク
- Speed boost achievement unlocked on Docker Desktop 4.6 for Mac - Docker公式ブログ(VirtioFS導入の発表)
- xattr - macOS Man Pages - xattrコマンドのリファレンス
以上、ありがとうございました。