LoginSignup
45
43

More than 1 year has passed since last update.

Dev Container 環境で Docker を使う

Last updated at Posted at 2022-07-31

VSCode の Remote - ContainersGitHub Codespaces のような Dev Container 環境で Docker を使う方法について解説します。

以前にも似たような記事を書きましたが、色々と内容に不足があったので改めて書き直すことにしました。

docker-from-docker と docker-in-docker

Dev Container のような Docker コンテナ内で Docker を使用する方法には、docker-from-docker と呼ばれる方式か docker-in-docker と呼ばれる方式の 2 つがあります。
両者の違いについて理解した上で要求にあっている方式を選択しましょう。

docker-from-docker

docker-outside-of-docker とも呼ばれます。

これは Docker コンテナにホストの Docker ソケットをバインドマウントすることにより、コンテナ内からホストの Docker を使用するというアプローチになります。
この方式を使う場合、Dev Container 内で作成されるコンテナは Dev Container の「兄弟」コンテナとなります。

この方式の特徴は次の通りです。

  • bind mount を行う場合、マウントするディレクトリはホスト側のパスで指定する必要がある
    • ホストの Docker を使用しているため、Dev Container 内のパスは使えません
    • Dev Container 内からホスト側のパスを取得するために工夫が必要となります
  • Dev Container から作成したコンテナに対して localhost でアクセスすることはできない
    • 作成したコンテナのポートはあくまでもホストに対して公開されるため、Dev Container にとっての localhost ではありません
    • Dev Container から作成したコンテナにアクセスするためには何らかの工夫が必要となります
  • コンテナやイメージ、ボリュームなどのリソースをホストと共有できる
    • ホスト側で build や pull したイメージを DevCotainer 内でも利用できます
    • Dev Container で build や pull したイメージをホスト側でも利用できます

docker-in-docker

Docker コンテナ内でホストとは異なる独自の Docker デーモンを持つ方法です。
この方式を使う場合、Dev Container 内で作成されるコンテナは Dev Container の純粋な「子」コンテナとなります。

この方式の特徴は次の通りです。

  • ホストから Docker を使うときと同じ感覚で Docker を使うことができる
    • bind mount に Dev Container 内のパスを使うことができます
    • 作成したコンテナに localhost でアクセスすることができます
  • Dev Container を --privileged オプションつきで実行する必要がある
    • Dev Container がホストのすべてのデバイスにアクセスする特権を得ることになるため、相応のリスクが伴います
  • イメージやボリュームなどのリソースは独自にコンテナ内で管理される
  • コンテナやイメージ、ボリュームなどのリソースは独自にコンテナ内で管理される
    • ホストと共有することはできません

docker-from-docker で Docker を使う

導入方法

主に 3 つの方法があります。

  1. Dev Container 定義 を使う
    • Dev Container を作成する際に Docker from Docker を選択する方法です
    • 開発に使う言語などは後から自分で Dockerfile をカスタマイズして追加することになります
    • Dev Container を docker-compose で動かしたい場合は Docker from Docker Compose を使うことができます
  2. Development Container Scripts を使う
    • Development Container Scripts とは Dockerfile の中で呼び出すことで様々な言語やツールのセットアップを行えるスクリプトです
    • Docker-from-Docker Install Script が提供されています
  3. Dev container features (preview) を使う

この記事では最もお手軽な 3 の方法のみ詳しく紹介します。
2022-11-25 追記: Dev container features は preview ではなくなっていますが、Dev Container 仕様のオープン化に伴い名称とリポジトリが変更されています。今後は こちら を参照してください(以下の情報は古くなっています)。

Dev container features については次の記事でも紹介しているので参照してください。

新規に Dev Container を作成する場合は、Select additional features to install で表示される Docker (Moby) support, reuse host Docker Engine (Docker-from-Docker) にチェックを入れれば OK です。

既存の Dev Container に Docker を導入したい場合は .devcontainer/devcontainer.json に次の設定を追加します。

.devcontainer/devcontainer.json
{
  // ...
  "features": {
    "docker-from-docker": {
      "version": "latest"
    }
  }
}

使用する docker-compose のバージョンを指定することもできます。

.devcontainer/devcontainer.json
{
  // ...
  "features": {
    "docker-from-docker": {
      "version": "latest",
      "dockerDashComposeVersion": "v2"
    }
  }
}

ゾンビプロセス防止のため、使用する Dev Container のベースイメージに代替の init システムが含まれていない場合は --init オプションをつけて Dev Container を起動するようにします。

.devcontainer/devcontainer.json
{
  // ...
  "runArgs": [
    // ...
    "--init"
  ]
}

Dev Container を docker-compose で動かす場合は次のようになります。

.devcontainer/compose.yaml
services:
  app:
    # ...
    init: true

Dev Container 内で bind mount を使う

前述の通り、docker-from-docker で bind mount を使う場合はマウントするディレクトリをホストのパスで指定する必要があります。

例えば、次のようなコマンドでは正しく bind mount できません。

$ docker run --rm -it -v $(pwd):/app golang:1.18 /bin/bash

ホスト側のパスを取得するためには .devcontainer/devcontainer.json で次のような環境変数を定義します。

