Environment Configuration¶
Managing configuration cleanly is critical for any application that talks to external APIs. This chapter covers how to structure environment variables for your Gaia-powered application across backend and frontend, handle secrets safely, and manage environment-specific overrides.
Configuration Architecture¶
A typical Gaia application has two separate configuration surfaces:
| Layer | Technology | Config Mechanism | Prefix |
|---|---|---|---|
| Backend | Python / FastAPI | .env file + Pydantic BaseSettings | None |
| Frontend | React / Vite | .env file (build-time injection) | VITE_ |
Never expose backend secrets to the frontend
The backend .env contains your GAIA_API_KEY and other secrets. The frontend .env only contains public configuration like the backend API URL. Vite embeds VITE_-prefixed variables into the JavaScript bundle at build time — anything there is visible to end users.
Backend Configuration¶
Pydantic Settings Model¶
Use Pydantic's BaseSettings to create a typed, validated configuration object that reads from environment variables and .env files automatically:
# backend/app/config.py
from pydantic_settings import BaseSettings
from pydantic import Field
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# ── Required ──
gaia_api_key: str = Field(..., description="Cohesity Gaia API key")
gaia_base_url: str = Field(
default="https://helios.cohesity.com/v2/mcm/gaia",
description="Gaia API base URL",
)
# ── Optional Gaia settings ──
gaia_verify_ssl: bool = Field(default=True, description="Verify SSL certificates")
gaia_security_ctx: str | None = Field(
default=None, description="Security context for multi-tenant operations"
)
# ── Application settings ──
request_timeout_seconds: int = Field(default=60, description="HTTP request timeout")
allow_cors_origin: str = Field(
default="http://localhost:5173", description="Allowed CORS origin"
)
db_path: str = Field(default="./data/sessions.db", description="SQLite database path")
session_ttl_minutes: int = Field(default=60, description="Session time-to-live")
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
"case_sensitive": False,
}
settings = Settings()
Why Pydantic BaseSettings?
- Validation at startup — missing or malformed variables fail fast with clear error messages.
- Type coercion —
"true"becomesTrue,"60"becomes60. - Defaults — sensible defaults mean fewer variables to configure in development.
- Documentation — the
Field(description=...)metadata serves as living documentation.
Variable Reference¶
Required Variables¶
| Variable | Type | Description |
|---|---|---|
GAIA_API_KEY | str | Your Cohesity Gaia API key. Obtained from the Cohesity Helios portal. |
GAIA_BASE_URL | str | Base URL of the Gaia API. Defaults to https://helios.cohesity.com/v2/mcm/gaia. |
Optional Variables¶
| Variable | Type | Default | Description |
|---|---|---|---|
GAIA_VERIFY_SSL | bool | true | Set to false only in development environments with self-signed certificates. Never disable in production. |
GAIA_SECURITY_CTX | str | None | Security context header for multi-tenant Cohesity clusters. |
REQUEST_TIMEOUT_SECONDS | int | 60 | Timeout for HTTP requests to the Gaia API. |
ALLOW_CORS_ORIGIN | str | http://localhost:5173 | Origin allowed for CORS. Set to your frontend domain in production. |
DB_PATH | str | ./data/sessions.db | Path to the SQLite database for session storage. |
SESSION_TTL_MINUTES | int | 60 | How long sessions remain valid before requiring re-authentication. |
Backend .env.example¶
# backend/.env.example
# ──────────────────────────────────────────────────────────────────────
# Copy this file to .env and fill in the required values.
# ──────────────────────────────────────────────────────────────────────
# ── Required ──────────────────────────────────────────────────────────
GAIA_API_KEY=your-api-key-here
GAIA_BASE_URL=https://helios.cohesity.com/v2/mcm/gaia
# ── Optional: Gaia connection ─────────────────────────────────────────
# GAIA_VERIFY_SSL=true
# GAIA_SECURITY_CTX=
# ── Optional: Application ────────────────────────────────────────────
# REQUEST_TIMEOUT_SECONDS=60
# ALLOW_CORS_ORIGIN=http://localhost:5173
# DB_PATH=./data/sessions.db
# SESSION_TTL_MINUTES=60
Frontend Configuration¶
Vite exposes environment variables to client code through the import.meta.env object. Only variables prefixed with VITE_ are included in the build.
Frontend .env.example¶
# frontend/.env.example
# ──────────────────────────────────────────────────────────────────────
# Copy this file to .env and adjust as needed.
# ──────────────────────────────────────────────────────────────────────
# URL of the backend API (no trailing slash)
VITE_API_BASE_URL=http://localhost:8000
# Application title shown in the browser tab
VITE_APP_TITLE=Gaia App
Accessing Variables in React¶
// frontend/src/config.ts
export const config = {
apiBaseUrl: import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000",
appTitle: import.meta.env.VITE_APP_TITLE ?? "Gaia App",
} as const;
Build-time vs. runtime
Vite replaces import.meta.env.VITE_* references at build time. If you need to change configuration without rebuilding, inject values into window.__ENV__ via a script tag in index.html and read them at runtime.
Environment-Specific Overrides¶
Maintain separate .env files for each environment and load the appropriate one at deploy time.
Loading Environment-Specific Files¶
You can point Pydantic to a specific .env file using an environment variable:
import os
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
# ... fields ...
model_config = {
"env_file": os.getenv("ENV_FILE", ".env"),
"env_file_encoding": "utf-8",
}
Then at launch:
Secrets Management Best Practices¶
Never commit secrets to version control
Add .env to your .gitignore. Only commit .env.example files with placeholder values.
The .gitignore Entry¶
Graduated Approach to Secrets¶
| Environment | Approach | Notes |
|---|---|---|
| Local dev | .env file | Simplest option. Each developer maintains their own. |
| CI / Staging | CI platform secrets | GitHub Actions secrets, GitLab CI variables, etc. Injected as env vars at runtime. |
| Production | Secrets manager | AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, or Kubernetes Secrets. |
Docker Secrets at Runtime¶
When deploying with Docker Compose, pass secrets through environment variables rather than baking them into images:
# docker-compose.yml (excerpt)
services:
backend:
environment:
- GAIA_API_KEY=${GAIA_API_KEY}
- GAIA_BASE_URL=${GAIA_BASE_URL}
The host's environment (or a .env file in the same directory as docker-compose.yml) supplies the actual values.
Twelve-Factor App
This approach follows the Twelve-Factor App methodology: store config in the environment, never in code. It keeps your application portable across environments without code changes.
Validating Configuration at Startup¶
The Pydantic BaseSettings model validates all configuration when Settings() is instantiated. Add an explicit startup check in your FastAPI app:
# backend/app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.config import Settings
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = Settings() # Validates all env vars — fails fast if misconfigured
app.state.settings = settings
yield
app = FastAPI(lifespan=lifespan)
If GAIA_API_KEY is missing, the application will refuse to start with a clear ValidationError — far better than discovering the problem at runtime when a user makes a request.
Next Steps¶
- Docker Deployment — Package your configured application into containers.
- Production Checklist — Verify your configuration is production-ready.