🐳

VSCode+Docker+Dev Containerを使って開発環境をコンテナに作ってみた

2022/11/08に公開

今回やったこと

開発環境をコンテナに構築しホストのVisual Studio Codeからイジイジできるようにした。
動作検証環境は Mac mini 2020(Apple M1), MacBook Pro 2019(Intel Core)で検証しています。Windows, Linux では試せていません。

メリット

開発環境の再現性が高いため以下のようなメリットが得られます。

  • 同じ開発環境をすぐに提供できる[1]
  • ツール・ライブラリのバージョンをプロジェクト毎に決められる
  • ホストの環境を汚さずにちょっとお試しができる
  • Dockerの設定によっては構築時の最新バージョンのツールを構築することもできる

デメリット

そうはいってもデメリットやできないこともあります。

  • Docker Desktopの企業利用は有料になる場合がある[2]
  • Dockerを動作させる為、それなりのPCスペックが必要
  • 構築するコンテナ環境によってはビルドが遅い
  • Dockerで管理できないツール(VSCodeを除くGUIツール)は対応不可
  • コンテナ環境の準備にはある程度の知識が必要

動作に必要なもの

Dev Containerのおさらい

Dev ContainerはVSCode(Local OS)からコンテナ(Container)へアクセスしてコンテナ上でビルドやアプリの実行を行い、あたかもホストで動かしているかのように振る舞います。
またこのコンテナ毎にVSCode拡張をインストールできるのでコンテナそれぞれで最適な環境を構築することが可能です。

dev-containers

より詳細を知りたい方はDev Containerをご覧ください。
またVSCodeがなくてもCLIからDev Containerの実行も可能となっています。Dev Container CLI

作ったものの紹介

以下の環境を作ってみました

リモートユーザはvsocde、ディレクトリは/workspaceをホストとゲストで共有しています。
実行確認用のプログラムも入れてあります。
また独断と偏見でVSCodeの拡張もインストールしています。何が入っているかは書くリポジトリのREADME.mdをご確認ください。

起動方法

Dockerは立ち上げておいてください。
好きなリポジトリをgitcloneします。
cloneしたフォルダをしてVSCodeで開いてください。
Dev Containerがインストールされていれば右下にReopen in Containerが出てきますのでそれを押してください。
押し損なった場合でもコマンドパレットからDev Containers: Open Folder In Containerで起動できます。
workspaceディレクトリがVSCodeで開いた状態になり完成です。

リポジトリの基本構成

今回作成したリポジトリは複数あります。だいたい同じ構成で作っていますので代表でPythonのリポジトリで説明します。

ホスト側の構成
+- .devconainer
|  +- Dockerfile
|  +- devcontainer.json
|  +- docker-compose.yml
+- workspace
|  +- .vscode
|  |  +- settings.json
|  +- hello-server.py
|  +- hello-world.py
+- .gitignore
+- LICENSE.md
+- README.md

.devcontainer ディレクトリ

dev containerの設定ファイルやDocker用の設定ファイルを保持するディレクトリ。
devcontainer.json, docker-compose.yml, Dockerfileの3つファイルがあります

devcontainer.json ファイル

dev containerの設定ファイル。今回はdocker-compose.ymlを利用していますのそのファイル名とサービス名を指定しています。
なおdocker-compose.ymlを使わずにDockerfileを直接設定する方法もあります。

devcontainer.json
{
  // dev container名(どこに使われてるかよくわかってない)
  "name": "docker-playground - python",
  // 今回は docker-compose.ymlを利用する。全リポジトリ共通にしてます。
  "dockerComposeFile": "docker-compose.yml",  
  // docker-compose.yml内のサービス名を指定。全リポジトリ共通にしてます。
  "service": "playground",
  // コンテナのどこにワークスペースを配置するかの指定。全リポジトリ共通にしてます。
  "workspaceFolder": "/workspace",
  // ゲストOSでの実行ユーザ。全リポジトリ共通にしてます。
  "remoteUser": "vscode",
  // ゲストOSの設定を変更する場合に利用する設定
  "customizations": {
    // VSCodeのデフォルトの設定を変更します
    // 'settings'を指定すればここでVSCodeの設定変更も可能
    // 今回のポリシーとして拡張はこのファイルに記載
    // VSCodeの動作変更は'workspace/.vscode/settings.json'に記載としています
    "vscode": {
      // 事前にゲストOS上のVSCodeで利用する拡張をインストールします
      "extensions": [
        // 全リポジトリにインストール
        "EditorConfig.EditorConfig",
        // 以下 python用の拡張
        "ms-python.python",
        "ms-python.flake8",
        "ms-python.autopep8",
        "njpwerner.autodocstring"
      ]
    }
  }
}
docker-compose.yml ファイル

docker composeで利用するようにdocker-compse.ymlを設定します。

docker-compose.yml
version: '3'
services:
  playground:                 # `devcontainer.json`の'service'と一致させる
    container_name: 'python'  # コンテナ名
    hostname: 'python'        # ホスト名.ゲストOSのシェルに表示されるので指定している

    # 以下は他のリポジトリも全て同じ設定にしています
    build: .                  # Dockerfileがあるディレクトリ
    restart: always           # 常に再起動
    working_dir: '/workspace' # デフォルトの作業ディレクトリ
    tty: true                 # コンテナを終了させずに待たせる
    volumes:                  # ホストとゲストのディレクトリを紐づける
      - type: bind            # ホストのディレクトリをゲストにマウントさせる
        source: ../workspace  # ホストの`workspace`ディレクトリを指定
        target: /workspace    # ゲストの`/workspace`にマウントさせる
