Open6

Dev Containerのコンテナ内でroot以外のユーザを使いつつDooDする

js4000alljs4000all

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は開発環境での利用に向いており、速度・簡易性・トラブルの少なさという面で優れています。

js4000alljs4000all

Dev ContainerでDooDする

docker.sockをマウントする設定をdevcontainer.jsonに書く。

devcontainer.json
    ...
    "mounts": [
        "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
    ],
    ...

コンテナ内でのユーザがrootの場合は、これだけで問題なく使えるようになる。

問題は、コンテナ内でのユーザがroot以外の場合だ。

Dockerfile
RUN set -x \
  && useradd -m -s /bin/bash -u 1000 user
USER user

当該ユーザが、ホスト側のdockerグループと同じGIDのグループに属していればdocker.sockにアクセス可能であり、DooDが成立するのだが、Dev Container起動時(つまりコンテナの起動時)に、このデフォルトユーザにホスト側のGIDを追加する都合の良い方法は見つけられなかった。

js4000alljs4000all

ワークアラウンド その1

多くの場合、dockerグループのGIDは998になっているらしい。
手っ取り早くDooDしたいのであれば、以下のようにGIDをベタ書きしてグループを作成し、ユーザを所属させれば良い。

Dockerfile
RUN set -x \
  && groupadd -g 998 docker \
  && useradd -m -s /bin/bash -aG docker -u 1000 user
USER user

当然ながら、この方法はポータビリティ上の問題を抱える。dockerグループのGIDが998以外の環境ではDooDが成立しない。

js4000alljs4000all

ワークアラウンド その2(失敗)

ホスト側のdockerグループのGIDをビルド引数で持ち込む。

Dockerfile
ARG DOCKER_GID
RUN set -x \
  && groupadd -g $DOCKER_GID docker \
  && useradd -m -s /bin/bash -aG docker -u 1000 user
USER user
compose.yaml
    build:
      context: .
      args:
        DOCKER_GID: ${DOCKER_GID}
DOCKER_GID=$(getent group docker | cut -d: -f3) docker compose build

ここまでは良かったが、Dev Container起動時にDOCKER_GIDを指定してやる方法がなく、頓挫した。

js4000alljs4000all

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した場合は繋がるのだが。

js4000alljs4000all

何やら、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のときにソケットが存在すればそれを優先します。