CI/CDパイプラインを構築していてコンテナランナー内でDockerイメージをビルドする必要がある場合、必ずぶつかる疑問があります:DinDを使うべきか、DooDを使うべきか?私自身も違いを理解するまでかなりの時間がかかりました。この記事では、同じ苦労をしなくていいよう、要点をまとめています。
5分でできる — GitLab RunnerでDooD を設定する
すぐに動かしたい方のために、GitLab Runner向けのDooD(Docker-out-of-Docker)設定方法を紹介します。これは私が本番環境で使っていて、最も安定している方法です。
ステップ1:Docker executorでGitLab Runnerをインストール
# GitLab Runner をインストール
curl -L --output /usr/local/bin/gitlab-runner \
https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
chmod +x /usr/local/bin/gitlab-runner
# ランナーを登録 — executor: docker を選択
gitlab-runner register \
--url https://gitlab.com \
--registration-token YOUR_TOKEN \
--executor docker \
--docker-image docker:24 \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
ステップ2:イメージビルド用の.gitlab-ci.ymlファイル
build-image:
image: docker:24
services: [] # DooD使用時はdindサービス不要
variables:
DOCKER_HOST: unix:///var/run/docker.sock
script:
- docker build -t myapp:$CI_COMMIT_SHORT_SHA .
- docker push myapp:$CI_COMMIT_SHORT_SHA
これで完了です。ランナーはホストのDockerデーモンと直接通信してDockerイメージをビルドできるようになります。思ったよりシンプルです。
DinDとDooD — 実際どこが違うのか?
名前は似ていますが、動作の仕組みはまったく異なります。正しく理解することで適切な選択ができ、パイプラインが突然失敗したときもどこをデバッグすればいいかわかります。
Docker-in-Docker (DinD)
DinDとは、コンテナの内部でDockerデーモンを動かすことを意味します。コンテナランナーは独自のデーモンを起動し、ホストから完全に独立して動作します。
# .gitlab-ci.yml でDinDを使用
build-dind:
image: docker:24
services:
- docker:24-dind # サービスコンテナ内で動作する独立したデーモン
variables:
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_CERT_PATH: "/certs/client"
DOCKER_TLS_VERIFY: 1
DOCKER_HOST: tcp://docker:2376
script:
- docker build -t myapp:latest .
- docker push myapp:latest
DinDはコンテナを--privilegedで実行する必要があります。このフラグにより、コンテナはカーネルサブシステム(namespaces、cgroups、overlay filesystem)にアクセスでき、内部で別のDockerデーモンを起動することが可能になります。
Docker-out-of-Docker (DooD)
DooD は新しいDockerデーモンを作成しません。代わりに、コンテナランナーがホストのDockerデーモンソケット(/var/run/docker.sock)をコンテナ内にマウントします。コンテナ内のdockerコマンドは、実際にはホストのデーモンと通信しています。
# DooD でコンテナを実行 — ホストソケットをマウント
docker run -v /var/run/docker.sock:/var/run/docker.sock \
-it docker:24 sh
# このコンテナ内で、以下のコマンドはホスト上でコンテナを実行する
docker run hello-world
簡単な比較
- DinD:独立したデーモン、より高い隔離性、
--privilegedが必要、遅い(毎回イメージを再pullする必要がある) - DooD:ホストのデーモンを使用、速い(イメージキャッシュを共有)、シンプル、ただしコンテナがDocker上でホストと同等の権限を持つ
詳細解説 — DinDはなぜ–privilegedが必要なのか?
Dockerデーモンはnamespaces、cgroups、overlay filesystemなどのカーネルサブシステムへのアクセスが必要です。コンテナ内で実行する場合、これらの権限はデフォルトで制限されています。--privilegedフラグはその制限のほとんどを解除します。
これがDinDの最大の弱点でもあります。--privilegedで実行するコンテナは、悪用されるとホストへの脱出(container escape)が可能になります。公開済みのコンテナエスケープCVEの中には、このフラグが関係するものが少なくありません。そのため、多くの企業のセキュリティチームは本番クラスターで--privilegedを完全にブロックしています。
TLSを使ったより安全なDinD設定
# セルフホストDinDランナー用docker-compose.yml
version: '3.8'
services:
dind:
image: docker:24-dind
privileged: true
environment:
DOCKER_TLS_CERTDIR: /certs
volumes:
- dind-certs-ca:/certs/ca
- dind-certs-client:/certs/client
- dind-storage:/var/lib/docker
networks:
- ci-network
gitlab-runner:
image: gitlab/gitlab-runner:latest
volumes:
- ./config:/etc/gitlab-runner
- dind-certs-client:/certs/client:ro
environment:
DOCKER_HOST: tcp://dind:2376
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: /certs/client
networks:
- ci-network
depends_on:
- dind
volumes:
dind-certs-ca:
dind-certs-client:
dind-storage:
networks:
ci-network:
この設定では、DinDデーモンはTLS経由で通信します。ポート2375(非暗号化)は開放しません。
応用編 — Kaniko:Dockerデーモン不要でイメージをビルド
KanikoはKubernetesクラスターで--privilegedのセキュリティレビューが承認されなかったときに見つけた解決策です。議論するより切り替えることにしたら、問題がすぐに解決しました。KanikoはDockerfileからDockerイメージをデーモンなしでビルドし、完全にユーザースペースで動作します。特別な権限は一切不要です。
# Kaniko使用時の.gitlab-ci.yml
build-kaniko:
image:
name: gcr.io/kaniko-project/executor:v1.21.0-debug
entrypoint: [""]
script:
- /kaniko/executor
--context $CI_PROJECT_DIR
--dockerfile $CI_PROJECT_DIR/Dockerfile
--destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
--cache=true
--cache-repo $CI_REGISTRY_IMAGE/cache
Kanikoのビルド時間はDooD比で約20〜30%遅くなります。デーモンによるレイヤーキャッシュがないためです。セキュリティが速度より優先される場合、このトレードオフは十分受け入れられます。
GitHub Actions — DooD 組み込み済み
GitHubホスト型ランナーにはDockerが最初からインストールされているので、そのまま使えます:
# .github/workflows/build.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push image
run: docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
セルフホスト型GitHub ActionsランナーはGitLab DooD と同様の設定です。ソケットをランナーコンテナにマウントするだけで完了します。
本番環境から学んだ実践的なTips
DooD でのレイヤーキャッシュを効果的に使う
DooD の最大のメリットはホストとイメージキャッシュを共有できることです。ただし、パイプラインを数週間継続的に実行すると、このキャッシュが50〜80GBに達することがあります。実際にランナーのVPSがディスク不足になった経験があります。定期的なクリーンアップのcronjobを追加しましょう:
# Dockerキャッシュを毎週クリーンアップ — crontabに追加
0 3 * * 0 docker system prune -f --filter "until=168h"
ソケットの権限を正しく設定する
# dockerソケットのグループを確認
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker ...
# ランナーユーザーはdockerグループに属する必要がある
usermod -aG docker gitlab-runner
# またはランナーイメージのDockerfileで修正
RUN groupmod -g 999 docker && usermod -aG docker runner-user
実際の結果
30台以上のコンテナを稼働する本番クラスターでDinDからDooD に移行したところ、リソース使用量が約40%削減されました。主な理由は、各ランナーコンテナでデーモンを起動する必要がなくなったことです。さらに、ジョブ間でイメージキャッシュを共有できるため、DinDのように毎回pullし直す手間もなくなります。
何を選ぶべき場面は?
- DooD:高パフォーマンスが必要なVM/VPS上のセルフホストランナー、ホストを管理できる小規模チーム向け
- DinD:パイプライン間の隔離が必要な場合、共有環境、または複雑なDockerネットワークのテスト向け
- Kaniko:Kubernetesクラスター、セキュリティを重視する環境、
--privilegedを使いたくない場合向け
私は専用VPS上のGitLabランナーにはDooD を使っています。速くてシンプルで、デーモンオーバーヘッドを気にする必要がありません。他のチームと共有するKubernetesクラスターではKanikoの方が安全です。結局のところ、「最良のツール」を選ぶのではなく、各環境の信頼レベルと隔離要件に合ったツールを選ぶことが大切です。

