Next.jsで作ったブログアプリ用にDockerFileを作成し、ECRにアップロードしてみた

#Docker
#Dockerfile
#Next.js
#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というフォルダを作ってまとめました

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 --from=base /base ./ RUN NODE_ENV=production npm run build FROM node:18-alpine AS production ENV NODE_ENV=production WORKDIR /app COPY --from=build /build/package.json /build/package-lock.json ./ COPY --from=build /build/.next ./.next COPY --from=build /build/public ./public COPY --from=build /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を確認できます

version: "3" services: nextjs: container_name: nextjs-app-container image: next_prod:latest ports: - "3000:3000"

ローカルでイメージを起動できるか確認用です

{ "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に失敗してしまいました、この辺は今一つよく分からないので宿題

$ 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

権限持ったprofileは別途用意しておいてください

$ aws ecr create-repository \ --repository-name blog_app \ --profile <profile name>
$ 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
$ docker image tag next_prod:latest <AWS account>.dkr.ecr.ap-northeast-1.amazonaws.com/blog_app:latest
$ docker image push <AWS account>.dkr.ecr.ap-northeast-1.amazonaws.com/blog_app:latest
$ cd blog_app $ docker-compose -f dockerfiles/docker-compose.yml up -d

起動後、3000番ポートでブラウザを開くことができました

ログを見たい場合は-dを除けばフォアグラウンドで動くのでコンソールから確認できます

雑ですがこんな感じ、

 

next dev: 開発環境でサーバー起動

next build: 本番用に最適化した成果物(.next)を作成

next start: 本番用モードでサーバー起動、起動するにはbuildを済ませて.nextをbuildを済ませておくことが必要