どうしてやりたかったか?
Next.js + typescript + Prisma2 + PostgreSQL でアプリケーションを作っていて、PostgreSQL は Docker で動かしていたので、合わせて docker-compose したかった。で、Deploy も docker で動かしたかった。
Production の PostgreSQL は、Docker 使わないけど、staging サーバ等では、Docker でも構わなかったので、docker-compose で、そのまま行くことにした。
ネットで調べたところ、開発環境用のものはいくつか見つかったが、Product Build 用のものは見当たらなかった。みんな vercel なの??
検証環境
- OS: MacOS Catalina Version 10.15.7(19H1615)
- node: v16.13.1
- yarn: 1.22.15
- docker: Docker version 20.10.11, build dea9396
- docker-compose: Docker Compose version v2.2.1
公式ドキュメントによると
with-docker の example を clone して build/run しろとある。気持ちは分かるけど、そうじゃない。
example は、github で見れるので、Dockerfile だけ拝借することにする。
Dockerfile は、こうなっている
# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry.
# ENV NEXT_TELEMETRY_DISABLED 1
CMD ["node", "server.js"]
- yarn install する
- build する
- 必要なものだけコピーして node server.js する
シンプル。特別なことはやってなさそう。
準備
まずは適当な Next.js アプリを作って、それで検証する。
$ yarn create next-app app --typescript
yarn dev, yarn build, yarn start 全部 OK。
ひとまず、Dockerfile で動かしてみたいので、先程の Dockerfile を置く。node_modules とかはコピーする必要がない(コピーしても無駄な上時間がかかる)ので、.dockerignore に。
Dockerfile
.dockerignore
node_modules
README.md
.next
とりあえず実行、そして失敗
"/app/.next/standalone" not found: not found
と言われてしまう。なんでやねん。
yarn build したときに出来た .next の中に standalone がないらしい。
手元で build したときに出来た .next の中にも確かにない。
対応、だがしかし
standalone は、experimental feature のようなので、next.config.js
に追加すれば良い。Product Build でだけ experimental feature 使うの?という気もするけど。
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
experimental: {
outputStandalone: true,
},
}
再度 docker build すると、成功する。そして、docker run もできる。Welcome to Next.js! も表示される。でも、エラーが出る。
$ docker run -p 3000:3000 nextjs-docker
Listening on port 3000
Error: Cannot find module './image-optimizer'
Require stack:
- /app/node_modules/next/dist/server/next-server.js
- /app/server.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:94:18)
at Object.fn (/app/node_modules/next/dist/server/next-server.js:609:49)
at Router.execute (/app/node_modules/next/dist/server/router.js:222:48)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Server.run (/app/node_modules/next/dist/server/next-server.js:1135:29)
at async Server.handleRequest (/app/node_modules/next/dist/server/next-server.js:325:20)
at async Server.<anonymous> (/app/server.js:18:5) {
code: 'MODULE_NOT_FOUND',
requireStack: [
'/app/node_modules/next/dist/server/next-server.js',
'/app/server.js'
]
}
image-optimizer
がないとのこと。最終的に実行されているのは、.next/standalone
の node_modules
なので、手元で .next/standalone/node_modules/next/dist/server
を確認してみるも、確かに image-optimizer.js
はない。
なんでー。
バグだった
Using component with-docker results in missing image-optimizer #32513
v12.0.8-canary.7
以上で対応済みとのこと。
現在の stable version は、 12.0.7 なので、しばし待て。ということか。
修正内容を見ると image-optimizer.js とそれを正しく動かすための sharp を含めれば良さそうだが、sharp なんてものはインストールされていないし、そう単純でもなさそうな気配がする。
対応方針
image-optimizer.js
は、next/image
API を利用する際に呼び出される。
考えられる方針は、以下3つくらい?
-
v12.0.8-canary.7
以上を使う(本日時点の最新は、v12.0.8-canary.19
) -
next/image API
の利用をやめる -
image-optimizer.js
の dependencies をひたすらコピーする
3つ目は、後で Dockerfile を編集するの嫌だし、途中まで試みたけど、切りがないのでやめる。
v12.0.8-canary.7
以上を使う
マイナーバージョンアップの canary だし、別に良っか。という方針。next/image
API を利用している場合は、こっち。v12.0.8
が出たら忘れずに直すこと。
{
...
"dependencies": {
"next": "12.0.8-canary.19",
...
},
...
}
next/image API の利用をやめる
潜在的エラーなので、微妙だけど、next/image
使わないよって場合は、こっち。今回の例では、app/pages/index.tsx
内で使っているので、そこを消してしまう。
import type { NextPage } from 'next'
import Head from 'next/head'
//import Image from 'next/image'
import styles from '../styles/Home.module.css'
const Home: NextPage = () => {
return (
<div className={styles.container}>
...
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
{/*<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>*/}
</a>
</footer>
</div>
)
}
export default Home
docker-compose に組み込む
Next.js のアプリの中に、Dockerfile を置いておくのは個人的に気持ち悪いので、以下のような配置とする。Dockerfile は他でも書くかも知れないので、名前を app に変更。.dockerignore は…ま、いっか。
.
├── Dockerfiles
│ └── app
├── app (Next.js アプリ)
├── README.md
└── docker-compose.yml
version: '3.8'
services:
app:
build:
context: ./app
dockerfile: ../Dockerfiles/app
ports:
- "3000:3000"
$ docker-compose build
$ docker-compose up -d
OK!
まとめ
本日時点で Next.js の Product Build を docker-compose で動かす手順をまとめると、
いくつか微妙な点はあるが(experimental feature 使ったり、canary 使ったり)
- with-docker example の Dockerfile を拝借
- next.config.js に standalone を追加
- next.js のバージョンを v12.0.8-canary.8 以上にするか、next/image API の使用をやめる
- docker-compose に組み込む
となる。Next.js のバージョンも変更できないし、next/image
API も使ってる!という場合は、build して yarn start するような Dockerfile に書き換えれば良いんじゃないかと。
おしまい。