Next.js is definitely a very good solution for making modern web applications, it's fast, simple, and reliable. It works very well also with Docker, you can build a production image with a few lines of Dockerfile, and deploy your app to the world.
However, there is a problem: when you build your docker image, and your app requires some client-side environment variables, (the famous NEXT_PUBLIC_) env vars, these variables will be set during build time, and you will no longer have a way to change them.
Well, a quite tricky solution is to do the variable replace directly on runtime as docker image entrypoint! Let's see an example:
Suppose you have to set up an API_URL endpoint for your client, obviously, you will set up something like that:
NEXT_PUBLIC_API_URL=
What we can do on the Dockerfile, is something like that:
# Install dependencies only when needed
FROM node:14-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 package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM node:14-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN NEXT_PUBLIC_API_URL=APP_NEXT_PUBLIC_API_URL npm run build
# Production image, copy all the files and run next
FROM node:14-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/entrypoint.sh ./entrypoint.sh
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
RUN chown -R nextjs:nodejs /app/.next
USER nextjs
EXPOSE 3000
RUN npx next telemetry disable
ENTRYPOINT ["/app/entrypoint.sh"]
CMD npm run start
This is a common Next.js dockerfile, but attention must be payed to this row:
RUN NEXT_PUBLIC_API_URL=APP_NEXT_PUBLIC_API_URL npm run build
The build will be launched with an environment placeholder in this row, so your API_URL will be temporarily set to a string with value: APP_NEXT_PUBLIC_API_URL.
After the image build, we set a custom entrypoint called entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]
This file contains a set of specific instructions:
#!/bin/sh
echo "Check that we have NEXT_PUBLIC_API_URL vars"
test -n "$NEXT_PUBLIC_API_URL"
find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#APP_NEXT_PUBLIC_API_URL#$NEXT_PUBLIC_API_URL#g"
echo "Starting Nextjs"
exec "$@"
When the docker image starts, the entrypoint will replace all the previously set environment placeholders, with the real values, passed by the NEXT_PUBLIC_API_URL environment variable!
So you can pass your value directly for example in your docker-compose.yml
:
version: "3.7"
services:
ui:
image: ghcr.io/useaurora/aurora/aurora
ports:
- "3000:3000"
environment:
NEXT_PUBLIC_API_URL: http://localhost:5000
Or also in your command line interface:
docker run -e NEXT_PUBLIC_API_URL="http://localhost:5000" ghcr.io/useaurora/aurora/aurora
This is all you need to do to accomplish this solution!
A couple of things to remember:
- This is a tricky solution, so use it if you don't have any other alternative.
- Using this technique, the image will be prepared on runtime, but if you need to change again the value, you need to delete the currently running container and run another, because the entrypoint will not find again the placeholder in the current container!
Thank you for reading this article, I really appreciate it. Please leave a reaction if the article helped you.
If you want you can follow me on Twitter
Seeya!
Top comments (8)
Awesome!!! Thanks a lot! Exactly what I've been looking for.
You're welcome!
Her Renato!
Today, after half a year of using this handmade adjustment of Next.js, I've discovered that now it takes way too long to replace each of my, say, 6 env vars across the significantly enlarged code base. I'm curious if you had a chance to improve the approach somehow or maybe go with something totally different.
Usually I've infrastructure team dealing with that and their config maps and so 😂
Either way I'm bookmarking this for my personal projects, thank you! 😀
You're welcome!
works like a charme :) thx
Hi @itsrennyman . Could you help me know which is the different between the above solution and using --build-arg in the build command?
Im using this like this. it seem working in my local app but it's worked when i deployed to the Azure Container App through azure devops
ARG node_value
ENV NODE_LOCAL_PORT ${node_value}
Wow Hacky but the only working solution, even with NextJS 14.1. Thank you!