The Runtime Config feature allows you to serve dynamic configuration from environment variables to your frontend application. This is especially useful for containerized applications (Docker/Kubernetes) where configuration varies by environment.
- SPAs in containers: Serve different API URLs per environment without rebuilding
- Feature flags: Enable/disable features via environment variables
- Kubernetes ConfigMaps: Expose ConfigMap values to frontend apps
- Multi-environment deployments: Same Docker image for dev/staging/prod
- Configure runtime config in
config.json - Set environment variables (with prefix or specific names)
- Server exposes a special route (e.g.,
/runtime-config.js) - Frontend fetches this route to get current configuration
- Variables are read at request time (always current)
{
"runtime_config": {
"enabled": true,
"route": "/runtime-config.js",
"format": "js",
"var_name": "APP_CONFIG",
"env_prefix": "APP_",
"no_cache": true
}
}| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false | Enable/disable runtime config |
route |
string | "/runtime-config.js" | Route where config will be served |
format |
string | "js" | Output format: "js" or "json" |
var_name |
string | "APP_CONFIG" | JavaScript variable name (js format only) |
env_prefix |
string | "" | Prefix for environment variables to include |
env_variables |
array | [] | Specific environment variables to include |
no_cache |
boolean | false | If true, adds no-cache headers |
Configuration:
{
"format": "js",
"var_name": "APP_CONFIG"
}Output (/runtime-config.js):
window.APP_CONFIG = {
"API_URL": "https://api.example.com",
"FEATURE_FLAG": "true",
"VERSION": "1.0.0"
};Usage in HTML:
<script src="/runtime-config.js"></script>
<script>
console.log('API URL:', window.APP_CONFIG.API_URL);
fetch(window.APP_CONFIG.API_URL + '/users');
</script>Configuration:
{
"format": "json"
}Output (/config.json):
{
"API_URL": "https://api.example.com",
"FEATURE_FLAG": "true",
"VERSION": "1.0.0"
}Usage in JavaScript:
fetch('/config.json')
.then(res => res.json())
.then(config => {
console.log('API URL:', config.API_URL);
});You can choose variables in two ways:
All environment variables starting with the prefix will be included, with the prefix removed from the key.
Configuration:
{
"env_prefix": "APP_"
}Environment Variables:
APP_API_URL=https://api.example.com
APP_VERSION=1.0.0
APP_FEATURE_X=true
OTHER_VAR=ignoredOutput:
window.APP_CONFIG = {
"API_URL": "https://api.example.com", // APP_ removed
"VERSION": "1.0.0", // APP_ removed
"FEATURE_X": "true" // APP_ removed
};
// OTHER_VAR is not included (no APP_ prefix)Explicitly list which environment variables to include.
Configuration:
{
"env_variables": ["API_URL", "DATABASE_URL", "REDIS_URL"]
}Environment Variables:
API_URL=https://api.example.com
DATABASE_URL=postgres://localhost/db
REDIS_URL=redis://localhost:6379
SECRET_KEY=secret123Output:
{
"API_URL": "https://api.example.com",
"DATABASE_URL": "postgres://localhost/db",
"REDIS_URL": "redis://localhost:6379"
}
// SECRET_KEY is not included (not in list)Note: If both env_prefix and env_variables are set, env_variables takes priority.
docker-compose.yml:
services:
frontend:
image: myapp/frontend:latest
environment:
- APP_API_URL=${API_URL}
- APP_ENV=${ENVIRONMENT}
- APP_ANALYTICS_ID=${ANALYTICS_ID}
volumes:
- ./config.json:/app/config.json
command: ["/app/koryx-serv", "-config", "/app/config.json", "-dir", "/app/build"]config.json:
{
"server": {
"port": 8080,
"root_dir": "/app/build"
},
"features": {
"spa_mode": true
},
"runtime_config": {
"enabled": true,
"route": "/runtime-config.js",
"format": "js",
"var_name": "APP_CONFIG",
"env_prefix": "APP_",
"no_cache": true
}
}React (index.html):
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<!-- Load runtime config FIRST -->
<script src="/runtime-config.js"></script>
</head>
<body>
<div id="root"></div>
<script src="/static/js/main.js"></script>
</body>
</html>React (src/config.js):
// Read config from window
const config = window.APP_CONFIG || {};
export default {
apiUrl: config.API_URL || 'http://localhost:3000',
environment: config.ENV || 'development',
analyticsId: config.ANALYTICS_ID || '',
};kubernetes/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-config
data:
APP_API_URL: "https://api.production.com"
APP_VERSION: "v1.2.3"
APP_FEATURE_NEW_UI: "true"kubernetes/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
template:
spec:
containers:
- name: frontend
image: myapp/frontend:latest
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: frontend-config
volumeMounts:
- name: config
mountPath: /app/config.json
subPath: config.json
volumes:
- name: config
configMap:
name: koryx-serv-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: koryx-serv-config
data:
config.json: |
{
"server": {
"port": 8080,
"root_dir": "/app/build"
},
"features": {
"spa_mode": true
},
"runtime_config": {
"enabled": true,
"route": "/runtime-config.js",
"format": "js",
"env_prefix": "APP_",
"no_cache": true
}
}config.json:
{
"runtime_config": {
"enabled": true,
"route": "/config.json",
"format": "json",
"env_prefix": "VUE_APP_",
"no_cache": true
}
}Vue (src/main.js):
import { createApp } from 'vue'
import App from './App.vue'
// Fetch runtime config before mounting app
fetch('/config.json')
.then(res => res.json())
.then(config => {
const app = createApp(App)
app.config.globalProperties.$config = config
app.mount('#app')
})
.catch(err => {
console.error('Failed to load config:', err)
// Fallback to defaults
const app = createApp(App)
app.config.globalProperties.$config = {}
app.mount('#app')
})Vue Component:
<template>
<div>
<p>API URL: {{ $config.API_URL }}</p>
<p>Version: {{ $config.VERSION }}</p>
</div>
</template>- Never expose secrets: Don't include sensitive values (API keys, passwords)
- Use specific variables: Prefer
env_variablesoverenv_prefixfor security - Validate in backend: Frontend config is public, validate everything server-side
- Use HTTPS: Protect config in transit with HTTPS
- Consider auth: Use basic auth if config contains semi-sensitive data
Bad Example (DON'T DO THIS):
APP_SECRET_KEY=super-secret-123
APP_DATABASE_PASSWORD=password123Good Example:
APP_API_URL=https://api.example.com
APP_ENVIRONMENT=production
APP_FEATURE_FLAG=true{
"no_cache": true
}This adds headers:
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Why: Ensures frontend always gets latest config when env vars change.
{
"no_cache": false
}Uses default cache headers from performance.cache_max_age.
When to use: If config rarely changes and you want better performance.
Problem: /runtime-config.js returns {}
Solutions:
- Check environment variables are set:
env | grep APP_ - Verify prefix matches:
"env_prefix": "APP_"needs vars starting withAPP_ - Check specific vars exist: If using
env_variables, ensure they're set
Problem: Changes to env vars don't reflect in output
Solutions:
- Restart the server (env vars are read at request time, but server needs restart)
- Check
no_cache: trueis set - Clear browser cache
Problem: Expected JSON but got JavaScript
Solution: Check "format": "json" is set correctly
Problem: 404 on /runtime-config.js
Solutions:
- Verify
"enabled": true - Check
routeconfiguration matches your request - Ensure runtime config is registered before main handler (it should be automatic)
- Negligible: Reading env vars is very fast (cached by OS)
- No file I/O: Everything is in memory
- No database: No external dependencies
- Adds ~1ms: To first request only (if no-cache is false)
| Approach | Pros | Cons |
|---|---|---|
| Runtime Config | ✓ Single image ✓ Dynamic config ✓ Simple |
✗ Public config ✗ Needs server |
| Build-time | ✓ Static ✓ No server needed |
✗ Rebuild per env ✗ Slow deploys |
| Inject at deploy | ✓ Single image | ✗ Complex ✗ Needs build tools |
| Config service | ✓ Centralized ✓ Dynamic |
✗ Extra dependency ✗ Network calls |
- Use prefix:
APP_orREACT_APP_keeps it organized - Enable no-cache: Ensures config is always fresh
- Load early: Fetch config before app initialization
- Provide defaults: Gracefully handle missing config
- Document vars: List all expected env vars in README
- Validate types: Convert strings to numbers/booleans as needed
- Use TypeScript: Type your config for safety
Dockerfile:
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM alpine:latest
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/build /app/build
COPY --from=builder /app/koryx-serv /app/koryx-serv
COPY --from=builder /app/config.json /app/config.json
EXPOSE 8080
CMD ["/app/koryx-serv", "-config", "/app/config.json", "-dir", "/app/build"]config.json:
{
"server": {
"port": 8080,
"root_dir": "/app/build"
},
"features": {
"spa_mode": true,
"index_files": ["index.html"]
},
"performance": {
"enable_compression": true,
"enable_cache": true,
"cache_max_age": 86400
},
"runtime_config": {
"enabled": true,
"route": "/runtime-config.js",
"format": "js",
"var_name": "REACT_APP_CONFIG",
"env_prefix": "REACT_APP_",
"no_cache": true
}
}src/config.ts:
interface Config {
API_URL: string;
ENV: string;
FEATURE_X: boolean;
}
declare global {
interface Window {
REACT_APP_CONFIG?: Record<string, string>;
}
}
const rawConfig = window.REACT_APP_CONFIG || {};
const config: Config = {
API_URL: rawConfig.API_URL || 'http://localhost:3000',
ENV: rawConfig.ENV || 'development',
FEATURE_X: rawConfig.FEATURE_X === 'true',
};
export default config;Last Updated: 2025-10-29 Version: 1.1.0