【Docker使えるようになりたい】【#16 Dev Container】Dev Containerを使って開発環境をコンテナ内に構築する

DevContainerについて

DevContainerは2018年にMicrosoftがVSCodeで拡張機能のRemote - Containerとして「Dockerコンテナ内でコードを編集・デバッグ」機能として実装しました。

すでに、歴史的な話の一環で出てきましたが、DevContainerは「Dockerコンテナ内でコードを編集・デバッグ」するための機能です。

この機能を使うことで「ホストOS上の環境を変更」することなく、使用するSDKのバージョンを切り替え(コンテナの切り替え)ながら開発ができるようになります

また、VSCodeの拡張機能の「Dev Containers」を使って接続することで、コンテナごとにVSCode内で使用する拡張機能を指定することができるので、無駄なVSCode拡張がないクリーンな状態で開発ができます。

DevContainerの使い方

Dockerは導入済みの前提の話になります。

もしまだ導入していない方は、公式サイトからダウンロードインストールしておいてください。

VSCodeに拡張機能をいれる

VSCode拡張機能マーケットプレイスから、「Remote Devolopment」をインストールします

この拡張には、「Dev Containers」「Remote – XXXX」系の拡張やWSL(Windows用)の拡張などいろいろはいっているのでとりあえずこれを入れておけばDockerを使った開発には困らないと思います。

既存のイメージを使いたい場合

まずは、シンプルな既存のイメージをそのまま使用する方法をご紹介します。

イメージを取得するには、「取得するイメージのリポジトリ」から使いたいイメージを探す必要があります。

イメージの選定と指定方法

これ以外にも「プライベートリポジトリ」なども指定可能です。

各リポジトリの「URLとイメージのバージョン」をimageプロパティに対して""で囲い以下のように記述します。

Docker Hub上にあるイメージを使用する場合は、URLなどは必要なく、メジャーなもイメージであれば、「イメージ名:タグ」で大体取得できます。

# Docker Hub
image: "php:latest"

# GitHub Container Registry
image: "ghcr.io/OWNER/REPOSITORY[:TAG]"

# Azure Container Registry
image: "<registry-name>.azurecr.io/<repository>[:tag]"

フォルダ構成とdevcontainer.jsonへの最低限の記述

まずはファイル構成として以下のような構成にしました。

dev-container-php-test/
├── .devcontainer/
   └── devcontainer.json
└── main.php

PHPの解説はしませんが、DevContainer内でmain.phpのコードを動かすことができれば作業は成功です。

DevContainerを使うために必要なフォルダ構造としては、.devcontainerフォルダとその中にあるdevcontainer.jsonになります。

devcontainer.jsonには最低限の設定として以下のようにnameプロパティとimageプロパティを指定します。

{
  "name": "Dev PHP",
  "image": "php:latest"
}
  • name : DevContainerの名前をつける。複数使う場合に見分けがつきやすいようにする程度の意味
  • image : DevContainerで使用するイメージを指定する。ローカルのDockerfileからビルド済みのイメージであれば使用可能

nameプロパティの効果

nameプロパティは指定しなくてもエラーにはならずDevContainerを動かすことができます。

つけたときとつけなかった時の違いとしては、以下2つの画像の赤枠の中の部分に違いが出ます。

nameなし
nameあり

実行

main.phpは空のままでいいので、DevContainerでコンテナに接続したあとに編集して実行します。

DevContainerを実行するには、いくつか方法がありますが、今回はマウスクリックで接続する方法を試します。

今回のプロジェクトのルートディレクトリであるdev-container-php-testフォルダをVSCodeで開いてください。

dev-container-php-test/ <- ここのフォルダをVSCodeで開く
├── .devcontainer/
   └── devcontainer.json
└── main.php

その後、VSCodeの左下の青いボタンを押します。
そうすると、画面中央上にポップアップが表示されるので、その中のコンテナで再度開くを選択することでDevCotntainerでコンテナに接続する事ができます。

事前に手動でコンテナなどを実行しておく必要はありません。
VSCodeがいい感じに準備してくれるのでDocer Desktopだけ起動させておけばOKです。

VSCodeが一度落ちて再度起動して、一見変わっていないような状態で動き出せば接続成功です。

ここで、main.phpに以下の内容を記述してください

main.php
<?php

echo "テスト\n";

その後、「ターミナルを開く」と以下のようなLinuxでよく見るターミナルが表示されます。
以下のコマンドを実行することで、同じ出力がされれば成功です。

