Strapi を開発環境と本番環境のDockerで動かしたい【その2】

【その1】では開発環境の Docker で Strapi を動かすことができました。

今回は、本番環境向けにデプロイするために、Next.js プロジェクト同様にマルチステージビルドができないかどうかを試していこうと思います。

開発環境向けのDockerfile

開発環境向けのDockerfileは以下のようになっています。

dev.Dockerfile
FROM node:16
# Installing libvips-dev for sharp Compatability
RUN apt-get update && apt-get install libvips-dev -y
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install
WORKDIR /opt/app
COPY ./ .
RUN yarn build
EXPOSE 1337
CMD ["yarn", "develop"]

これを元にDockerイメージを作成した場合、できあがったイメージは2.61GBあります。

$ docker image ls

REPOSITORY                 TAG       IMAGE ID       CREATED        SIZE
donuts-strapi-image        latest    4ecbe41a55a9   27 hours ago   2.61GB

かなり大きいなと感じます。Next.js プロジェクトを本番環境向けにデプロイしたイメージは約120MBという軽さだったので(多分これが軽すぎるのもある)、マルチステージビルドがうまくできれば軽くなるんじゃないかなというので試してみようと思います。

マルチステージビルドを利用した形を考える

今回は本番環境とほぼ同じ環境で構築するステージング環境用のファイルで試してみます。
stg.Dockerfileを用意します。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# Dockerfile を作成する
$ touch stg.Dockerfile

Node.js を軽量版にしてみる(失敗😢)

まずはNode.jsalpine(軽量版)に変更してうまくできるかを試します。
alpine版を利用する場合には不足しがちなlibc6-compatも追加します。

stg.Dockerfile
FROM node:16-alpine
RUN apk add --no-cache libc6-compat

# Installing libvips-dev for sharp Compatability
RUN apt-get update && apt-get install libvips-dev -y
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install
WORKDIR /opt/app
COPY ./ .
RUN yarn build
EXPOSE 1337
CMD ["yarn", "develop"]
# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# Docker イメージを作成する
$ docker build -f stg.Dockerfile -t donuts-strapi-image-staging .

エラーが出ました。軽量版ではapt-getがないのでダメそう。

 > [2/8] RUN apt-get update && apt-get install libvips-dev -y:
#6 0.344 /bin/sh: apt-get: not found

deps ステージと builder ステージを作る

Node.js の alpine 版を利用した方法ではうまく依存ライブラリをインストールすることができませんでした。

次に、依存ライブラリをインストールするためのdepsステージとビルドを担当するbuilderステージに分けてみます。

stg.Dockerfile
### deps ステージ ###
FROM node:16 AS deps
RUN apt-get update && apt-get install libvips-dev -y
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install

### builder ステージ ###
FROM node:16-alpine AS builder

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/app

# deps ステージでインストールしたライブラリをコピーする
COPY --from=deps /opt/node_modules ./node_modules
COPY . .
RUN yarn build

EXPOSE 1337
CMD ["yarn", "develop"]

この Dockerfileを元にビルドしてみます。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# Docker イメージを作成する
$ docker build -f stg.Dockerfile -t donuts-strapi-image-staging .

# イメージを確認する
REPOSITORY                    TAG       IMAGE ID       CREATED              SIZE
donuts-strapi-image-staging   latest    e88ac351b4a3   About a minute ago   467MB

おお。これがちゃんと動くかどうかですね。

前回の方法1のやり方で、MySQLのコンテナを作成して、そこに Strapi コンテナを接続します。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# ネットワークを作成する
$ docker network create donuts-strapi-network-staging

# MySQLのコンテナを作成する
# docker run --name コンテナ名 -dit --net=ネットワーク名 -p ポートの指定 環境の設定
$ docker run --name donuts-strapi-mysql -dit --net=donuts-strapi-network-staging -p 3306:3306 -e MYSQL_ROOT_PASSWORD=donuts-strapi -e MYSQL_DATABASE=donuts-strapi -e MYSQL_USER=donuts-strapi -e MYSQL_PASSWORD=donuts-strapi mysql --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password

# コンテナを確認
$ docker container ls

CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS          PORTS                                         NAMES
101a871614aa   mysql            "docker-entrypoint.s…"   11 seconds ago   Up 11 seconds   0.0.0.0:3306->3306/tcp, 33060/tcp             donuts-strapi-mysql

# Strapi のコンテナを作成
# docker run -p ポート --net=ネットワーク名 --name コンテナの名前 元になるイメージ
$ docker run -p 1337:1337 --net=donuts-strapi-network-staging --name donuts-strapi donuts-strapi-image-staging

yarn run v1.22.19
$ strapi develop
Starting the compilation for TypeScript files in /opt/app
[2022-09-22 14:49:33.663] debug: ⛔️ Server wasn't able to start properly.
[2022-09-22 14:49:33.665] error: Could not load js config file /opt/app/node_modules/@strapi/plugin-upload/strapi-server.js: 
Something went wrong installing the "sharp" module

Cannot find module '../build/Release/sharp-linuxmusl-x64.node'

だめですね。

以下のようにalpineではないものを利用して同様にやってみるとうまくいきました。

