CASCADING OVERRIDES - nself-org/cli GitHub Wiki

Environment Cascade & Cascading Overrides

Understand how nself's environment variable system works and how to manage configuration across development, staging, and production.

Overview

nself uses a cascading override system where configuration files layer on top of each other. Each file overrides the previous one, allowing for:

  • Shared defaults (committed to git)
  • Local customization (on your machine only)
  • Environment-specific overrides (staging vs production)
  • Secrets management (never in git)

The Cascade Order

Configuration is loaded in this exact order. Later files override earlier files:

.env.dev             (1. Base - Committed to git)
    โ†“
.env.local           (2. Local - On your machine only)
    โ†“
.env.staging         (3. Staging - On staging server only)
    โ†“
.env.prod            (4. Production - On prod server only)
    โ†“
.env.secrets         (5. Secrets - Ultra-sensitive, server only)
    โ†“
.env.computed        (6. Computed - Auto-generated, never edit)

File Purposes

1. .env.dev (Shared Defaults)

Purpose: Base configuration used by all developers.

Properties:

  • Committed to git
  • Shared by entire team
  • Contains safe default values
  • No secrets or sensitive data

Example:

PROJECT_NAME=my-app
ENV=dev
BASE_DOMAIN=localhost
POSTGRES_DB=myapp_dev
HASURA_GRAPHQL_ENABLE_CONSOLE=true

2. .env.local (Developer Overrides)

Purpose: Your personal machine customizations.

Properties:

  • Gitignored (never committed)
  • Only on your machine
  • Overrides .env.dev
  • Can contain personal secrets

Example:

# You want to use a different Postgres password
POSTGRES_PASSWORD=my-super-secret-local-password

# You want to enable Redis locally
REDIS_ENABLED=true

Result: Your machine uses your password, all other values from .env.dev.

3. .env.staging (Staging Server)

Purpose: Configuration specific to the staging environment.

Properties:

  • On staging server only (not on developer machines)
  • Overrides .env.dev and .env.local
  • Staging-specific values (staging database, staging API keys)

Example:

# Staging database with more data
POSTGRES_PASSWORD=staging-db-password

# Staging Hasura admin secret
HASURA_GRAPHQL_ADMIN_SECRET=staging-secret-123

# Enable monitoring on staging
MONITORING_ENABLED=true

Access: Senior developers via SSH to staging server.

4. .env.prod (Production Server)

Purpose: Production-specific configuration.

Properties:

  • On production server only
  • Overrides everything above
  • Production URLs, API keys, feature flags

Example:

# Production database
POSTGRES_PASSWORD=prod-database-password

# Production domain
BASE_DOMAIN=myapp.com

# Production Hasura secret
HASURA_GRAPHQL_ADMIN_SECRET=prod-secret-xyz

Access: Lead developers only via SSH to production.

5. .env.secrets (Secrets Only)

Purpose: Ultra-sensitive credentials, generated on server.

Properties:

  • Generated on server at setup time
  • Never in git (ever)
  • Only on production server
  • Encrypted if stored at rest

Example:

# Master encryption key
ENCRYPTION_KEY=long-random-string-from-server

# Database root password
POSTGRES_ROOT_PASSWORD=ultra-secret-generated-key

# JWT signing key
JWT_SECRET=another-generated-secret

Access: Lead developers only, synced via secure SSH.

6. .env.computed (Auto-Generated)

Purpose: Computed values generated during nself build.

Properties:

  • Auto-generated by nself (never edit)
  • Contains derived values
  • Based on other environment variables

Example:

# Generated from POSTGRES credentials
DATABASE_URL=postgresql://postgres:password@postgres:5432/myapp_db

# Generated from PROJECT_NAME
DOCKER_NETWORK=my-app_network

# Generated service hosts
POSTGRES_HOST=postgres
HASURA_HOST=hasura

Loading Order in Code

When nself starts, it loads variables in order:

#!/bin/bash

# Load .env.dev first (shared defaults)
if [ -f .env.dev ]; then
  source .env.dev
fi

# Load .env.local second (your overrides)
if [ -f .env.local ]; then
  source .env.local
fi

# Load environment-specific (staging, prod, etc)
if [ -f .env.${ENV} ]; then
  source .env.${ENV}
fi

# Load production secrets last (if they exist)
if [ -f .env.secrets ]; then
  source .env.secrets
fi

# Auto-generate computed values
source .env.computed

Practical Example

Project Setup

Starting configuration:

File: .env.dev (committed to git)

PROJECT_NAME=my-app
ENV=dev
BASE_DOMAIN=localhost
POSTGRES_DB=myapp_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=change-me
HASURA_GRAPHQL_ADMIN_SECRET=change-me
REDIS_ENABLED=false
MONITORING_ENABLED=false

