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=[email protected]: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
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | Yes | — | PostgreSQL asyncpg connection string |
SECRET_KEY | Yes | — | JWT signing key (long, random) |
LXD_ENDPOINT | Yes | — | LXD API URL (https://10.0.0.3:8443) |
LXD_CERT_PATH | Yes | — | Path to LXD client certificate |
LXD_KEY_PATH | Yes | — | Path to LXD client key |
LXD_SSH_JUMP_HOST | Yes | — | Public IP for SSH to containers (65.109.236.163) |
REDIS_URL | No | redis://localhost:6379 | Redis connection |
CORS_ORIGINS | No | http://localhost:5173 | Comma-separated allowed origins |
LESSONS_REPO_URL | No | — | SSH URL for private lessons GitLab repo |
LESSONS_SSH_KEY_PATH | No | /app/private_key | SSH key for lessons repo |
ACCESS_TOKEN_EXPIRE_HOURS | No | 8 | JWT expiry in hours |
Frontend Environment Variables
Single variable, set in frontend/.env (dev) or frontend/.env.production (prod):
| Variable | Description |
|---|---|
VITE_API_URL | Backend 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