.devcontainer/devcontainer.json
{
  // ...
  "remoteEnv": {
    "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
  }
}

この環境変数により Workspace のホスト側のパスを参照することができます。
つまり、先ほどの例を正しく動作させるためには以下のようにします。

$ docker run --rm -it -v ${LOCAL_WORKSPACE_FOLDER}:/app golang:1.18 /bin/bash

docker-from-docker の場合、Workspace 外のディレクトリを bind mount することはできません。

Dev Container 内で docker-compose の bind mount を使う

docker-compose でも bind mount を使う場合はホスト側のパスを指定してあげる必要があります。

次の設定は Dev Container 内では動作しません。

compose.yaml
services:
  app:
    image: ruby:2.7
    volumes:
      - .:/app
    working_dir: /app
    command: bundle exec rackup

次のように書くことで動作します。

compose.yaml
services:
  app:
    image: ruby:2.7
    volumes:
      - ${LOCAL_WORKSPACE_FOLDER}:/app
    working_dir: /app
    command: bundle exec rackup

ただ、上記のような書き方をしてしまうと Dev Container の外では動かすことができません。
それでは不便なので Dev Container 用の設定は別ファイルに分けてあげると良さそうです。

compose.devcontainer.yaml
services:
  app:
    volumes:
      - ${LOCAL_WORKSPACE_FOLDER}:/app
$ docker compose up -f compose.yaml -f compose.devcontainer.yaml

Dev Container 内で作成したコンテナにアクセスする

前述の通り、docker-from-docker の場合次のようなコマンドでコンテナを作成しても localhost:8080 でアクセスすることができません。

$ docker run --rm -it -p 8080:80 nginx
$ curl localhost:8080
curl: (7) Failed to connect to localhost port 8080: Connection refused

これは作成したコンテナがポートを公開しているのはホストであるためです。
兄弟コンテナである Dev Container 側からは localhost ではアクセスできません。

アクセスするには次のような工夫が必要です。

ホスト経由でアクセスする

Docker for Windows や Docker for Mac では、ホストの API アドレスを指す特殊なドメイン host.docker.internal をコンテナ内で使用することができます。
これを使用してホスト経由でアクセスすることができます。

$ curl host.docker.internal:8080

ただし、Docker for Linux では host.docker.internal がサポートされていません。
代わりに次のようにして host.docker.internal を使えるようにすることが可能です。

.devcontainer/devcontainer.json
{
  // ...
  "runArgs": [
    // ...
    "--add-host=host.docker.internal:host-gateway"
  ]
}

Dev Container を docker-compose で動かす場合は次のようになります。

.devcontainer/compose.yaml
services:
  app:
    # ...
    extra_hosts:
      - host.docker.internal:host-gateway

サービスディスカバリを利用する

Dev Container と作成するコンテナを同一ブリッジネットワークに所属させることで、サービスディスカバリを利用してアクセスすることができます。

これを行うには予めユーザー定義ブリッジネットワークを作成しておきます。

$ docker network create dev

次のようにして Dev Container を作成したネットワークに接続するようにします。

.devcontainer/devcontainer.json
{
  "runArgs": [
    // ...
    "--net=dev"
  ]
}

コンテナを作成する際に同一ネットワークを指定します。
コンテナ間通信となるためポートの公開は不要です。

$ docker run --rm -it --name web --net dev nginx

コンテナ名を使ってアクセスできます。

$ curl web

Dev Container を docker-compose で動かす場合は docker-compose 側で作成されるネットワークをそのまま利用できます。

$ docker run --rm -it --name web --net workspace_devcontainer_default nginx
$ curl web

docker-in-docker で Docker を使う

導入方法

docker-from-docker と同様に、主に 3 つの方法があります。

  1. Dev Container 定義 を使う
    • Dev Container を作成する際に Docker in Docker を選択する方法です
  2. Development Container Scripts を使う
  3. Dev container features (preview) を使う

最もお手軽な 3 の方法のみ詳しく紹介します。
2022-11-25 追記: Dev container features は preview ではなくなっていますが、Dev Container 仕様のオープン化に伴い名称とリポジトリが変更されています。今後は こちら を参照してください(以下の情報は古くなっています)。

新規に Dev Container を作成する場合は、Select additional features to install で表示される Docker (Moby) support (Docker-in-Docker) にチェックを入れれば OK です。

既存の Dev Container に Docker を導入したい場合は .devcontainer/devcontainer.json に次の設定を追加します。

.devcontainer/devcontainer.json
{
  // ...
  "features": {
    "docker-in-docker": {
      "version": "latest"
    }
  }
}

使用する docker-compose のバージョンを指定することもできます。

.devcontainer/devcontainer.json
{
  // ...
  "features": {
    "docker-in-docker": {
      "version": "latest",
      "dockerDashComposeVersion": "v2"
    }
  }
}

ゾンビプロセス防止のため、使用する Dev Container のベースイメージに代替の init システムが含まれていない場合は --init オプションをつけて Dev Container を起動するようにします。

.devcontainer/devcontainer.json
{
  // ...
  "runArgs": [
    // ...
    "--init"
  ]
}

Dev Container を docker-compose で動かす場合は次のようになります。

.devcontainer/compose.yaml
services:
  app:
    # ...
    init: true
45
43
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
43