Reputation: 2175
This question has been asked before, ive been trying plenty of examples over the past two days to try and configure with no luck so I am posting my environment for any help.
Problem
Nextjs environment variables are all undefined after deploying to kubernetes using Terraform
Expected Result
staging: NEXT_PUBLIC_APIROOT=https://apis-staging.mywebsite.com
production: NEXT_PUBLIC_APIROOT=https://apis.mywebsite.com
The secrets are stored in github actions. I have a terraform setup that deploys my application to my staging and production klusters, a snippet below:
env:
ENV: staging
PROJECT_ID: ${{ secrets.GKE_PROJECT_STAG }}
GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS_STAG }}
GKE_SA_KEY: ${{ secrets.GKE_SA_KEY_STAG }}
NEXT_PUBLIC_APIROOT: ${{ secrets.NEXT_PUBLIC_APIROOT_STAGING }}
I have an additional step to manually create a .env file as well
- name: env-file
run: |
touch .env.local
echo NEXT_PUBLIC_APIROOT: ${{ secrets.NEXT_PUBLIC_APIROOT_STAGING }} >> .env.local
Dockerfile
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json .npmrc ./
RUN npm ci
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:16-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
next.config.js
module.exports = withBundleAnalyzer({
publicRuntimeConfig: {
NEXT_PUBLIC_APIROOT: process.env.NEXT_PUBLIC_APIROOT,
},
output: 'standalone',
webpack: (config, { dev, isServer }) => {
if (dev && isServer) {
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
config.plugins.push(
new ForkTsCheckerWebpackPlugin({
eslint: {
files: './src/**/*.{ts,tsx,js,jsx}',
},
})
)
}
return config
},
})
Anybody have experience with this issue?
Upvotes: 6
Views: 2424
Reputation: 5898
I wanna start by saying that I am by no means an expert in NextJS. Therefore, I tried approaching your problem under the following assumptions:
next.config.js
mechanism that automatically loads environment variables from .env.local
us-central1-c
)My first step was to create a dummy NextJS application with a single API endpoint that simply prints one of the environment variables that I am trying to set when deploying the workload to Kubernetes. When it comes to the Dockerfile, I used the exact same image that you provided. Please find below the relevant files from my dummy app:
pages/api/test.js
export default function handler(req, res) {
res.status(200).json(process.env.NEXT_PUBLIC_APIROOT)
}
next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: true,
});
module.exports = withBundleAnalyzer({
publicRuntimeConfig: {
NEXT_PUBLIC_APIROOT: process.env.NEXT_PUBLIC_APIROOT,
},
output: 'standalone'
})
Dockerfile
FROM node:16-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:16-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["npm", "start"]
There is a single change that I've done in the Dockerfile and that is updating the CMD entry so that the application starts via the npm start
command.
As per the official documentation, NextJS will try to look for .env.local
in the app root folder and load those environment variables in process.env
.
Therefore, I created a YAML file with Kubernetes resources that will be used to create the deployment setup.
nextjs-app-setup.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: nextjs-app-config
data:
.env.local: |-
NEXT_PUBLIC_APIROOT=hello_i_am_an_env_variable
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextjs-app
labels:
app: nextjs-app
spec:
replicas: 1
selector:
matchLabels:
app: nextjs-app
template:
metadata:
labels:
app: nextjs-app
spec:
containers:
- name: nextjs-app
image: public.ecr.aws/u4x8r8g3/nextjs-app:latest
ports:
- containerPort: 3000
volumeMounts:
- name: nextjs-app-config
mountPath: "/app/.env.local"
subPath: ".env.local"
readOnly: true
volumes:
- name: nextjs-app-config
configMap:
name: nextjs-app-config
---
apiVersion: v1
kind: Service
metadata:
name: nextjs-service
spec:
selector:
app: nextjs-app
ports:
- protocol: TCP
port: 3000
targetPort: 3000
There are multiple things happening in the above configuration:
.env.local
that will hold all of the environment variables and will be mounted as a file in the application podvolumes
and volumeMounts
blocks. Here, I am mounting the .env.local
entry from the ConfigMap that was defined on the /app/.env.local
pathAfter connecting to the GKE cluster via kubectl, I applied the configuration via kubectl apply -f nextjs-app-setup.yaml
.
To connect to the service from my local workstation, I executed kubectl port-forward service/nextjs-service 3000:3000
. Then I navigated in my browser to localhost:3000/api/test
and can see the value that I set in the ConfigMap as the output.
Disclaimer: I understand that your setup might involve some additional components especially when it comes to CI/CD and Infrastructure-as-Code, but my answer here should at least provide you with an approach to accessing environment variables in your containerized NextJS workloads. If you still get undefined
values, my assumption is that it would most likely be related to how you are configuring them in your CI/CD pipeline, but that would be a different issue that is not related to NextJS or Kubernetes.
Upvotes: 2