DEV Community

Cover image for Manage NEXT_PUBLIC Environment Variables at Runtime with Docker
Renato Pozzi
Renato Pozzi

Posted on • Updated on

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=
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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 "$@"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Or also in your command line interface:

docker run -e NEXT_PUBLIC_API_URL="http://localhost:5000" ghcr.io/useaurora/aurora/aurora
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
nikitades profile image
nikitades

Awesome!!! Thanks a lot! Exactly what I've been looking for.

Collapse
 
itsrennyman profile image
Renato Pozzi

You're welcome!

Collapse
 
nikitades profile image
nikitades

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.

Collapse
 
joelbonetr profile image
JoelBonetR 🥇

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! 😀

Collapse
 
itsrennyman profile image
Renato Pozzi

You're welcome!

Collapse
 
christiandraeger profile image
Christian Dräger

works like a charme :) thx

Collapse
 
luxuryfi profile image
LuxuryFi • Edited

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}

Collapse
 
pleymor profile image
Adrien Laugueux

Wow Hacky but the only working solution, even with NextJS 14.1. Thank you!