オレオレdevcontainerテンプレート

いつもdevcontainerの設定方法を探しているので、よく利用する構成を残しておく。

ソースコード

https://github.com/ng3rdstmadgke/devcontainer-template

# 達成したかったこと

- 同一ホストで複数のユーザーが同一プロジェクトのdevcontainerを立ち上げる
    - docker-composeは利用しない
        - 同一ホストで同一プロジェクトのdevcontainerを複数立ち上げることができないため
- devcontainerごとにネットワークを分離する
    - devcontainer起動時にdocker networkを作成してネットワークを指定する
        - `initializeCommand` のスクリプトでdocker networkを作成
        - `runArgs` に `--network` オプションを指定して、devcontainerの起動ネットワークを指定
- devcontainerとは別にアプリの開発用コンテナを立ち上げる
    - docker outside of docker を利用する
        - `features` に `ghcr.io/devcontainers/features/docker-outside-of-docker:1` を指定
    - devcontainerと同一ネットワークに起動する
        - `containerEnv` に `DOCKER_NETWORK` 環境変数を定義し、devcontainerのネットワークを設定
    - あらかじめポートフォワーディングされている状態にする
        - `forwardPorts` にアプリ開発コンテナのホスト名とポートを指定しておく

# ディレクトリ構成
```
├── .devcontainer
│   ├── .bashrc_private
│   ├── devcontainer.json
│   ├── Dockerfile
│   ├── .env
│   ├── etc
│   │   └── .tmux.conf
│   ├── init.sh
│   ├── on-create.sh
│   └── post-attach.sh
...
```

# 実装

```js:.devcontainer/devcontainer.json
/**
 * devcontainerプロパティ一覧: https://containers.dev/implementors/json_reference/
 *   - devcontainer.jsonで利用できる変数: https://containers.dev/implementors/json_reference/#variables-in-devcontainerjson
 */
{
  /**
   * プロパティ: https://containers.dev/implementors/json_reference/#general-properties
   */
  // UIに表示するdevcontainerの名前
  "name": "devcontainer-template",

   // コンテナの環境変数設定
  "containerEnv": {
      // ホスト側のプロジェクトディレクトリ
      "HOST_DIR": "${localWorkspaceFolder}",
      // コンテナ側のプロジェクトディレクトリ
      "PROJECT_DIR": "${containerWorkspaceFolder}",
      // ホスト側のユーザー名
      "HOST_USER": "${localEnv:USER}",
      // devcontainerが所属するネットワーク名
      "DOCKER_NETWORK": "br-devcontainer-template-${localEnv:USER}"
  },

  "forwardPorts": [
    "sample-app-${localEnv:USER}:8000"
  ],

  "mounts": [
      "source=${localEnv:HOME}/.ssh/,target=/home/vscode/.ssh/,type=bind,consistency=cached",
      "source=${localEnv:HOME}/.aws/,target=/home/vscode/.aws/,type=bind,consistency=cached"
  ],

  // devcontainerに追加する機能の定義
  // features一覧: https://containers.dev/features
  "features": {
    // docker outside of dockerを利用するための設定: https://github.com/devcontainers/features/tree/main/src/docker-outside-of-docker
    "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
    // k9s: https://github.com/rio/features
    // "ghcr.io/rio/features/k9s:1": {}
  },


  /**
   * Docierfile関連のプロパティ: https://containers.dev/implementors/json_reference/#image-specific
   */
  "build": {
    "dockerfile": "Dockerfile",
    "context": ".."  // プロジェクトルートをdocker buildのコンテキストに指定
  },

  // devcontainer 起動時の docker run のオプション
  "runArgs": [
    "--network=br-devcontainer-template-${localEnv:USER}"
  ],


  /**
   * ライフサイクル関連のプロパティ: https://containers.dev/implementors/json_reference/#lifecycle-scripts
   */
  // コンテナ初期化時にホスト側で実行されるコマンド
  "initializeCommand": "/bin/bash .devcontainer/init.sh",

  // コンテナが初めて起動した直後にコンテナ内で実行されるコマンド
  "onCreateCommand": "/bin/bash .devcontainer/on-create.sh",

  // devcontainerにアタッチするたびにコンテナ内で毎回実行されるコマンド
  "postAttachCommand": "/bin/bash .devcontainer/post-attach.sh",



  /**
   * VSCodeのプラグイン
   */
  "customizations": {
    "vscode": {
      "extensions": [
        // Docs
        "hediet.vscode-drawio",
        "yzhang.markdown-all-in-one",
        "bierner.github-markdown-preview",
        "bierner.markdown-mermaid",
        "jebbs.plantuml",

        // Terraform
        "hashicorp.terraform",
        "saramorillon.terraform-graph",

        // Python
        "ms-python.vscode-pylance",
        "ms-python.black-formatter",
        "matangover.mypy",
        "charliermarsh.ruff",
        "ms-toolsai.jupyter",

        // Copilot
        "GitHub.copilot",
        "GitHub.copilot-chat",

        // Others
        "ms-vscode.makefile-tools",
        "ms-azuretools.vscode-docker"
      ]
    }
  }
}
```

