日常CLIコンテナでhost UID/GIDに合わせてshellへ入る設計
Posted:
日常CLIコンテナでhost UID/GIDに合わせてshellへ入る設計
目的
日常的に使う CLI tool をコンテナ image にまとめると、host の環境を壊さずに新しい tool や最新版を試せる。 一方で、作業ディレクトリを bind mount して shell に入る場合、コンテナ内ユーザーの UID/GID が host とずれると、作成ファイルの所有者が壊れる。
Dockerfile に個人の user name、UID、GID を焼かず、起動時に host user に合わせる。
shell wrapperとentrypointを分ける
責務を分ける。
shell wrapper:
- host側で実行する
- container runtimeを選ぶ
- bind mountを組み立てる
- hostのUID/GID/USER/HOMEを環境変数で渡す
- containerは一度rootで起動する
entrypoint:
- container内で実行する
- hostと同じUID/GIDのuser/groupを作る
- 必要な補助groupを設定する
- 作業userへ権限を落としてcommandを実行する
この形にすると、image 自体は汎用のままにできる。 利用者ごとの UID/GID は runtime の入力として扱う。
なぜdocker exec –userだけにしないか
docker exec --user <uid>:<gid> で入るだけだと、container 内の /etc/passwd や /etc/group と整合しない。
特に supplementary groups が反映されないと、serial device、Docker socket、containerd socket のような group permission に依存する対象で詰まりやすい。
entrypoint で user/group を作り、対話 shell では setpriv --init-groups や gosu のような tool で group database に基づいて降りる。
root entrypoint
-> create group if needed
-> create user with host UID/GID
-> adjust supplementary groups
-> setpriv --init-groups --reuid <uid> --regid <gid>
-> shell
既存UIDとの衝突
base image に同じ UID の user が存在することがある。 この場合、UID が同じなら file ownership としては同一人物に見えるが、login name や HOME が想定と違うと shell 体験が壊れる。
方針:
- host user name を優先して作る
- 同じ UID が既にある場合は、必要に応じて non-unique user を許容する
- HOME は bind mount した host home または専用 workspace に寄せる
- container 内の既存 service user を壊さない
日常 CLI image では、daemon を常駐させるより対話 shell が主目的になる。 そのため、起動時 user creation の単純さを優先できる。
sudo groupを安易に付けない
作業 user へ host と同じ UID/GID を与えても、container 内で sudo/admin 権限を常用させる必要はない。 root が必要な初期化は entrypoint で済ませ、対話 shell は通常 user に落とす。
必要な補助 group は用途ごとに限定する。
dialout:
serial device access
docker:
Docker socket access
containerd-related group:
containerd socket access
socket を bind mount する場合は、host 側権限をそのまま container へ持ち込むことになる。 便利だが、container breakout 相当の権限になることを理解して使う。
対話shellのTTY問題
su - user -c fish のような入り方では、非標準の TTY や devcontainer 内で job control 周りの warning が出ることがある。
日常 CLI container では、login shell の再現性より、TTY と supplementary groups が正しく動くことを優先する。
entrypoint:
gosu user command
interactive shell:
setpriv --init-groups ...
gosu は default command を通常 user で実行する用途に合う。
一方で、後続の対話 shell では setpriv --init-groups のように group 初期化を明示できる形が扱いやすい。
コンテナは停止後に残さない
日常 CLI container は、実行中の常駐コンテナがあれば docker exec で再利用する。
ただし、停止済み container object は再利用しない。
shell.bash は docker run --rm で起動し、停止したら container object を自動削除する。
次回は host の現在状態に合わせて新しい container を作る。
この方針にすると、停止済みの /cli-tool-docker が Docker の名前を保持して次回の docker run --name cli-tool-docker と衝突する問題を避けられる。
また、--init、bind mount、Docker socket group、entrypoint の user/group 調整を変更した時に、古い停止済み container を誤って再利用しない。
消えるのは container 自身と container writable layer である。
host の $HOME は bind mount なので、日常作業で作ったファイルは container 削除の対象外になる。
一方で、停止後の docker logs cli-tool-docker や docker inspect cli-tool-docker はできない。
停止後調査を残したい場合は、停止前に logs/inspect を保存するか、検証用には別名の docker run --rm なし container を使う。
旧設定で起動していた停止済み container が残っている場合は、docker ps では見えない。
確認には docker ps -a --filter name=cli-tool-docker を使う。
現在の shell.bash は、そのような停止済み container を見つけた場合、再利用せず docker rm してから新規作成する。
macOSのdocker credential helper
macOS host の Docker config を container 内へ mount すると、Linux container 側に存在しない credential helper が指定されていることがある。
典型例。
error getting credentials: executable file not found in PATH
原因は、host の Docker config が macOS 用 credential helper を指しているためである。 Linux container 内ではその helper が存在しない。
対処方針:
- host の Docker config をそのまま使うか、container 専用 config を使うかを分ける
- Linux container 用には Linux で利用できる credential helper を使う
- registry 操作が不要なら Docker config を mount しない
- credential helper の中身や secret は image に焼かない
Docker credential は平文 config だけの問題ではなく、host の keychain / password store / secret service との組み合わせで動く。 日常 CLI image では、credential 管理を image 内の永続状態にしない方がよい。
検証項目
起動後に最低限見るもの。
id
whoami
printf '%s\n' "$HOME"
touch /workspace/.container-write-test
ls -ln /workspace/.container-write-test
rm /workspace/.container-write-test
Docker socket を使うなら。
docker version
docker ps
serial device を使うなら。
id
ls -l /dev/ttyACM0
所有者、group、supplementary groups が期待通りであることを確認してから、日常作業に使う。
まとめ
日常 CLI container では、image に個人 UID/GID を焼くより、shell wrapper と entrypoint で runtime に合わせる方が扱いやすい。 重要なのは、root で container を起動して初期化し、対話作業は host user 相当へ落とし、補助 group と bind mount の権限を明示的に扱うことだ。