File: .env.local (on developer's machine, gitignored)

# Developer wants local Redis for caching
REDIS_ENABLED=true

# Developer uses different password
POSTGRES_PASSWORD=my-local-password-456
HASURA_GRAPHQL_ADMIN_SECRET=my-local-secret-789

What Actually Gets Used?

When you run nself start on your local machine:

Variable Source Value
PROJECT_NAME .env.dev my-app
ENV .env.dev dev
BASE_DOMAIN .env.dev localhost
POSTGRES_DB .env.dev myapp_db
POSTGRES_USER .env.dev postgres
POSTGRES_PASSWORD .env.local (override) my-local-password-456
HASURA_GRAPHQL_ADMIN_SECRET .env.local (override) my-local-secret-789
REDIS_ENABLED .env.local (override) true
MONITORING_ENABLED .env.dev false

On Staging Server

The staging server doesn't have .env.local. It loads:

File: .env.dev (from git)

PROJECT_NAME=my-app
POSTGRES_PASSWORD=change-me
# ...

File: .env.staging (only on staging server)

POSTGRES_PASSWORD=staging-db-password-xyz
BASE_DOMAIN=staging.myapp.com
MONITORING_ENABLED=true

Result on staging:

Variable Source Value
POSTGRES_PASSWORD .env.staging staging-db-password-xyz
BASE_DOMAIN .env.staging staging.myapp.com
MONITORING_ENABLED .env.staging true
Other vars .env.dev (defaults)

Developer Access Levels

nself supports role-based environment access:

Developer (Junior)

Access: .env.dev + .env.local only

Can develop locally but cannot see staging/production secrets.

nself env switch local          # Works (they have .env.local)
nself sync pull staging         # Fails (no SSH access to staging)

Senior Developer

Access: .env.dev, .env.local, + .env.staging

Can test on staging server without accessing production.

nself env switch local          # Works
nself sync pull staging         # Works (SSH access to staging)
nself sync pull prod            # Fails (no SSH access to prod)

Lead Developer

Access: All files including .env.secrets

Full access to production for emergencies and critical tasks.

nself env switch local          # Works
nself sync pull staging         # Works
nself sync pull prod            # Works (SSH access to prod)
nself sync pull secrets         # Works (access to .env.secrets)

Syncing Configuration Across Machines

Pull Staging Configuration

Senior developers can pull staging configuration to their local machine:

# Pull .env.staging from staging server via SSH
nself sync pull staging

# This creates .env.staging locally (gitignored)
# Now you can test staging config locally

Pull Production Configuration

Lead developers only:

# Pull .env.prod from production server
nself sync pull prod

# Pull .env.secrets (master credentials)
nself sync pull secrets

Push Configuration

Update configuration on remote:

# Sync your .env changes to staging
nself sync push staging

# Sync to production (Lead Dev only)
nself sync push prod

Gitignore for Environment Files

Your .gitignore should include:

# Local overrides
.env.local

# Environment-specific (don't commit staging/prod)
.env.staging
.env.prod

# Secrets (NEVER commit)
.env.secrets

# Computed values (auto-generated)
.env.computed

Only .env.dev should be committed to git.

# Good - only base config in git
git add .env.dev
git commit -m "feat: update base configuration"

# BAD - never do this
git add .env.secrets  # โŒ NEVER!
git add .env.prod     # โŒ NEVER!

Common Workflows

1. Local Development

# Edit .env.dev for shared changes
vim .env.dev
git add .env.dev
git commit -m "update base config"
git push

# Edit .env.local for personal changes
vim .env.local  # Not committed
# No git commands needed

2. Testing Staging Config Locally

# Pull staging config
nself sync pull staging

# Switch to staging config for testing
nself env switch staging

# Run tests
nself build
nself start

# Switch back to local
nself env switch local

3. Emergency Production Fix

# Lead dev only
nself sync pull prod

# Make emergency change
vim .env.prod

# Apply immediately
nself build
nself restart

# Push change to remote
nself sync push prod

4. Adding New Variable to All Environments

As a developer:

# Add to .env.dev (shared)
echo "NEW_VAR=value" >> .env.dev
git add .env.dev
git commit -m "add NEW_VAR to configuration"
git push

# Tell Sr Dev and Lead Dev they need to add to their environment files

As Sr Dev:

# Add staging value
echo "NEW_VAR=staging-value" >> .env.staging
nself sync push staging

As Lead Dev:

# Add production value
echo "NEW_VAR=prod-value" >> .env.prod
nself sync push prod

Troubleshooting

Wrong Value in Use

Problem: You set a value in .env.local but it's not being used.

Solution: Check the cascade order. Later files override earlier ones.

# Check which file has the value
grep "POSTGRES_PASSWORD" .env.dev
grep "POSTGRES_PASSWORD" .env.local

# The .env.local value should be used

Staging Config Not Syncing

Problem: Changed .env.staging on staging server but not seeing changes locally.

Solution: Pull the latest config:

nself sync pull staging

Secrets File Missing

Problem: .env.secrets doesn't exist on production server.

Solution: Generate it:

# On production server
nself config generate-secrets

# This creates .env.secrets with generated values

Port Conflict Between Environments

Problem: Local dev uses port 8080, but staging also tries to use 8080.

Solution: Use different ports in each environment:

# .env.dev
NGINX_PORT=8080

# .env.staging
NGINX_PORT=8081

# .env.prod
NGINX_PORT=443  # HTTPS only

Best Practices

  1. Keep .env.dev minimal - Only shared defaults
  2. Never commit .env.local - Add to .gitignore
  3. Never commit secrets - Use .env.secrets on servers
  4. Use meaningful defaults - change-me for passwords
  5. Document required variables - List in README
  6. Rotate secrets regularly - Especially in production
  7. Audit changes - Use nself audit logs config

Next Steps


Key Takeaway: The cascading environment system lets you have one source of truth (git) while allowing safe local development and secure production secrets. Understand the order and you'll never have configuration issues.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