Zsh
# コマンド
root@6b8fa1d565b9:/workspaces/PHP# php main.php 

# 出力
テスト

DevContainerを終了させる

DevContainerを終了させるのも接続時と同じようにできます。

VSCodeの左下の青いボタンを押します。
画面中央上にポップアップが表示されるので、その中のリモート接続を終了するを選択することでDevCotntainerでコンテナに接続する事ができます。

切断をしても編集していたファイルはホストOSからのマウントなので編集状況を維持することができます。

Dockerfileでイメージをビルドして使いたい場合

今回は、Go言語の開発環境を構築していこうと思います。

今回はDevContainerがマウントするフォルダを設定して任意のフォルダにマウントできるような設定を追加、さらに、VSCodeの拡張機能を入れて開発がよりしやすい環境を構築します。

フォルダ構成とdevcontainer.jsonへの最低限の記述

DevContainerで開発するための開発用フォルダを作成します。

ファイルの中身は空で大丈夫なので、フォルダとファイルを追加して以下ディレクトリ構造と同じフォルダを作成してください。

dev-container-test/
├── .devcontainer/
│   └── devcontainer.json
├── Dockerfile
└── main.go
  • .devcontainer(フォルダ) : devcontainer.jsonをここに配置すると決まっている
  • devcontainer.json : DevContainerでコンテナに接続する時の設定が書いてある
  • Dockerfile : オリジナルイメージを作成するために使用する
  • main.go : Goのソースコード

作成したディレクトリを今回は先にVSCodeのフォルダーを開くで開いてこれ以下のファイルの編集をしていきます。

dvcontainer.jsonに記述

まずは、devcontainer.jsonに対して以下の内容を記述します。

{
  "name": "Go DevContainer",
  "build": {
    "dockerfile": "../dockerfile" // 相対パス
  },
  "workspaceFolder": "/workspace",
  "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
  "customizations": {
    "vscode": {
      "extensions": [
        "golang.go"
      ]
    }
  }
}

以下、記述してある内容の説明になります。

  • name : DevContainerの名前を指定します。この名前はVS Codeのステータスバーやその他のUI要素で表示され、複数のDevContainerを扱う際の識別に役立ちます。
  • build : コンテナのビルドに関する設定を指定します。主に使用するDockerfileのパスを定義します。
    • dockerfile: 使用するDockerfileへのパスを指定します。この例では、devcontainer.jsonが配置されている.devcontainer/ディレクトリから見た相対パス../dockerfileを指しています。
  • workspaceFolder : コンテナ内でのワークスペースディレクトリを指定します。ホストマシンのプロジェクトディレクトリがこの場所にマウントされます。
    • 注意点: デフォルトでは、/workspaces/<project-folder>/が使用されますが、カスタマイズする場合は以下のworkspaceMount設定も合わせて調整してください。
  • workspaceMount : ホストマシンのプロジェクトディレクトリをコンテナ内のworkspaceFolderにバインドマウントする設定です。
    • ${localWorkspaceFolder} : ホストOSのルートフォルダのパスを持っている変数
"source=バインドしたいホストOSの絶対パス,target=バインド先のコンテナ内のパス,type=bind,consistency=cached"
  • customizations : コンテナ内のVS Codeのカスタマイズ設定を指定します。
    • vscode : VS Codeの設定を行います。ここでは、必要な拡張機能を指定しています。
      • extensions : コンテナ起動時に自動的にインストールされるVS Codeの拡張機能をリストで指定します。

Dockerfileに記述

Dockerfileには「使用するイメージ」ぐらいしか記述しませんが、今後イメージにツールをいれる可能性を考慮してDockerfileを使用しています。

Dockerfile
FROM golang:1.23.2

main.goに記述

最後にmain.goに対して以下のコードを記述してサーバーとして、ポート番号8080で動くようなコードを作成します。

http://localhost:8080にアクセスすると「やあ」と表示されるはずです。

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", Handler)

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal()
    }
}

func Handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "やあ")
}

実行

これで必要なフォルダ構造の準備はできたので、実際にDevContainerでコンテナに接続しながらmain.goを動作させてみようと思います。

今まで編集していたフォルダを開いている状態のVSCodeで「コマンドパレット」を開いて「Dev Open Folder in」ぐらいまで打ち込んで、「開発コンテナー: コンテナーでフォルダーを開く」を実行します。

その後、「Finder or エクスプローラー」が開くので、そのまま「開く」を押すと、今のフォルダでDevContainerを実行する事ができます。

