I suspect this problem won’t impact too many people. However, it caught me out so I figured I’d document it and share the fix.
Heads up: you’re probably missing a next.config.js
file. Or perhaps you have one, but it’s not got the right setting.
OK, I will first share my config:
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 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 during the build.
ENV NEXT_TELEMETRY_DISABLED 1
# If using npm comment out above and use below instead
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# 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
CMD ["node", "server.js"]
Code language: Dockerfile (dockerfile)
That’s my Dockerfile
in the root of my project.
For reference, that’s mostly taken from the NextJS with Docker example on GitHub.
I also have their .dockerignore
file in the project root:
*.sql
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
Code language: CSS (css)
When building a Docker image, Docker looks for files to include in the image based on the instructions in the Dockerfile
.
However, sometimes there may be files that are present in the directory where the Dockerfile is located that should not be included in the Docker image. These files could be temporary files, log files, or other files that are not necessary for the application to run in a Docker container.
To exclude these files from being included in the Docker image, you can use a .dockerignore
file. The purpose of a .dockerignore
file is to specify files and directories that should be excluded from the Docker build context, which is the set of files that Docker uses to build the image.
By listing files and directories in the .dockerignore
file, you can ensure that they are not included in the Docker build context and therefore not included in the Docker image. This can help reduce the size of the Docker image and improve the efficiency of the Docker build process.
The Dockerfile
actually looks a lot nicer in the IDE, thanks to the separators:
Keen eyed viewers will spot I have a couple of extra config lines in the screenshot versus the code sample. The reason for that is to make it so you can easily copy / paste the Dockerfile
code without my custom bits.
OK, so running a docker build
here gives me this:
Sending build context to Docker daemon 2.634MB
Step 1/25 : FROM node:18-alpine AS base
---> 2b1b2c7e0c10
Step 2/25 : FROM base AS deps
---> 2b1b2c7e0c10
Step 3/25 : RUN apk add --no-cache libc6-compat
---> Using cache
---> d831fbca573a
Step 4/25 : WORKDIR /app
---> Using cache
---> b9541cf0d6a7
Step 5/25 : COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
---> Using cache
---> 3ea2ca7f55a5
Step 6/25 : RUN if [ -f yarn.lock ]; then yarn --frozen-lockfile; elif [ -f package-lock.json ]; then npm ci; elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; else echo "Lockfile not found." && exit 1; fi
---> Using cache
---> fb63901dc487
Step 7/25 : FROM base AS builder
---> 2b1b2c7e0c10
Step 8/25 : WORKDIR /app
---> Using cache
---> cfebc28e6e0e
Step 9/25 : COPY --from=deps /app/node_modules ./node_modules
---> Using cache
---> 7ac119fc48d1
Step 10/25 : COPY . .
---> 557bced0a9cb
Step 11/25 : COPY ./.env.build ./.env.local
---> 48cf4f79364a
Step 12/25 : ENV NEXT_TELEMETRY_DISABLED 1
---> Running in 2b104ac949f8
Removing intermediate container 2b104ac949f8
---> 83599a076d59
Step 13/25 : RUN npm run build
---> Running in 324bc1f0535a
> highestpayingjobs.co.uk@0.2.0 build
> export NODE_OPTIONS=--openssl-legacy-provider; next build
info - Loaded env from /app/.env.local
info - Loaded env from /app/.env.production
info - Linting and checking validity of types...
error - ESLint: Failed to load config "next" to extend from. Referenced from: /app/.eslintrc
info - Creating an optimized production build...
info - Compiled successfully
info - Collecting page data...
info - Generating static pages (0/39)
info - Generating static pages (9/39)
info - Generating static pages (19/39)
info - Generating static pages (29/39)
info - Generating static pages (39/39)
info - Finalizing page optimization...
Route (pages) Size First Load JS
┌ ● / 9.11 kB 84.5 kB
├ /_app 0 B 73.3 kB
├ ● /[sector] 1.35 kB 76.7 kB
├ └ /cleaning
├ ● /[sector]/[job-role] (ISR: 10 Seconds) (118094 ms) 120 kB 195 kB
├ ├ /cleaning/cleaning-manager (19748 ms)
├ ├ /cleaning/cleaning-operative (15061 ms)
├ ├ /cleaning/cleaning-assistant (10876 ms)
├ ├ /cleaning/domestic (10119 ms)
├ ├ /cleaning/area-cleaning-manager (6038 ms)
├ ├ /cleaning/cleaning-account-manager (5286 ms)
├ ├ /cleaning/cleaning-contract-manager (3859 ms)
├ └ [+28 more paths] (avg 1682 ms)
├ ○ /404 181 B 73.5 kB
├ λ /api/hello 0 B 73.3 kB
└ λ /api/role-to-article 0 B 73.3 kB
+ First Load JS shared by all 78.6 kB
├ chunks/framework-2c79e2a64abdb08b.js 45.2 kB
├ chunks/main-a0dca5a2ff5035f1.js 26.8 kB
├ chunks/pages/_app-e90929023a77ab46.js 549 B
├ chunks/webpack-8fa1640cc84ba8fe.js 750 B
└ css/6f9369f01753e953.css 5.21 kB
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
(ISR) incremental static regeneration (uses revalidate in getStaticProps)
npm notice
npm notice New minor version of npm available! 9.3.1 -> 9.5.1
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v9.5.1>
npm notice Run `npm install -g npm@9.5.1` to update!
npm notice
Removing intermediate container 324bc1f0535a
---> cfb984673f22
Step 14/25 : FROM base AS runner
---> 2b1b2c7e0c10
Step 15/25 : WORKDIR /app
---> Using cache
---> cfebc28e6e0e
Step 16/25 : ENV NODE_ENV production
---> Using cache
---> 4ac8be51b6e8
Step 17/25 : ENV NEXT_TELEMETRY_DISABLED 1
---> Using cache
---> 535a61efc955
Step 18/25 : RUN addgroup --system --gid 1001 nodejs
---> Using cache
---> 466c15be1160
Step 19/25 : RUN adduser --system --uid 1001 nextjs
---> Using cache
---> 84726b291022
Step 20/25 : COPY --from=builder /app/public ./public
---> Using cache
---> a9952793e68b
Step 21/25 : COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY failed: stat app/.next/standalone: file does not exist
make: *** [Makefile:2: docker_build] Error 1
Code language: Shell Session (shell)
Again, pretty hard to read as plain text. It doesn’t look that much better in the terminal to be fair.
The crucial bit is the last step – the failed step – step 21 of 25:
Step 21/25 : COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY failed: stat app/.next/standalone: file does not exist
make: *** [Makefile:2: docker_build] Error 1
Code language: JavaScript (javascript)
And that’s not really super helpful, is it?
Because it’s not saying: hey, you dun goofed here and forgot to provide a next.config.js
file, or that you did and the config you gave was wrong.
The Fix
I couldn’t find anything to tell me to do this in the official NextJS docs for deploying with Docker. It’s almost as if they would much prefer it if you deployed to Vercel… who would have thought it?
Actually, I have used Vercel to deploy NextJS before and it is absolutely brilliant. But I digress.
The fix here is to provide the necessary config in your next.config.js
file:
// next.config.js
module.exports = {
// ... rest of the configuration.
output: "standalone",
};
Code language: JavaScript (javascript)
In my case I didn’t even have this file in my project. It needs to live in the project root, by the way, next to where you have your Dockerfile
and .dockerignore
files.
With that, your project should build. Hopefully.
At least, mine did.