はじめに
最近,バックエンドにGo,フロントエンドにTypeScriptを採用したWebアプリケーションを作っているのですが,これをDockerコンテナ化するときに,どのようなDockerfileを書けばいいのか,少し迷ってしまいました.
どういう方法でDockerコンテナ化したのかを記録しておこうと思います.
Dockerのマルチステージビルドとは
前述の通り,バックエンドとフロントエンドでは別の言語を使用しているため,別々のランタイムを準備してビルドを行う必要があります.愚直にやるのであれば,Ubuntu等のイメージにGoとNode.jsのランタイムを入れてビルドするのがいいのでしょうが,これらはビルド時以外には使用しません.Goは最終的にバイナリファイルを1個吐き出すだけですし,TypeScriptのフロントエンドはビルドして静的ファイルとなります.従って,最終的なイメージには,使用しないランタイムが含まれてしまい,無駄が生じてしまいます.それに,使用するのであれば,最初からGoやNode.jsのイメージを使ってしまったほうがDockerfileもシンプルになります.この方法を実現するのがマルチステージビルドです.
docs.docker.com
Dockerfileの書き方
Dockerfileの一例はこのようになります.
FROM golang:latest AS build-backend WORKDIR /go/src COPY main.go main.go COPY go.mod go.mod COPY go.sum go.sum RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM node:lts AS build-frontend WORKDIR /root COPY ./frontend/package.json . RUN npm i COPY ./frontend/src/ ./src/ COPY ./frontend/public/ ./public/ COPY ./frontend/tsconfig.json . COPY ./frontend/yarn.lock . RUN npm run build FROM alpine:latest LABEL maintainer="maintainer@example.com" WORKDIR /root/ COPY --from=build-backend /go/src/app . COPY --from=build-frontend /root/build/ ./frontend/build/ EXPOSE 8080 CMD [ "./app" ]
ディレクトリの構造は,アプリケーションのルートディレクトリにmain.go,frontendディレクトリの中にTypeScriptのプロジェクトという構成です.
FROM
から次のFROM
の直前までが一つのステージとなります.ステージにはFROM image AS name
というようにして名前をつけることができます.これはオプションで指定するものですが,後述のファイルのコピーの際に便利になるので,名前をつけたほうがいいと思います.
各ステージでは,依存関係のインストール,アプリケーションのビルド等を行い,成果物を生成します.
一番最後のステージはアプリケーションの実行用のイメージとなります.ここでは,前のステージで生成された成果物をコピーしています.コピーはCOPY [src] [dist]
というように書きますが,--from=[src]
というオプションが使用できます.ここで,COPY --from=build-backend /go/src/app .
というように指定することで,build-backendステージの/go/src/appをコピーしてくることができます.このようにすることで,ランタイムは含めずに,成果物のみを含んだイメージを生成することができます.
おわりに
マルチステージビルドという機能自体は,dotnetのアプリケーションをDockerコンテナ化する方法を調べたときに見かけて知ってはいましたが,今回すぐには思い出せませんでした.
docs.microsoft.com
マルチステージビルドという方法があることを思い出して,使ってみたところ,かなりいい感じだったので良かったです.