Skip to main content

Configuration Management

Principle

All config that varies between deploys lives in environment variables. The backend reads them via pydantic Settings at startup and fails fast if required variables are missing.

Loading

Pydantic Settings reads from environment variables automatically. A .env file is loaded in development.

# backend/app/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
database_url: str
secret_key: str
redis_url: str = "redis://localhost:6379"
lxd_endpoint: str
lxd_cert_path: str
lxd_key_path: str
lxd_ssh_jump_host: str
cors_origins: str = "http://localhost:5173"
lessons_repo_url: str = ""
lessons_ssh_key_path: str = "/app/private_key"
access_token_expire_hours: int = 8

class Config:
env_file = ".env"

settings = Settings()

Secrets → Environment Variables Only

Never in code, never committed.

# backend/.env (gitignored)
DATABASE_URL=postgresql+asyncpg://platform:password@localhost:5432/platform
SECRET_KEY=super-secret-key-change-in-production
REDIS_URL=redis://localhost:6379
LXD_ENDPOINT=https://10.0.0.3:8443
LXD_CERT_PATH=/app/.lxd/client.crt
LXD_KEY_PATH=/app/.lxd/client.key
LXD_SSH_JUMP_HOST=65.109.236.163
CORS_ORIGINS=http://localhost:5173
LESSONS_REPO_URL=git@gitlab.com:zsaidov1988/lessons-content.git
LESSONS_SSH_KEY_PATH=/app/private_key

backend/.env.example is committed with placeholder values to document what's required.

Backend Environment Variables

VariableRequiredDefaultDescription
DATABASE_URLYesPostgreSQL asyncpg connection string
SECRET_KEYYesJWT signing key (long, random)
LXD_ENDPOINTYesLXD API URL (https://10.0.0.3:8443)
LXD_CERT_PATHYesPath to LXD client certificate
LXD_KEY_PATHYesPath to LXD client key
LXD_SSH_JUMP_HOSTYesPublic IP for SSH to containers (65.109.236.163)
REDIS_URLNoredis://localhost:6379Redis connection
CORS_ORIGINSNohttp://localhost:5173Comma-separated allowed origins
LESSONS_REPO_URLNoSSH URL for private lessons GitLab repo
LESSONS_SSH_KEY_PATHNo/app/private_keySSH key for lessons repo
ACCESS_TOKEN_EXPIRE_HOURSNo8JWT expiry in hours

Frontend Environment Variables

Single variable, set in frontend/.env (dev) or frontend/.env.production (prod):

VariableDescription
VITE_API_URLBackend API base URL (e.g. https://mvp-api.zafarsaidov.uz)

frontend/.env.production is committed to the repo — it contains only the production API URL, not secrets.

Rules

  • No config.production.py — production differs from dev only via env vars
  • Fail fast on startup — pydantic raises immediately if a required variable is missing
  • Swap environments by changing env vars — no code changes needed
  • Never hardcode URLs or credentials in source code