Skip to main content

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, or stoneware-v2.
  • The shared project owns values that are genuinely shared across deployments, such as SharePoint and Microsoft credentials.
  • The monolith-dev project 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:

  1. Process environment variables.
  2. The app's Doppler project, using the active config, usually dev.
  3. Shared development fallback projects, monolith-dev by default.
  4. The repository-root .env file.

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

  1. 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 and monolith-dev.
    • Development-only aggregate value: add or reference it in monolith-dev.
  2. Add the value to every config that needs it, typically dev and prd.
  3. Add the variable to code.
    • App-specific variable: define it on the app-local ProjectEnv class.
    • Shared capability variable: define it in the owning EnvVars class, such as shared.sharepoint.env, shared.db.env, or shared.s3.env.
  4. Import the app-local env in app code, or the owning capability class in shared library code.
  5. 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.