GitHub Codespaces で自前イメージを使う

## この記事は

GitHub Codespaces では、その環境をユーザがカスタマイズできます。
この記事ではカスタマイズのための全体の流れの例を説明します。具体的なカスタマイズの詳細については触れません。

- 自前イメージへの切り替え
- GitHub Actions によるイメージ生成および GitHub Container Registry への配置
- GitHub Container Registry に置いたイメージを使った Codespaces 起動

あたりについて書きます。

### おことわり

私は普段 VS Code を使っておらず、(Codespaces と基本は同じと思われる) VS Code Remote + devcontainer でのノウハウも特に持っていません。的外れなことを書いていたらごめんなさい。

## 手順

### リポジトリに基本となる .devcontainer/ を置く

Codespaces を使うリポジトリのルートに `.devcontainer/` を置きます。

まずは、標準の Codespaces で使われているコンテナ定義を使うのが良いでしょう。

https://github.com/microsoft/vscode-dev-containers/tree/main/containers/codespaces-linux

の `.devcontainer/` をコピーすれば OK です。

あるいはより簡素な

https://github.com/microsoft/vscode-dev-containers/tree/main/containers/ubuntu

も良いと思います。

#### ファイルの説明

vscode-dev-containers で提供されている　```.devcontainer/``` は以下のような構成になっているようです。

- `devcontainer.json`
    - 大元の定義ファイル
    - ここにどの Dockerfile が使われるかが指定されている
- `Dockerfile`
    - 実際に Codespaces 起動に使われるのはこれ
    - 実質1行。生成済みイメージが `FROM` でベースイメージに指定されている
    - 簡単な変更ならこのファイルに追加すればよい
- `base.Dockerfile`
    - `Dockerfile` の `FROM` に書かれている生成済みイメージを生成するための Dockerfile と思われる
    - (そのままでは) 実際の Codespaces 起動時には使われない
    - よりきめ細かく変更したいなら、これを変更してイメージ生成する (今回はこちら)
- その他ヘルパースクリプトなど

`Dockerfile` はこんな感じです。コメントにあるように、パッケージの追加程度ならこれを変更すれば良いでしょう。

```dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/universal:1-linux

# ** [Optional] Uncomment this section to install additional packages. **
# USER root
#
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>
#
# USER codespace
```

### そのまま Codespaces 起動してみる

まずはそのまま Codespaces を起動してみます。
デフォルトと同じ環境が10-20秒ほどで起動するはずです。

### 起動時にイメージをビルドする

次に、`base.Dockerfile` を使うように `devcontainer.json` を変更して commit & push します。

```diff
diff --git a/containers/codespaces-linux/.devcontainer/devcontainer.json b/containers/codespaces-linux/.devcontainer/devcontainer.json
index 7a6605c2..b0acd62e 100644
--- a/containers/codespaces-linux/.devcontainer/devcontainer.json
+++ b/containers/codespaces-linux/.devcontainer/devcontainer.json
@@ -1,7 +1,7 @@
 {
        "name": "GitHub Codespaces (Default)",
        "build": {
-               "dockerfile": "Dockerfile"
+               "dockerfile": "base.Dockerfile"
        },
        "settings": {
                "go.toolsManagement.checkForUpdates": "local",
```

Codespaces を起動してみます。同じ環境が起動しますが、今度は起動時にイメージのビルドが行われるので、10-20分ぐらいかかるはずです。
これでは実用に耐えませんね。

### GitHub Actions を使ってイメージをビルド & GitHub Container Registry に置く

Codespaces を起動するたびにイメージをビルドするのはつらいので、予め GitHub Actions でビルドしておくことにします。
以下は

-  `main` ブランチの `.devcontainer/` が変更されたことをトリガーとして
-  イメージをビルドして GitHub Container Registry に push する

ための GitHub Actions の定義です。この例だと `ghcr.io/ユーザ名/リポジトリ名/codespace:latest` に push されます。
お好みに合わせて適当に変更してください。

このファイルを commit & push した後で、試しに `.devcontainer/` 以下にファイルを追加するなどして GitHub Actions を走らせてみましょう。

```yaml:.github/workflows/build-codespace-image.yml
name: Create and publish a Docker image for Codespaces

on:
  push:
    branches:
      - main
    paths:
      - '.devcontainer/**'

env:
  REGISTRY: ghcr.io

jobs:
  build-and-push-image:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Log in to the Container registry
        uses: docker/login-action@v1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and push Docker image
        uses: docker/build-push-action@v2
        with:
          context: .devcontainer/
          file: .devcontainer/base.Dockerfile
          push: true
          tags: ${{ env.REGISTRY }}/${{ github.repository }}/codespace:latest
```

### GitHub の Personal Access Token を生成する

Codespaces が GitHub Container Registry に置いたイメージを取得できるように、アクセス権を付与する必要があります。(同じ GitHub なんだからこの設定なしでやってくれてもいい気もしますが)

https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token

のドキュメントを参考にしつつ、　https://github.com/settings/tokens からトークンを生成します。付与するスコープ(権限) は `read:packages` だけで OK です。ここで表示される値はこのあとすぐに Codespaces 用設定に登録するので、ブラウザのタブはそのままにしておきましょう。

### GitHub Container Registry にアクセスするための情報を Codespaces の Secret に登録する

https://github.com/settings/codespaces の「Codespaces secrets」から Secret を3つ登録します。

- `GHCR_CONTAINER_REGISTRY_SERVER` : ghcr.io
- `GHCR_CONTAINER_REGISTRY_USER` : GitHubアカウント名
- `GHCR_CONTAINER_REGISTRY_PASSWORD` : 先ほど生成した Personal Access Token

この `GHCR` の部分は共通になっていればなんでも OK のようです。それぞれ、 Codespaces を利用したいリポジトリから使えるように選択しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114219/04b6bbd4-9158-ea5c-98bb-7ad92e48ea94.png)


このあたりについての公式ドキュメントはこちら。

https://docs.github.com/en/codespaces/managing-your-codespaces/managing-encrypted-secrets-for-your-codespaces

https://docs.github.com/en/codespaces/codespaces-reference/allowing-your-codespace-to-access-a-private-image-registry

### GitHub Container Registry に置いたイメージを使う

GitHub Container Registry に置いたイメージを指定する Dockerfile を用意します。

```Dockerfile:.devcontainer/Dockerfile.ghcr 
FROM ghcr.io/ユーザ名/リポジトリ名/codespace:latest
```

devcontainer.json を変更します。

```diff
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index b0acd62..17290a7 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,7 +1,7 @@
 {
        "name": "GitHub Codespaces (Default)",
        "build": {
-               "dockerfile": "base.Dockerfile"
+               "dockerfile": "Dockerfile.ghcr"
        },
        "settings": {
                "go.toolsManagement.checkForUpdates": "local",
```

Codespaces を起動すると、今度は GitHub Container Registry からイメージを取得して 30秒-1分程度で起動するはずです。

### イメージをカスタマイズ

これでひととおり CI/CD のパスが通りました。あとは `base.Dockerfile` をいじってイメージをカスタマイズしていきましょう。


## まとめ

- GitHub Codespaces の環境をカスタマイズするためのステップ例を紹介しました。
- GitHub Actions を使ってイメージを GitHub Container Registry におき、それを使うことで Codespaces 起動を高速化できます。