When GitHub Actions builds your Docker images, the environment variables are passed as build arguments and baked into the image during the build process.
- GitHub Secrets → Set in your repository settings
- Workflow reads secrets → Passes them as build-args to Docker
- Dockerfile receives build-args → Sets them as ENV during build
- Next.js build → Embeds
NEXT_PUBLIC_*vars into JavaScript bundles - Final image → Contains pre-built static files with env vars baked in
Go to your repository → Settings → Secrets and variables → Actions → New repository secret
Add these secrets:
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
NEXT_PUBLIC_FRONTEND_URL=https://yourdomain.com
FROM node:24-alpine AS builder
WORKDIR /app
# Build-time arguments (received from GitHub Actions)
ARG NEXT_PUBLIC_API_URL
ARG NEXT_PUBLIC_FRONTEND_URL
# Set as environment variables for the build
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_FRONTEND_URL=$NEXT_PUBLIC_FRONTEND_URL
# Build the app (env vars are embedded in the build)
RUN yarn build- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
build-args: |
NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL || 'https://api.yourdomain.com' }}
NEXT_PUBLIC_FRONTEND_URL=${{ secrets.NEXT_PUBLIC_FRONTEND_URL || 'https://yourdomain.com' }}- ✅ Build-time only: Frontend env vars are embedded during
yarn build - ✅ Cannot be changed at runtime: Once image is built, env vars are fixed
- ✅ Fallback values: If secrets aren't set, uses default values
- ✅ Next.js requirement: Only
NEXT_PUBLIC_*vars are exposed to browser
Backend env vars are NOT baked into the image. They are provided at runtime when you start the container.
- Security: Sensitive data (JWT secrets, DB passwords) shouldn't be in images
- Flexibility: Same image can be used in different environments
- Best practice: Separate config from code
docker run -d \
-e MONGODB_URI="mongodb://prod-db:27017" \
-e JWT_SECRET="your-secret" \
-e FRONTEND_URL="https://yourdomain.com" \
ghcr.io/yourorg/backend:latestservices:
backend:
image: ghcr.io/yourorg/backend:latest
environment:
- MONGODB_URI=mongodb://mongodb:27017
- JWT_SECRET=${JWT_SECRET}
- FRONTEND_URL=https://yourdomain.comapiVersion: v1
kind: Secret
metadata:
name: backend-secrets
data:
jwt-secret: <base64-encoded>
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: backend
env:
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: backend-secrets
key: jwt-secret| Component | When Set | Where Stored | Can Change at Runtime? |
|---|---|---|---|
Frontend NEXT_PUBLIC_* |
Build time | In JavaScript bundles | ❌ No - rebuild required |
| Backend env vars | Runtime | Environment variables | ✅ Yes - restart container |
- Runs on every push/PR
- Uses secrets or fallback values
- Just validates build works
- Does NOT push images
- Runs when frontend code changes
- Builds with production env vars from secrets
- Pushes image to GitHub Container Registry
- Image contains baked-in env vars
- Runs when backend code changes
- Builds Go binary
- Pushes image to GitHub Container Registry
- Image does NOT contain env vars
- Runs when you push a version tag (e.g.,
v1.0.0) - Builds both frontend and backend
- Tags images with version number
- Frontend image has baked-in env vars
cd frontend
docker build \
--build-arg NEXT_PUBLIC_API_URL=https://api.test.com \
--build-arg NEXT_PUBLIC_FRONTEND_URL=https://test.com \
-t frontend:test .cd backend
docker build -t backend:test .
docker run -e MONGODB_URI=mongodb://localhost:27017 backend:test-
Set GitHub Secrets:
- Go to repo Settings → Secrets → Actions
- Add
NEXT_PUBLIC_API_URL - Add
NEXT_PUBLIC_FRONTEND_URL
-
Push Code:
- GitHub Actions will build images
- Images pushed to
ghcr.io/yourorg/repo/frontend:latest - Images pushed to
ghcr.io/yourorg/repo/backend:latest
-
Deploy:
- Pull images from GitHub Container Registry
- Frontend: Just run (env vars already baked in)
- Backend: Provide runtime env vars
Frontend (requires rebuild):
- Update GitHub secrets
- Push code or manually trigger workflow
- New image built with new env vars
- Deploy new image
Backend (no rebuild needed):
- Update env vars in deployment config
- Restart containers
- Done!
- ✅ Check they start with
NEXT_PUBLIC_ - ✅ Check GitHub secrets are set
- ✅ Rebuild and redeploy image
- ✅ Check runtime env vars are provided
- ✅ Check connection strings are correct
- ✅ Check network connectivity
- Frontend: Normal (includes Node.js and built files)
- Backend: Should be small (~20MB with Alpine)
- Never commit secrets to git
- Use GitHub Secrets for sensitive data
- Frontend: Only non-sensitive data in
NEXT_PUBLIC_* - Backend: All sensitive data as runtime env vars
- Rotate secrets regularly
- Use different secrets for dev/staging/prod
# 1. Set GitHub secrets (one time)
# Go to repo settings and add secrets
# 2. Push code
git tag v1.0.0
git push origin v1.0.0
# 3. GitHub Actions builds images with env vars baked in
# 4. Pull and run on your server
docker pull ghcr.io/yourorg/repo/frontend:v1.0.0
docker pull ghcr.io/yourorg/repo/backend:v1.0.0
# 5. Run frontend (env vars already in image)
docker run -d -p 3000:3000 ghcr.io/yourorg/repo/frontend:v1.0.0
# 6. Run backend (provide env vars at runtime)
docker run -d -p 8080:8080 \
-e MONGODB_URI="mongodb://prod:27017" \
-e JWT_SECRET="prod-secret" \
ghcr.io/yourorg/repo/backend:v1.0.0TL;DR:
- ✅ Frontend env vars: Baked into image at build time via GitHub Actions
- ✅ Backend env vars: Provided at runtime when starting container
- ✅ Set GitHub Secrets for production values
- ✅ Fallback values used if secrets not set