Environment Variables Across Layers
Quick reference for which variables can be edited at each layer and how.
Why Build-Time Locking Exists
Astro/Vite compiles PUBLIC_* variables into JavaScript at build time-they're not available at runtime. This is intentional: smaller bundle, no secret leaks. Trade-off: rebuild needed to change PUBLIC_API_URL. Other variables (LOG_LEVEL, HOST, PORT) are runtime and can change without rebuilding.
Quick Decision Table
| Variable | Layer 0 | Layer 1 | Layer 2 | Layer 3 | Changeable | How |
|---|---|---|---|---|---|---|
PUBLIC_API_URL |
Runtime | Build-time | Build-time | Runtime env | Need rebuild (L1-2) | --build-arg for layers 1-2; k8s env for layer 3 |
LOG_LEVEL |
Yes | Yes | Yes | Yes | Change anytime | Environment variable; restart to apply |
HOST |
Yes | Yes (env) | Yes (service name) | Yes | Change anytime | Override in config; restart |
PORT |
Yes | Port map | Port map | Yes (service port) | Rarely | Edit docker-compose.yml or Kubernetes manifest |
PUBLIC_API_URL
Tells the Web service where to find the API.
Layers 0 & Dev Server (can change at runtime):
PUBLIC_API_URL=http://localhost:8000 pnpm --dir services/web dev
Layers 1 & 2 (locked at build time-must rebuild to change):
# During build
docker build --build-arg PUBLIC_API_URL=http://api:8000 ...
# In docker-compose.yml
web:
build:
args:
PUBLIC_API_URL: http://api:8000
Why this difference? Layer 0 uses Astro dev server (reloads on change). Layers 1-2 bake the value into the bundle at build time. For container-to-container communication, use http://api:8000 (service name). For host access, use http://localhost:8000.
For step-by-step rebuild examples, see Layer 1 Manual Walkthrough or Layer 2 Rebuild Section.
LOG_LEVEL
Controls logging verbosity. Options: debug, info, warning, error.
Layer 0 (direct):
LOG_LEVEL=debug uv run api
Layers 1 & 2 (Docker):
docker run -e LOG_LEVEL=debug api:latest
# or in docker-compose.yml
api:
environment:
- LOG_LEVEL=debug
Layer 3 (Kubernetes): Set in k8s ConfigMap or env field. Change takes effect on pod restart.
Can change anytime without rebuild. Just apply and restart the service.
HOST
IP address the service binds to. 0.0.0.0 = all interfaces (default). 127.0.0.1 = localhost only.
Layer 0:
HOST=0.0.0.0 uv run api
Layers 1, 2, 3: Set via environment variable or service config. Override at runtime-no rebuild needed.
PORT
Service port inside the container (not the host port exposed).
Layer 0: Change via environment or script flag.
Layers 1 & 2: Port mapping in docker run or docker-compose.yml. Host port may differ from container port:
docker run -p 9000:8000 api:latest # Host 9000 to container 8000
Layer 3: Service port in Kubernetes manifest.
When to Edit What
| Scenario | Action |
|---|---|
| Quick API iteration | Layer 0: Direct uv run api |
| Testing Docker build | Layer 1-2: Build image locally |
| Docker Compose testing | Layer 2: Edit docker-compose.yml, restart |
| Kubernetes testing | Layer 3: Edit manifest, apply, redeploy |
| Change PUBLIC_API_URL | Layers 1-2 only: Rebuild with new --build-arg |
| Change LOG_LEVEL, HOST, PORT | Any layer: Set env var, restart (no rebuild) |
For Kubernetes-specific configuration, see Layer 3 Documentation.
See Also
- Layer 1: Individual Docker Images - Rebuild examples
- Layer 2: Docker Compose - docker-compose.yml configuration
- Layer 3: Kubernetes with kind - Kubernetes deployment examples
- API Service
- Web Service