Multi-stage buildsを使って環境別のコンテナイメージを作成する

n番煎じですが、Multi-stage buildsを使って環境別のコンテナイメージを作成する一例になります。
環境別にパッケージや挙動・設定が異なるのは望ましくないですが、
こういう書き方をする場合もあるということが誰かの参考になれば幸いです。

今回は、環境別にコンテナイメージを作成する場合のDockerfileとGitHub Actionsについて紹介します。

環境別のコンテナイメージを作成したい時とは

環境別のコンテナイメージを作成したいケースは、以下のようなケースがあります。

  • 環境別にインストールするパッケージやライブラリを変更する
  • 環境別に設定ファイルを変更する

KubernetesのConfigMapなどで外部から追加・差し替えができる場合は、イメージを環境別に作成する必要はありません。
ですが、差し替えするのが簡単ではない場合もあり、そういった場合は環境別にコンテナイメージを作成した方がメンテが楽な場合があります。 ※1

環境別にコンテナイメージを作成する

環境別にコンテナイメージを作成するために、今回達成したい要件をまとめます。

  • AlpineをベースにしてRedisを動かすイメージを作成する
  • 全てのIPv4,IPv6アドレスからの接続を許可する
  • ローカルの場合、BashVimを利用できるようにする
  • ローカルの場合、ログレベルをdebugにする
  • 本番の場合、ログレベルをwarningに変更する

これを踏まえたDockerfileは以下のようになります。

# 全ての環境で共通
FROM alpine:3.16 as base
RUN apk add --update --no-cache redis
RUN sed -i 's/bind 127.0.0.1 -::1/bind 0.0.0.0 ::/g' /etc/redis.conf
CMD ["redis-server"]

# ローカル
FROM base as dev
RUN apk add --update --no-cache bash vim
RUN sed -i 's/loglevel notice/loglevel debug/g' /etc/redis.conf

# ステージング
FROM base as staging

# 本番
FROM base as production
RUN sed -i 's/loglevel notice/loglevel warning/g' /etc/redis.conf

ローカルでdevステージを動かす

ローカルではdocker comoseを使って動かしたいので、compose.yamlを用意します。 ※2
ローカル向けでは、buildのオプションのtargetdevを指定することで、devステージのイメージが利用されます。

services:
  redis:
    build:
      target: dev
    ports:
     - 6379:6379

docker compose up を実行して起動することで動作確認ができます。
実際にBashなどが起動できるかは、docker compose exec -it redis bashで動作確認ができます。

今回はRedis用のイメージとして書いていますが、アプリケーションにも同様の仕組みが使えるので、 是非使ってみてください。

ステージングと本番向けのイメージをGitHub Actionsでビルドする

mainブランチが更新されたら、ステージング・本番向けにそれぞれイメージをビルドする仕組みを作ります。

こちらもローカル同様にtargetstagingか、productionを指定することで実現しています。

name: Build images

on:
  push:
    branches:
      - main

jobs:
  build_and_push:
    strategy:
      matrix: 
        stage: [staging, production]
    steps:
      - uses: actions/checkout@v3
      - docker/setup-qemu-action@v2
      - docker/setup-buildx-action@v2

      - name: Build and push
        uses: docker/build-push-action@v3
        with:
          context: .
          target: ${{ matrix.stage }}
          push: false

といった形で、各環境別にイメージを作成する方法について紹介しました。
実際に利用するケースはあまりないですが、どうしても利用したい場合などは、こういった書き方を検討してみてください。

※1 色々書いてますが、ローカルを含めた全ての環境で同一イメージを利用することが望ましいです

※2 動作確認の都合上、docker-composeではなくdocker composeを使っています