stg.Dockerfile
### deps ステージ ###
FROM node:16 AS deps
RUN apt-get update && apt-get install libvips-dev -y
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install

### builder ステージ ###
FROM node:16 AS builder

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/app

# deps ステージでインストールしたライブラリをコピーする
COPY --from=deps /opt/node_modules ./node_modules
COPY . .
RUN yarn build

EXPOSE 1337
CMD ["yarn", "develop"]
# イメージを作り直す
$ docker build -f stg.Dockerfile -t donuts-strapi-image-staging .

# イメージを確認
$ docker image ls

REPOSITORY                    TAG       IMAGE ID       CREATED          SIZE
donuts-strapi-image-staging   latest    af335accb0cf   3 minutes ago    1.26GB

# コンテナを起動
$ docker run -p 1337:1337 --net=donuts-strapi-network-staging --name donuts-strapi donuts-strapi-image-staging

無事に起動しています。とりあえずSIZEは半分になりましたね!

runner ステージを作る

depsステージとbuilderステージでのマルチステージビルドには無事成功しました。

次に builderステージをさらにrunnerステージに分けてみようと思います。

以下のように分けてみました。

stg.Dockerfile
### deps ステージ ###
FROM node:16 AS deps
RUN apt-get update && apt-get install libvips-dev -y
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install

### builder ステージ ###
FROM node:16 AS builder

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /opt/app

# deps ステージでインストールしたライブラリをコピーする
COPY --from=deps /opt/node_modules ./node_modules
COPY . .
RUN yarn build

### runner ステージ ###
FROM node:16 AS runner

WORKDIR /opt/app

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 strapi

COPY --from=builder /opt/app/public ./public
COPY --from=builder /opt/app/package.json ./package.json
COPY --from=builder /opt/app/favicon.ico ./favicon.ico

# builder で生成された dist ディレクトリをコピー
COPY --from=builder --chown=strapi:nodejs /opt/app/dist ./
COPY --from=builder --chown=strapi:nodejs /opt/app/node_modules ./node_modules
COPY --from=builder --chown=strapi:nodejs /opt/app/.env ./.env

EXPOSE 1337

ENV PORT 1337

CMD ["yarn", "start"]

ビルドしてみます。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# Dockerイメージを作成
# docker build -f Dockerfile のファイル名 -t 作成するするイメージ名 もとになるDockerfileの場所
$ docker build -f stg.Dockerfile -t donuts-strapi-image-staging .

# イメージを確認する
$ docker image ls

REPOSITORY                    TAG       IMAGE ID       CREATED          SIZE
donuts-strapi-image-staging   latest    8e4375bfe733   17 seconds ago   1.26GB

# コンテナを起動
$ docker run -p 1337:1337 --net=donuts-strapi-network-staging --name donuts-strapi donuts-strapi-image-staging

node_modulesをそのまま持っていくしかないので、特にサイズは小さくなりません。Next.js の standalone すごいんだなと思いました。

余計なものを持ち込まないという意味ではrunnerステージはあったほうがいい気がするので、このまま行きます。

本番環境向けとは

本番環境向けにNODE_ENV=productionに設定してビルドするとデータ構造の変更等ができない状態になるそうです。

以下の画像の右上にデータ構造を変更するボタンが見当たりません。

本番環境向けへのビルド方法について色々と勉強させていただきました。

本番環境のDockerfileを作成する

ステージング環境と同様に本番環境用のprod.Dockerfileを作成します。

# 作業ディレクトリに移動する
$ cd ~/StrapiProjects/donuts-strapi

# Dockerfile を作成する
$ touch prod.Dockerfile
prod.Dockerfile
### deps ステージ ###
FROM node:16 AS deps
RUN apt-get update && apt-get install libvips-dev -y
ENV NODE_ENV=production
WORKDIR /opt/
COPY ./package.json ./yarn.lock ./
ENV PATH /opt/node_modules/.bin:$PATH
RUN yarn config set network-timeout 600000 -g && yarn install

### builder ステージ ###
FROM node:16 AS builder

ENV NODE_ENV=production

WORKDIR /opt/app

# deps ステージでインストールしたライブラリをコピーする
COPY --from=deps /opt/node_modules ./node_modules
COPY . .
RUN yarn build

### runner ステージ ###
FROM node:16 AS runner

WORKDIR /opt/app

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 strapi

COPY --from=builder /opt/app/public ./public
COPY --from=builder /opt/app/package.json ./package.json
COPY --from=builder /opt/app/favicon.ico ./favicon.ico

COPY --from=builder --chown=strapi:nodejs /opt/app/dist ./
COPY --from=builder --chown=strapi:nodejs /opt/app/node_modules ./node_modules
COPY --from=builder --chown=strapi:nodejs /opt/app/.env ./.env

EXPOSE 1337

ENV PORT 1337

CMD ["yarn", "start"]

おわりに

今回は本番環境のDockerfileの作成を行いました。
これまでは、MySQLのコンテナを適当に作成してそのコンテナに接続を行なっていました。
データベース自体は Strapi に完全依存しない方がバックアップが取りやすいのかな〜とかぼんやり考えています。

その辺も含めて、次回はデータベースとの接続について考えていこうと思います。