Secrets & environments
We use Doppler as the source of truth for secrets and environment-specific configuration. Python code declares the variables it needs with typed environment classes, and deployment tools decide which Doppler project or platform environment provides the values.
Doppler projects
Doppler is organized around deployment ownership:
- Each deployed app or service has its own project, such as
database,datasmart,data-tools,automation-scales-api,automation-ms-sync,automation-ms-graph-proxy, orstoneware-v2. - The
sharedproject owns values that are genuinely shared across deployments, such as SharePoint and Microsoft credentials. - The
monolith-devproject is the local-development aggregate. It contains or references all variables needed for normal development so notebooks, scripts, and partially configured app environments can still run locally.
App-specific values should live in the app's Doppler project. Shared credentials should live in shared and be referenced into app projects or monolith-dev rather than copied by hand.
Python model
Environment variables are declared in small EnvVars classes near the code that owns the capability:
from shared.utils.environment_variables import EnvVars, env_var
class DatabaseEnv(EnvVars):
PGURL: str
PROD_PGURL: str = env_var(fallback="PGURL")
Applications compose those capability classes into one app-local ProjectEnv in env.py:
from shared.db.env import DatabaseEnv
from shared.sharepoint.env import SharePointEnv
from shared.utils.environment_variables import ProjectEnv, RuntimeEnv
class DatasmartEnv(ProjectEnv, RuntimeEnv, DatabaseEnv, SharePointEnv):
_doppler_project = "datasmart"
env = DatasmartEnv.current()
App code should import from its app-local environment:
from datasmart.env import env
pg_url = env.PGURL
Shared utilities should import or accept the capability class that owns their required variables:
from shared.sharepoint.env import SharePointEnv
class SharePointClient:
def __init__(self, env: SharePointEnv | None = None):
self.env = env or SharePointEnv.current(lazy=True)
For notebooks and ad-hoc development, shared.env remains available as a compatibility facade:
from shared.env import PGURL
New application code should prefer the app-local env object. New shared utility code should add or reuse an EnvVars capability class instead of adding unrelated variables to a central file.
Loading order
In development, ProjectEnv.current() loads values in this effective precedence order:
- Process environment variables.
- The app's Doppler project, using the active config, usually
dev. - Shared development fallback projects,
monolith-devby default. - The repository-root
.envfile.
MONO_PROJECT, MONO_DOPPLER_PROJECT, and MONO_DOPPLER_CONFIG are set by mono run for configured tools. A class can also pin its project with _doppler_project = "project-name".
In production, Doppler is not loaded by application code. Secrets should be injected by the platform. Dagster Cloud deployments can load from AWS Secrets Manager when BRIMSTONE_AWS_SECRETS_PATH is set; Railway and similar platforms provide process environment variables directly.
Adding or changing a secret
- Decide ownership.
- App-only secret: add it to the app's Doppler project.
- Shared credential: add it to
shared, then reference it from the app projects andmonolith-dev. - Development-only aggregate value: add or reference it in
monolith-dev.
- Add the value to every config that needs it, typically
devandprd. - Add the variable to code.
- App-specific variable: define it on the app-local
ProjectEnvclass. - Shared capability variable: define it in the owning
EnvVarsclass, such asshared.sharepoint.env,shared.db.env, orshared.s3.env.
- App-specific variable: define it on the app-local
- Import the app-local
envin app code, or the owning capability class in shared library code. - Run the app or a focused import/test locally so missing variables fail before deployment.
Descriptor options
Use env_var() when a variable needs behavior beyond "required string":
from typing import Optional
from shared.utils.environment_variables import EnvVars, env_var
class ServiceEnv(EnvVars):
API_TOKEN: str
PUBLIC_MODE: bool = False
LEGACY_TOKEN: str = env_var(fallback="API_TOKEN")
OPTIONAL_WEBHOOK: Optional[str] = env_var(optional=True)
AWS_ACCESS_KEY_ID: Optional[str] = env_var(
optional=True,
set_in_process=True,
)
Supported field types are str, bool, Optional[str], and Optional[bool]. Required variables are verified when the environment object is created unless they are marked lazy=True.