```.devcontainer/init.sh
#!/bin/bash


DOCKER_NETWORK=br-devcontainer-template-${USER}
NETWORK_EXISTS=$(docker network ls --filter name=$DOCKER_NETWORK --format '{{.Name}}')

if [ -z "$NETWORK_EXISTS" ]; then
  docker network create $DOCKER_NETWORK
fi
```

```.devcontainer/on-create.sh
#!/bin/bash
set -ex

cp ${PROJECT_DIR}/.devcontainer/etc/.tmux.conf ~/.tmux.conf

cat <<EOF >> ~/.bashrc

source ${PROJECT_DIR}/.devcontainer/.bashrc_private
EOF
```

```.devcontainer/post-attach.sh
#!/bin/bash

set -ex

SCRIPT_DIR=$(cd $(dirname $0); pwd)

mkdir -p $HOME/.docker

# .docker/config.jsonにcredsStoreが自動で追記されてしまうため、空のファイルで上書きする
cat <<EOF > $HOME/.docker/config.json
{}
EOF
```

```sh:.devcontainer/.bashrc_private
#!/bin/bash

export $(cat ${PROJECT_DIR}/.devcontainer/.env | grep -v -e "^$" -e "^ *#" | sed -e "s| *#.*$||" | xargs)

source <(kubectl completion bash)
source <(helm completion bash)
source <(argocd completion bash)
source <(kustomize completion bash)
source <(poetry completions bash)
complete -C '/usr/local/bin/aws_completer' aws

# Dockerfile の terraform -install-autocomplete で.bashrcに自動的に追記されるが、備忘として明示的に残しておく
# https://developer.hashicorp.com/terraform/cli/commands#shell-tab-completion
complete -C '/usr/local/bin/terraform' terraform
```

```.devcontainer/.env
FOO=foo
BAR=bar
```

```Dockerfile:.devcontainer/Dockerfile
# https://mcr.microsoft.com/en-us/product/devcontainers/base/about
FROM mcr.microsoft.com/devcontainers/base:dev-ubuntu-24.04

RUN apt-get update && \
    apt-get install -y \
      sudo \
      locales \
      net-tools \
      iputils-ping \
      dnsutils \
      bash-completion \
      less \
      curl \
      wget \
      tar \
      xz-utils \
      unzip \
      make \
      gcc \
      git \
      vim \
      tmux \
      jq \
      fzf \
      groff \
      procps \
      default-mysql-client \
      python3.12

# ロケール設定
RUN echo 'ja_JP.UTF-8 UTF-8' >> /etc/locale.gen && \
    locale-gen && \
    update-locale LANG=ja_JP.UTF-8
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# terraform インストール
# https://developer.hashicorp.com/terraform/install
ENV TERRAFORM_VERSION=1.10.3
RUN wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip && \
mv terraform /usr/local/bin/ && \
terraform -install-autocomplete

# aws cli インストール
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
    unzip awscliv2.zip && \
    ./aws/install

# kubectl インストール
# https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
RUN curl -LO https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl && \
    chmod +x ./kubectl && \
    mv ./kubectl /usr/local/bin/kubectl

# eksctl インストール
# https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/setting-up-eksctl.html#setting-up-eksctl-linux
RUN curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp && \
    mv /tmp/eksctl /usr/local/bin

# Kustomizeのインストール
# https://kubectl.docs.kubernetes.io/installation/kustomize/binaries/
RUN curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash && \
    mv kustomize /usr/local/bin/

# helmインストール
RUN curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Argo CD CLI インストール
# https://argo-cd.readthedocs.io/en/stable/cli_installation/#download-with-curl
RUN curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64 && \
    sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd && \
    rm argocd-linux-amd64

# Poetry インストール
RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/home/vscode/.pypoetry python3.12 - && \
    chown -R vscode:vscode /home/vscode/.pypoetry
ENV PATH="$PATH:/home/vscode/.pypoetry/bin"

# spotinfo インストール
RUN wget https://github.com/alexei-led/spotinfo/releases/download/1.0.7/spotinfo_linux_amd64 && \
    chmod 755 spotinfo_linux_amd64 && \
    mv spotinfo_linux_amd64 /usr/local/bin/spotinfo
```

```.devcontainer/.env
FOO=foo
BAR=bar
```