Dockefile ファイル

コンテナのイメージ設定。基本的には言語やツールの最新版のslimがあればをそれを選択しています。Apple シリコンで動作しない場合は別途設定やインストールで実行できるようにしています。

Dockerfile
# ベースとなるイメージ。Python 3系の安定版のスリム版。
FROM python:3-slim

# ユーザ'vscode'を作成する
# 他のリポジトリではUSER_UIDがベースのイメージですでに使われている場合は別の値を使っています。
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID

# ユーザの作成と`sudo`の利用設定
# 言語・場所設定
RUN apt-get update \
    && groupadd --gid $USER_GID $USERNAME \
    && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \
    && apt-get install -y sudo \
    && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
    && chmod 0440 /etc/sudoers.d/$USERNAME \
    && apt-get -y install locales \
    && localedef -f UTF-8 -i ja_JP ja_JP.UTF-8

# curlとgitは利用頻度が高いので他のリポジトリでもインストールしています。
RUN apt-get -y install curl git

# その他環境変数など
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

# vim, less, gccのインストール
RUN apt-get install -y vim less gcc

# 以下 pip
# VSCodeで自動フォーマットやリントの動作をさせるため事前にインストール
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install flake8 autopep8

# サンプルソースで利用しているパッケージとこの後利用しそうなものを追加
RUN pip install python-dotenv fastapi uvicorn

workspace ディレクトリ

このホストのディレクトリがコンテナのゲストOS上ではルートのディレクトリとしてバインドされる。
ソースコードやビルドの設定情報。gitなどもこのフォルダを起点に設定可能。

コンテナ(ゲスト)側にはこう見える
/workspace
  +- .vscode
  |   +- settings.json
  +- hello-server.py
  +- hello-world.py
.vscode ディレクトリ

VSCodeの設定を保存するディレクトリ。このディレクトリをgitなどSCMで管理するかは議論の分かれるところだと思います。
とりあえずPythonのフォーマットやリンターの設定をしてあります。細かい説明は割愛。
リンターの設定や保存した時に自動フォーマットしてます。

settings.json ファイル
settings.json
{
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.fixAll": true,
    },
    "autoDocstring.docstringFormat": "google",
    "python.linting.lintOnSave": true,
    "python.linting.pylintEnabled": false,
    "python.linting.flake8Enabled": true,
    "python.formatting.provider": "autopep8",
    "python.formatting.autopep8Args": [
        "--aggressive",
        "--aggressive"
    ],
    "[python]": {
        "editor.defaultFormatter": "ms-python.autopep8"
    }
}
hello-world.py ファイル

開発環境ができたらまずこれ。hello, world!を表示します。[3]
ゲストOSで上./hello-world.pyを入力して実行できます。

hello-world.py
#!/usr/local/bin/python3

print('hello, world!')
hello-server.py ファイル

メッセージとして'hello, world'を返すサーバです。
ゲストOSで上./hello-server.pyを入力して実行できます。
実行されるとVSCode拡張のDev Containerが自動的にポートをホストとマッピングします。ホスト上のブラウザからアクセスしてゲスト上のサーバにアクセスできます。

hello-server.py
#!/usr/local/bin/python3

import uvicorn
from fastapi import FastAPI

app = FastAPI()


@app.get('/')
async def root():
    return {'message': 'hello, world'}

if __name__ == '__main__':
    uvicorn.run(app)

.gitignore ファイル

gitで追跡対象外にするファイル・フォルダを指定します。
ここにはworkspaceディレクトリの設定については指定しないこととしています。
workspaceでは別途gitを初期化して利用することを想定しているためです。

.gitignore
.DS_Store

LICENSE.md ファイル

ライセンスファイル。docker-playground-...は全てMITライセンスにしています。

README.md ファイル

github用のREADMEファイル。リポジトリごとにどんな設定にしているかを記載しています。

その他注意事項

denoについて

denoは正確にはlinuxのArmアーキテクチャのビルドイメージがありません。
そのため--platform=linux/amd64でコンテナを動作させて代用しています。

haskellについて

armアーキテクチャのイメージにはstackが入っていません。
なのでIntel macとAppleシリコン macでできることが違います。
「同じ開発環境をすぐに提供できる」と言ったな。あれは嘘だ!

失敗談

最初はworkspaceフォルダではなくoptフォルダを利用して作成していました。
pythonの時とかは良かったのですがhaskellの時に問題になりました。
haskellのツールghciなどは/opt/ghc/にインストールされています。
これを後からホストのoptディレクトリでbindしてしまうためツール類がゴッソリなくなりコンテナの立ち上げでエラーが起きてました。これで2日無駄にしました。😂

脚注
  1. CPUアーキテクチャの差異で異なる場合があります ↩︎

  2. Rancher Desktopで代用できる? ↩︎

  3. 今気がついた、、、他の言語では!付けてないかも。 ↩︎

Discussion