以下の画像のような状態でVSCodeのエクスプローラー部分が「WORKSPACE[開発コンテナー...]」となっていれば接続成功です。

ターミナルを開くと、Linuxでよく見るBashが実行されると思います。
すでにカレントディレクトリでコマンドを実行するだけの状態になっていると思うので、以下のコマンドを実行します。

root@6b26cff87908:/workspace# go run main.go

実行後、ブラウザでhttp://localhost:8080を開くことで「やあ」が表示されれば正常に動いています。

ポートの開放は手動しなくてもいい

DevContainerでは、通常のコンテナと違い、ポートをホストOSのポートに手動でバインドしていなくても、コンテナ内で実行されるアプリケーションが特定のポートを使用する必要がある場合、自動的にバインドされ、ホストOS側でアクセスできる様になっています。

切断

VSCodeの左下の青いボタンを押します。
画面中央上にポップアップが表示されるので、その中のリモート接続を終了するを選択することでDevCotntainerの接続を解除することができます。

切断をしても編集していたファイルはホストOSからのマウントなので編集状況を維持することができます。

DockerfileとDevContaienrを使う場合のWorkspaceの整合性をとる

dockerfileの最終的なWORKDIRは、/workspaceにするようにすることで、DevContainerとの整合性が取りやすくなっています。

devcontainer.json側も同様にすることで、dockerfileの最終的なディレクトリと、DevContainer側の作業ディレクトリを合わせることができるので整合性が取りやすいのでこのようにしています。

Dockerfile
# === 省略 ===
"workspaceFolder": "/workspace"
# === 省略 ===

注意したいところ : バインドマウントされる事による上書き

ケースとしてはかなりレアかもしれませんが、実験としてはわかりやすい結果が出たのでまとめています。

Dockerfileと、devcontainer.jsonで以下のような定義をしていた場合

Dockerfile
FROM golang:1.23.2

# golangのデフォルトのパスが/goなのでバインドマウントしたディレクトリに移動
WORKDIR /workspace

# test.txtを/workspaceに作成する
RUN touch test.txt
devcontainer.json
{
  "name": "Go DevContainer",
  "build": {
    "dockerfile": "../dockerfile" // 相対パス
  },
  "workspaceFolder": "/workspace",
  "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
  "customizations": {
    "vscode": {
      "extensions": [
        "golang.go"
      ]
    }
  }
}

Dockerfileの最後の処理であるtouch test.txtを実行すると当然、/workspace内にtest.txtが生成されるが、このあとに、DevContainerの接続がされて、バインドマウントが実行され、/workspace内にあるファイルはすべて上書きされて消えてしまいます。

もしどうしてもファイルを作成したいなら以下の2パターンの手法があります。

  • "postCreateCommand": "touch /workspace/test.txt"をdevcontainer.jsonに追記して、コンテナ接続後に生成する
  • 手動でホストOSのディレクトリ内に作成しておく

外部からもDevContainerにアクセスできるようにする

サーバーを起動すると、今までの設定では、ホストOS上のlocalhost:ポート番号にバインドされていましたが、これでは、ホストOS以外のデバイスからはアクセスができない状態になります。

それは、Portforardが必ずLocalhostとしてポートをバインドするからです。

これを外部に公開するには、runArgs-pオプションで明示的にバインドする必要があります。

以下のdevcontainer.jsonが外部からのアクセスに対応したものになります。

devcontainer.json
{
  "name": "Go DevContainer",
  "build": {
    "dockerfile": "../dockerfile"
  },
  "workspaceFolder": "/workspace",
  "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
  "customizations": {
    "vscode": {
      "extensions": ["golang.go"]
    }
  },
  //  ここを追加して、forwardPortsを削除
  "runArgs": [
    "-p",
    "8080:8080" // ホストの全てのインターフェースにバインド
  ]
}

外部からのアクセスには、ホストOSのIPが必要になるので、ホストOS上で以下のコマンドを実行してIPを調べてください。

# コマンド
ifconfig | grep 192

# 出力
inet 192.168.0.33 netmask ....

調べたIPを使って外部からアクセスできるようになると思います。(サーバーを動かすのを忘れずに)

http://ホストOSのIPアドレス:8080

私の環境では以下のようになります。

http://192.168.0.33:8080

おわりに

DevContainerを使いこなすことで、ホストOS上に開発環境を構築せずにすむ上に、デバイスの移行時などに開発用のディレクトリを共有してしまえばすぐに移行ができるので、とても便利な機能だと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA