Dev Containerのコンテナ内でroot以外のユーザを使いつつDooDする
DooD(Docker-outside-of-Docker)とは?
GPT-4oによると、以下の通り。
DooD(Docker-outside-of-Docker) は、コンテナの中からホストマシンのDockerデーモンを使う構成です。
コンテナ内部に docker
CLI をインストールし、ホストの /var/run/docker.sock
を共有マウントすることで、ホスト上で動作するDockerエンジンに対してコマンドを発行できます。
この方式により、Dev Container(=1つのコンテナ)内から複数のアプリケーション用コンテナ(例:Rails+DB)を起動できるようになります。
DinD(Docker-in-Docker)との違い
方式 | 説明 | 特徴 |
---|---|---|
DooD | コンテナ内からホストのDockerデーモンを使う(/var/run/docker.sock を共有) |
軽量、ホストと同一ネットワーク、ボリューム共有しやすい |
DinD | コンテナ内に完全なDockerデーモンを起動(docker:dind イメージなど) |
完全に隔離された環境、CI向け、一部制限や複雑さがある |
DooDは開発環境での利用に向いており、速度・簡易性・トラブルの少なさという面で優れています。
Dev ContainerでDooDする
docker.sockをマウントする設定をdevcontainer.jsonに書く。
...
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
],
...
コンテナ内でのユーザがrootの場合は、これだけで問題なく使えるようになる。
問題は、コンテナ内でのユーザがroot以外の場合だ。
RUN set -x \
&& useradd -m -s /bin/bash -u 1000 user
USER user
当該ユーザが、ホスト側のdockerグループと同じGIDのグループに属していればdocker.sock
にアクセス可能であり、DooDが成立するのだが、Dev Container起動時(つまりコンテナの起動時)に、このデフォルトユーザにホスト側のGIDを追加する都合の良い方法は見つけられなかった。
ワークアラウンド その1
多くの場合、dockerグループのGIDは998
になっているらしい。
手っ取り早くDooDしたいのであれば、以下のようにGIDをベタ書きしてグループを作成し、ユーザを所属させれば良い。
RUN set -x \
&& groupadd -g 998 docker \
&& useradd -m -s /bin/bash -aG docker -u 1000 user
USER user
当然ながら、この方法はポータビリティ上の問題を抱える。dockerグループのGIDが998
以外の環境ではDooDが成立しない。
ワークアラウンド その2(失敗)
ホスト側のdockerグループのGIDをビルド引数で持ち込む。
ARG DOCKER_GID
RUN set -x \
&& groupadd -g $DOCKER_GID docker \
&& useradd -m -s /bin/bash -aG docker -u 1000 user
USER user
build:
context: .
args:
DOCKER_GID: ${DOCKER_GID}
DOCKER_GID=$(getent group docker | cut -d: -f3) docker compose build
ここまでは良かったが、Dev Container起動時にDOCKER_GID
を指定してやる方法がなく、頓挫した。
DooDで、Dev Containerから子コンテナに繋がらない
Dev Containerと子コンテナが、実際には姉妹であるから、所属ネットワークが違うので繋がってないっぽい。
そこで、Dev Container起動時にネットワークモードをhostにすることで、Dev Containerと子コンテナとが親子のなるようにしてみたが、それでもだめ。
$ rails db:create
PG::ConnectionBad: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
子コンテナでNginxを起動して、Dev Container内でlocalhost:80
した場合は繋がるのだが。
何やら、host=localhost
と書いた場合とhost=127.0.0.1
と書いた場合とで、挙動が異なるっぽい。
なあにそれ。
🔍 エラーの核心
PG::ConnectionBad: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
これは、localhost
に接続しようとしているようで実はUNIXドメインソケットを使おうとしているときに出る PostgreSQL の典型的なエラーです。
PostgreSQLのRubyクライアント(pg
gemなど)は、環境によっては host=localhost
のときにUNIXソケットを優先して接続しようとします。
✅ 解決策
host=127.0.0.1
にする
方法①:localhost
ではなく、明示的に 127.0.0.1
を指定してTCP接続にすれば、この問題を回避できます。
# config/database.yml
development:
<<: *default
host: 127.0.0.1 # これ重要!
port: 5432
UNIXソケット経由を避けたいなら、常に
127.0.0.1
を使う方が安全です。
PGHOST=127.0.0.1
を環境変数で指定する
方法②:一時的に環境変数で制御することも可能です:
PGHOST=127.0.0.1 rails db:create
🔧 なぜNginxでは成功したのにPostgreSQLはダメだったのか?
- Nginxは常にTCPポート(:80)で待ち受けるため、
localhost
アクセスが素直に TCP で通る。 - PostgreSQLはUNIXドメインソケット(
/var/run/postgresql/.s.PGSQL.5432
)でも待ち受けるため、クライアントがTCPとソケットのどちらを使うか選ぶ仕様になっている。
Rubyの
pg
gemは、host=localhost
のときにソケットが存在すればそれを優先します。