フロントエンドのコンテナイメージ作成はSkaffoldがいい落とし所な気がする話

[バックエンドエンジニアがフロントエンドのコンテナイメージを環境に依存しないで利用できるようにしたかったけどできなかった話](https://qiita.com/shinnara/items/f5021c3fc46ccab183ea) というブログを以前書きました。この後に Skaffold というツールを知りました。今回は `フロントエンドのコンテナイメージを作成するのはSkaffoldを使うのが現実的にいい落し所じゃない？` という話をさせて戴きます。

# Skaffold とは
[Skaffold](https://skaffold.dev/docs/)とはコンテナベースなKubernetesアプリケーションの継続的開発を支援するコマンドラインツールです。Skaffoldはコンテナイメージのビルド、リポジトリへのPush、Kubernetesへのデプロイを支援します。

[Features](https://skaffold.dev/docs/#features)を見て頂くと分りますが、結構色々なことができそうです。今回は [Docker Build](https://skaffold.dev/docs/builders/builder-types/docker/) のみを使います。Skaffoldのインストールなどは[Installing Skaffold](https://skaffold.dev/docs/install/)などを参照してください。

# 前回はなにが問題だったのか
[Docker Build](https://skaffold.dev/docs/builders/builder-types/docker/) の使い方を述べるまえに、前回の [バックエンドエンジニアがフロントエンドのコンテナイメージを環境に依存しないで利用できるようにしたかったけどできなかった話](https://qiita.com/shinnara/items/f5021c3fc46ccab183ea) ではなにが問題だったのかを確認しておきましょう。

[何が起きていたか](https://qiita.com/shinnara/items/f5021c3fc46ccab183ea#何が起きていたか)に記載しましたが、バックエンドエンジニアとしては全ての環境で同じコンテナイメージを使いたくなります。しかし、 Single Page Application かつ Static Site Generation の場合は、これが不可能です。接続先のURLを外部から注入できないためです。Server Side Rendering や Client Side Rendering を使えば接続先URLなどの環境依存情報を外部から注入できます。しかし Static Site Generation ではできません。 Static Site Generation 自体は Next などでも推奨されたやり方ですし、業務の要件として Static Site Generation を利用しなければならないこともあると思います。Single Page Application かつ Static Site Generation という構成の場合は完全に同じコンテナイメージを全ての環境で利用することは諦めざるおえません。

# 今回の落し所ってどこ？
今回の落し所は

**バイナリレベルで同じものを使っている保証は不可能だけど、せめてソースコードレベルで同じものを使っている保証くらいはしておこう。**

というものです。それでは [Docker Build](https://skaffold.dev/docs/builders/builder-types/docker/) の説明と具体的な方法に移ろうと思います。

# Docker Build
[Docker Build](https://skaffold.dev/docs/builders/builder-types/docker/) は Skaffold を使ったコンテナイメージの作成方法です。必要なものは

* Dockerfile
* skaffold.yaml

の2つのファイルです。

## Dockerfile

まず、全ての環境用のコンテナイメージを作成する Dockerfile を記述します。以下に例を記載します。

```
# 全ての環境用のコンテナイメージを作成する Dockerfile

# 全ての環境用にビルド
FROM node:20.11.1-bullseye as build
COPY . .
RUN npm ci
# 以下の結果はそれぞれ別のディレクトリに出力される想定です。
RUN npm build:dev
RUN npm build:qa
RUN npm build:stage
RUN npm build:prod

# Dev環境用のコピー
FROM nginx:1.24.0-bullseye as dev
COPY --from=build /dist/dev /usr/share/nginx/html
ENTRYPOINT ["nginx"]

# QA環境用のコピー
FROM nginx:1.24.0-bullseye as qa
COPY --from=build /dist/qa /usr/share/nginx/html
ENTRYPOINT ["nginx"]

# Stage環境用のコピー
FROM nginx:1.24.0-bullseye as stage
COPY --from=build /dist/stage /usr/share/nginx/html
ENTRYPOINT ["nginx"]

# Prod環境用のコピー
FROM nginx:1.24.0-bullseye as prod
COPY --from=build /dist/prod /usr/share/nginx/html
ENTRYPOINT ["nginx"]
```

Dockerのマルチステージビルドを使っています。最初のステージで全ての環境用のビルドを行ない、後続のステージで各環境用にコピーしています。

## skaffold.yaml

続いて skaffold.yaml の記述です。こちらで各環境用のcontainer imageを作成します。

```
apiVersion: skaffold/v2beta27
kind: Config
metadata:
build:
  local:
    push: false
    tryImportMiissing: false
  tagPolicy:
    gitCommit: {}
  artifacts:
  - image: frontend-dev
    dockerfile: Dockerfile
    target: dev
  - image: frontend-qa
    dockerfile: Dockerfile
    target: qa
  - image: frontend-stage
    dockerfile: Dockerfile
    target: stage
  - image: frontend-prod
    dockerfile: Dockerfile
    target: prod
```

詳細は [Docker Build](https://skaffold.dev/docs/builders/builder-types/docker/) を参照してください。

## container image作成

上記のDockerfileとskaffold.yamlが出来たら、ソースコードが配置されたトップディレクトリに置いてください。skafolldがインスートル済みの状態で

```
$ skaffold build
```

と実行すると、それぞれの環境用の `frontend-dev` 、 `frontend-qa` 、 `frontend-stage` 、 `frontend-prod` という container image が作成されます。

# おわりに

frontendでも出来れば同じ container image を使えれば良いのですが、forntend特有の事情で中々難しい。そんな時にはせめて同じソースコードでビルドされたものを保証するという落し所でした。
また、この方法は環境用に複数のDockerfileを作る必要もなくなるので管理も楽になります。

どなたかの一助になれれば幸いです。