Next.jsで作ったブログアプリ用にDockerFileを作成し、ECRにアップロードしてみた
本日のアジェンダ(アジェンダと言ってみたかっただけ)
- ・Nextjsアプリ用のDockerfileを作成
- ・DockerFileからDockerImageを作成
- ・DockerImageをECRにアップロード
ブログアプリをVercelで管理しているためECSなどを使った場合のデプロイ方法が気になって調べてみました
まずはDockerFileからDockerImageを作ってECRにアップロードまでやってみましたので備忘録として残しておきます
Docker関連はまだ勉強中なので間違いなどはあるかと思いますが悪しからず
ディレクトリ構成
blog_app/ ├ components ├ dockerfiles/ │ ├ docker-compose.yml │ └ Dockerfile ├ node_modules ├ pages ├ public ├ package-lock.json └ package.json
Dockerfileを読み込む際、同じ階層のファイルを全て検索してしまうみたいなのでDocker関連のファイルはdockerfilesというフォルダを作ってまとめました
ソースコード
dockerfiles/Dockerfile
FROM node:18-alpine AS base WORKDIR /base COPY . . RUN npm install --omit=dev RUN npm install sharp FROM base AS build WORKDIR /build COPY /base ./ RUN NODE_ENV=production npm run build FROM node:18-alpine AS production ENV NODE_ENV=production WORKDIR /app COPY /build/package.json /build/package-lock.json ./ COPY /build/.next ./.next COPY /build/public ./public COPY /build/node_modules ./node_modules RUN chown -R node /app USER node EXPOSE 3000 CMD ["npm", "run", "start"]
FROMが複数ありますが、マルチステージビルドという手法です
Docker 17.05以降で使えるらしく、段階をつけてイメージをビルドすることで最終的な成果物のイメージを最小化することが目的です
npm install --omit=devとすることでpackage.jsonのdevDependenciesを除外してnpm_modulesを作成するようです(--productionでbuildかけたところ変更するようにwarningされました)
npm install sharpはインストールするとNextjsの画像imageなどを本番環境用に最適化してくれるようです(Vercelでは勝手にインストールしてくれるらしい)、インストールせずにbuildしたところwarningログが出ました
USERを特に設定せずにbuildし、imageを起動するとrootユーザーとして実行されていました
細かいことはまだよく分かっていないのですが、セキュリティ的に問題があるようです
Userを作ってないのに「USER node」としているのは、すべての公式Node.jsイメージではnodeというLinuxユーザーが追加されているためのようです
ユーザーを変更しただけでもbuildは通り、画面表示までできましたが、app以下のファイルを編集できないため、このままページ遷移など行うとcacheを書き込めないなどのerrorログが出てきたので「RUN chown -R node /app」を追加しました
$ docker-compose -f dockerfiles/docker-compose.yml exec nextjs sh # whoami
コンテナ内に入ってwhoamiで実行Userを確認できます
dockerfiles/docker-compose.yml
version: "3" services: nextjs: container_name: nextjs-app-container image: next_prod:latest ports: - "3000:3000"
ローカルでイメージを起動できるか確認用です
package.json
{ "name": "learn-starter", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "eslint ./src/**/*.ts && prettier --write ./src" }, "dependencies": { "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "@material-ui/system": "^4.12.2", "autoprefixer": "^10.4.2", "date-fns": "^2.28.0", "gray-matter": "^4.0.3", "next": "^12.1.0", "postcss": "^8.4.6", "react": "17.0.2", "react-clipboard-button": "^0.0.7", "react-dom": "17.0.2", "react-hot-toast": "^2.2.0", "react-markdown": "^8.0.1", "react-syntax-highlighter": "^15.5.0", "remark-gfm": "^3.0.1", "tailwindcss": "^3.0.23", "@types/gtag.js": "^0.0.8", "@types/node": "^17.0.20", "eslint-config-next": "^12.1.0", "eslint-config-prettier": "^8.4.0", "eslint": "^8.9.0", "typescript": "^4.5.5" }, "devDependencies": { "@types/react": "^17.0.39", "@types/react-syntax-highlighter": "^15.5.1", "prettier": "^2.5.1" } }
typescript,@types/nodeなどはdevDependenciesに置くものだと思っていたのですが、何故かdependenciesに置かないとbuildに失敗してしまいました、この辺は今一つよく分からないので宿題
Dockerイメージの作成
$ cd blog_app $ docker build --rm -t next_prod:latest -f dockerfiles/Dockerfile .
docker buildコマンドでDockerfileを元にイメージを作成します
--rm: 構築成功後に中間コンテナを削除
-t: <name>:<tag>の形式でタグづけする
-f: Dockerfileの名前、パスを設定できる
.: カレントディレクトリを指定してbuildを行う
imageが出来ているか確認
$ docker images
ECRへアップロード
1.ECRにリポジトリを作成
権限持ったprofileは別途用意しておいてください
$ aws ecr create-repository \ --repository-name blog_app \ --profile <profile name>
2.ECRにログイン
$ aws ecr get-login-password --region ap-northeast-1 \ --profile <profile name> \ | docker login --username AWS \ --password-stdin <AWS account>.dkr.ecr.ap-northeast-1.amazonaws.com
3.タグ付け
$ docker image tag next_prod:latest <AWS account>.dkr.ecr.ap-northeast-1.amazonaws.com/blog_app:latest
4.ECRへimageをpush
$ docker image push <AWS account>.dkr.ecr.ap-northeast-1.amazonaws.com/blog_app:latest
DockerComposeを使ってローカルで起動する
$ cd blog_app $ docker-compose -f dockerfiles/docker-compose.yml up -d
起動後、3000番ポートでブラウザを開くことができました
ログを見たい場合は-dを除けばフォアグラウンドで動くのでコンソールから確認できます
補足Next.jsのbuildコマンドについて
雑ですがこんな感じ、
next dev: 開発環境でサーバー起動
next build: 本番用に最適化した成果物(.next)を作成
next start: 本番用モードでサーバー起動、起動するにはbuildを済ませて.nextをbuildを済ませておくことが必要