Manage NEXT_PUBLIC Environment Variables at Runtime with Docker

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!

51