Skip to content

Environment Files for Dev, Stage, and Prod

This guide explains how to create and manage your .env file for local development (dev), stage, and production (prod) using the files and Make targets in this repo. The app will pick up these settings via Docker Compose/containers.


Quick Start

Local development (dev)

Use env.dev.example as a safe default for local work. You only need to change a few values (e.g., Google Maps keys, docs repos/token) if you plan to exercise those features locally.

# Copy the dev example and open it for editing (overwrites existing .env)
make env

Minimum you might want to customize locally:

  • GOOGLE_MAPS_BROWSER_API_KEY, GOOGLE_MAPS_SERVER_API_KEY – see google_maps_api_key_guide.md
  • GH_REPOS, GH_PAT, GH_WEBHOOK_SECRET, DOCS_BUILD_TOKEN – if you want local MkDocs builds pulling from your GitHub repos

For local dev, keeping the remaining change-me-in-production values is acceptable; these are not used outside your machine.

Stage / Prod

For stage and prod you should use real values. Start from env.prod.example and fill in the necessary secrets and hostnames.

# Copy the prod example and open it for editing (overwrites existing .env)
make prod-env

Rule of thumb: Only values explicitly shown as change-me-in-production or marked as # secret=true must be replaced. Common defaults like POSTGRES_PORT=5432, POSTGRES_DB=ok_db, etc., can stay as-is unless you deliberately changed your infra.


Generating Secrets with Make

Use the provided Make targets to generate cryptographic secrets inside the containers (no host dependencies needed):

# 50-char Django SECRET_KEY
make dj-secret

# 32-byte hex token (e.g., HEALTH_CHECK_TOKEN)
make token-32

# 64-byte hex token (e.g., OTEL_WRITE_TOKEN)
make token-64

# Bcrypt hash for passwords used by Caddy/Prometheus basic auth
make caddy-hash-password

Paste the outputs into the corresponding .env variables.


What to Change (by section)

Below is a checklist covering stage and prod (both use real values). For dev, you can usually keep the defaults unless noted.

Postgres

  • POSTGRES_USER (prod/stage: real value)
  • POSTGRES_PASSWORD (prod/stage: real secret)
  • OK to keep defaults: POSTGRES_HOST=postgres, POSTGRES_PORT=5432, POSTGRES_DB=ok_db, POSTGRES_LOCALE=en_US.UTF-8

Caddy (Prod only)

  • TLS_EMAIL (prod: real email for ACME/Let’s Encrypt)

Django / Gunicorn

  • BUILD_TARGET=development (dev) / production (stage & prod)
  • DJANGO_SETTINGS_MODULE – dev: mow.settings.dev, prod/stage: mow.settings.prod
  • SECRET_KEY (prod/stage: generate via make dj-secret)
  • JWT_SIGNING_KEY (prod/stage: generate via make dj-secret or another secret)
  • DEBUG=False (prod/stage)
  • ALLOWED_HOSTS (prod/stage: set your domains)
  • HEALTH_CHECK_TOKEN (prod/stage: make token-32)
  • Usually fine unchanged: GUNICORN_WORKERS, GUNICORN_THREADS, GUNICORN_MAX_REQUESTS*

CORS / CSRF / Domains

  • CORS_ALLOW_ALL_ORIGINS=False (already safe default)
  • CORS_ALLOWED_ORIGINS (prod/stage: comma-separated https URLs)
  • CSRF_TRUSTED_ORIGINS (prod/stage: comma-separated https URLs)
  • DOMAIN_NAME, ADMIN_DOMAIN, PORTAL_DOMAIN (prod/stage: real domains)

Initial Admin User

  • ADMIN_USERNAME, ADMIN_EMAIL (prod/stage: your real bootstrap admin)
  • ADMIN_ROLE=ADMIN (default is fine)

Google Maps API Keys

  • GOOGLE_MAPS_BROWSER_API_KEY, GOOGLE_MAPS_SERVER_API_KEY (prod/stage: real keys)
  • For how to create/secure keys, see: google_maps_api_key_guide.md

