Handling Dynamic Environment Variables in Nextjs Docker Images.

Handling Dynamic NEXT_PUBLIC Variables in Next.js Docker Images

Avatar
Pavan Bhaskar
4 Oct, 2025
dockerdflowNextjs

When deploying a Next.js app using a Docker image, you might notice that changes to your NEXT_PUBLIC_* environment variables don’t take effect even after rebuilding the container.

That’s because Next.js embeds all public environment variables directly into the build output. Once your Docker image is built, those values become static and can’t be changed at runtime.
We ran into this issue while working on dFlow, where several features depend on NEXT_PUBLIC variables. After exploring different approaches, here’s what we found 👇

React Context Approach

One solution is to pass environment variables from the server to the client through React Context.


We can fetch them server-side, pass them as props to a provider, and use them across the app.

  1. Let's first create a React Context which we can wrap our entire layout.


1// EnvironmentVariablesProvider.tsx
2'use client'
3
4import React, { createContext, use } from 'react'
5
6import { Branding } from '@/payload-types'
7
8type VariablesType = {
9 NEXT_PUBLIC_WEBSITE_URL: string
10}
11
12const VariablesContext = createContext<
13 {environmentVariables: VariablesType}
14>(undefined)
15
16// showing toaster when user goes to offline & online
17export const useVariablesContext = ({
18 children,
19 environmentVariables,
20}: {
21 children: React.ReactNode
22 environmentVariables: EnvironmentVariablesType
23}) => {
24 return (
25 <VariablesContext.Provider value={{ environmentVariables }}>
26 {children}
27 </VariablesContext.Provider>
28 )
29}
30
31export const useVariablesContext = () => {
32 const context = use(VariablesContext)
33
34 if (context === undefined) {
35 throw new Error(
36 'useVariablesContext must be used within a useVariablesContext',
37 )
38 }
39
40 return context
41}


  1. Now let's warp our layout.tsx with context.
1// app/layout.tsx
2import { EnvironmentVariablesProvider } from '@/providers/EnvironmentVariablesProvider'
3
4export default async function RootLayout({
5 children,
6}: {
7 children: React.ReactNode
8}) {
9 return (
10 <html>
11 <body>
12 <EnvironmentVariablesProvider environmentVariables={{
13 NEXT_PUBLIC_WEBSITE_URL: process.env.NEXT_PUBLIC_WEBSITE_URL
14 }}>{children}</EnvironmentVariablesProvider>
15 </body>
16 </html>
17
18 )
19}

This makes your environment variables accessible across the app

1// use client
2import { useVariablesContext } from '@/providers/EnvironmentVariablesProvider'
3import Logo from '@/components/Logo'
4
5const Navbar = () => {
6 const { environmentVariables } = useVariablesContext()
7
8 return (
9 <nav>
10 <Link href={environmentVariables.NEXT_PUBLIC_WEBSITE_URL}>
11 <Logo />
12 </Link>
13 </nav>
14 )
15}
16
17export default Navbar

However, there’s a catch — using React Context forces components to be client-side, which means you lose server-side rendering (SSR) benefits.

Shell Script Approach (Recommended)

After trying several methods, we found a cleaner and more scalable solution (inspired by the Nhost repo).

  1. We can replace environment variables dynamically at container startup using a simple shell script.
1# Dockerfile
2
3FROM node:22.12.0-alpine AS base
4
5# Install dependencies only when needed
6FROM base AS deps
7# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
8RUN apk add --no-cache libc6-compat
9WORKDIR /app
10
11# Install dependencies based on the preferred package manager
12COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
13RUN \
14 if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
15 elif [ -f package-lock.json ]; then npm ci; \
16 elif [ -f pnpm-lock.yaml ]; then npm install -g corepack@latest && corepack enable && corepack prepare pnpm@10.2.0 --activate && pnpm i --frozen-lockfile; \
17 else echo "Lockfile not found." && exit 1; \
18 fi
19
20# Rebuild the source code only when needed
21FROM base AS builder
22WORKDIR /app
23COPY --from=deps /app/node_modules ./node_modules
24COPY . .
25
26ARG NEXT_PUBLIC_WEBSITE_URL
27
28ENV NEXT_PUBLIC_WEBSITE_URL=__NEXT_PUBLIC_WEBSITE_URL__
29
30RUN \
31 if [ -f yarn.lock ]; then yarn run build; \
32 elif [ -f package-lock.json ]; then npm run build; \
33 elif [ -f pnpm-lock.yaml ]; then corepack enable && COREPACK_INTEGRITY_KEYS=0 corepack prepare pnpm@10.2.0 --activate && pnpm run build; \
34 else echo "Lockfile not found." && exit 1; \
35 fi
36
37# Production image, copy all the files and run next
38FROM base AS runner
39WORKDIR /app
40
41ENV NODE_ENV=production
42# Uncomment the following line in case you want to disable telemetry during runtime.
43ENV NEXT_TELEMETRY_DISABLED=1
44
45RUN addgroup --system --gid 1001 nodejs
46RUN adduser --system --uid 1001 nextjs
47
48
49COPY --from=builder /app/public ./public
50
51# Automatically leverage output traces to reduce image size
52# https://nextjs.org/docs/advanced-features/output-file-tracing
53COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
54COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
55COPY scripts/entrypoint.sh /app/entrypoint.sh
56RUN chmod +x /app/entrypoint.sh
57
58USER root
59EXPOSE 3000
60
61ENV HOSTNAME="0.0.0.0"
62ENTRYPOINT ["/app/entrypoint.sh"]
  1. Now let's create entrypoint.sh which will replace the variables with dynamic values
1// scripts/entrypoint.sh
2#!/bin/sh
3set -e
4
5# 🔁 Replace placeholders in built output
6NEXT_PUBLIC_WEBSITE_URL="${NEXT_PUBLIC_WEBSITE_URL}"
7
8# 🪄 Replace values in built static files
9find .next -type f -exec sed -i "s~__NEXT_PUBLIC_WEBSITE_URL__~${NEXT_PUBLIC_WEBSITE_URL}~g" {} +
10
11# Run your Next.js app
12exec node server.js

Now, whenever you run the container, the shell script will dynamically inject your latest environment variables into the built files before starting the app.

Why This Works Well

  • No code changes are required in your app
  • Environment variables are updated each time the container starts
  • Works perfectly in CI/CD environments
  • Keeps your Next.js build process unchanged

That’s it for this post!
Thanks for reading — stay tuned and peace ✌️

dFlow logodFlow

dFlow simplifies cloud deployments with powerful tools for managing servers, services and domains.

© 2025 dFlow. All rights reserved.
Handling Dynamic NEXT_PUBLIC Variables in Next.js Docker Images | dFlow