Manage NEXT_PUBLIC Environment Variables at Runtime with Docker
Tags: docker, nextjs, react
Published: 2021-07-19 20:19:00

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:

```bash
NEXT_PUBLIC_API_URL=
```

What we can do on the Dockerfile, is something like that:

```bash
# 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:

```bash
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*

```bash
ENTRYPOINT ["/app/entrypoint.sh"]
```

This file contains a set of specific instructions:

```bash
#!/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`:

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

```bash
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](https://twitter.com/imarenny)

Seeya!