Outbound Email (Prod only if used)

  • EMAIL_HOST_USER, EMAIL_HOST_PASSWORD (real creds)
  • DEFAULT_FROM_EMAIL, SERVER_EMAIL (valid addresses)

Timezone (Supercronic)

  • TZ (default UTC is fine)

Observability / Telemetry

  • OTEL_WRITE_TOKEN (prod/stage: make token-64)

Prometheus / Caddy Basic Auth

  • PROMETHEUS_USER (prod/stage: pick a user)
  • PROMETHEUS_PASSWORD_HASH (prod/stage: make caddy-hash-password)

Grafana

  • GRAFANA_USER (prod/stage: pick a user)
  • GRAFANA_PASSWORD (prod/stage: strong password)
  • GRAFANA_FROM_EMAIL (valid email if sending mail)
  • GF_DATABASE_PASSWORD (prod/stage: real secret)

MkDocs / Docs Service

  • GH_REPOS (prod/stage: comma-separated list of org/repo[@branch])
  • GH_PAT (prod/stage: fine‑grained token with Contents: Read‑only)
  • GH_WEBHOOK_SECRET (prod/stage: random secret)
  • DOCS_BUILD_TOKEN (optional manual trigger; random if used)
  • OPENAPI_REPO, OPENAPI_PATH (point to your OpenAPI file)
  • DOCS_SITE_NAME (branding)
  • DOCS_SITE_URL, DOCS_API_URL (prod/stage: public URLs)
  • DOCS_INITIAL_BUILD (true/false as desired)

Deploy (Prod and/or Stage if you use the deploy container)

  • GH_DEPLOY_KEY (prod/stage: real deploy key if used)

Dev (local)

  1. Run make env and accept the overwrite prompt.
  2. Optionally set:

  3. Google Maps keys (see guide)

  4. Docs-related vars (if you’ll build docs locally)
  5. Start your dev stack as usual via Docker Compose/Make.

Stage

  1. Run make prod-env to copy env.prod.example to .env and edit.
  2. Replace everything marked change-me-in-production and any # secret=true items using the Make targets above.
  3. Set stage hostnames in ALLOWED_HOSTS, CORS_ALLOWED_ORIGINS, CSRF_TRUSTED_ORIGINS, and the *_DOMAIN vars.
  4. Provide real Google Maps keys (see guide), GitHub repos/PAT, and any OpenAPI settings for docs.
  5. Commit .env only to your secret manager/CI secrets – never to Git.

Prod

Repeat the Stage steps with production hostnames and credentials. Ensure:

  • DEBUG=False
  • Valid TLS email (TLS_EMAIL)
  • All secrets are strong and unique

Safe Defaults You Don’t Need to Change

  • POSTGRES_HOST=postgres
  • POSTGRES_PORT=5432
  • POSTGRES_DB=ok_db
  • POSTGRES_LOCALE=en_US.UTF-8
  • Gunicorn worker/thread counts (tune later if needed)
  • TZ=UTC
  • ADMIN_ROLE=ADMIN
  • OPENAPI_PATH=api/openapi.json
  • DOCS_SITE_NAME=Developer Docs
  • DOCS_INITIAL_BUILD=true

Prod Settings that MUST be correct

  • BUILD_TARGET=production
  • DJANGO_SETTINGS_MODULE=mow.settings.prod
  • DEBUG=False
  • CORS_ALLOW_ALL_ORIGINS=False

Tips

  • Keep .env out of Git (already covered by .gitignore).
  • Store real secrets in a secure secrets manager (e.g., AWS SSM Parameter Store – see aws/scripts/ssm-*).
  • To regenerate a single secret, re-run the relevant make target and update .env.
  • After editing .env, restart containers that read it (often easiest: bring the stack down/up).

Reference


< Backend Development - Google Maps API Key Guide

Next: Backend Development - Django Python Dependency Management >