Deploying a Frontend Application with Dynamic Environment Variables Using Docker, Kubernetes, and Nginx
This guide demonstrates how to deploy a frontend application using Docker and Kubernetes (k8s) with dynamic environment variables that can change across different namespaces. We will use Nginx as a web server to serve the frontend static files.
Create a Dockerfile for your frontend project:
# Use a Node.js base image
FROM node:14 AS build
# Set working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy the rest of the project files
COPY . .
# Build the project
RUN npm run build
# Use an Nginx base image for serving static files
FROM nginx:1.21
# Copy the build output from the previous stage
COPY --from=build /app/build /usr/share/nginx/html
# Copy a custom Nginx configuration file
COPY nginx.conf /etc/nginx/conf.d/default.conf
Create an nginx.conf file in your project root:
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /app-config.js {
alias /usr/share/nginx/html/app-config.js;
add_header Content-Type application/javascript;
expires -1; # Disable caching
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; # Ensure no caching in proxies and clients
}
}
In your index.html file, add the following line before the main JavaScript bundle:
<head>
...
<script src="/app-config.js"></script>
...
</head>
Create a window.env object in your frontend code to access environment variables:
const API_URL = window.env.REACT_APP_API_URL || 'http://localhost:3000/api';
Build and push the Docker image to a container registry:
docker build -t your-registry/your-frontend-image:latest .
docker push your-registry/your-frontend-image:latest
Create a ConfigMap and a Secret (if needed) for each namespace in Kubernetes:
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-config
namespace: your-namespace
data:
REACT_APP_API_URL: 'http://your-backend-api-url'
---
apiVersion: v1
kind: Secret
metadata:
name: frontend-secrets
namespace: your-namespace
type: Opaque
stringData:
REACT_APP_SECRET: 'your-secret-value'
Replace the your-namespace, http://your-backend-api-url, and your-secret-value placeholders with the appropriate values. Create separate ConfigMaps and Secrets for different namespaces as needed.
Create a Kubernetes deployment for your frontend project:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: your-namespace
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
initContainers:
- name: app-config-init
image: busybox:1.33
command: ['sh', '-c', 'echo "window.env = { REACT_APP_API_URL: \"$(REACT_APP_API_URL)\", REACT_APP_SECRET: \"$(REACT_APP_SECRET)\" };" > /usr/share/nginx/html/app-config.js']
envFrom:
- configMapRef:
name: frontend-config
- secretRef:
name: frontend-secrets
volumeMounts:
- name: app-config
mountPath: /usr/share/nginx/html
containers:
- name: frontend
image: your-registry/your-frontend-image:latest
ports:
- containerPort: 80
volumeMounts:
- name: app-config
mountPath: /usr/share/nginx/html
volumes:
- name: app-config
emptyDir: {}
Create a Kubernetes Service to expose your frontend deployment:
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: your-namespace
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
Apply the Kubernetes manifests:
kubectl apply -f your-manifest-file.yaml
With this setup, the app-config.js file is generated dynamically by the initContainers section during the deployment, and the Nginx server serves the file without caching. The frontend application loads the environment variables from this file at runtime, allowing you to modify the behavior of your application, such as changing the backend API service it accesses, across different namespaces.