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:
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
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
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