Secret providers
Set the optional secrets field to choose where a stack’s Effect Config
values come from: one provider or an ordered list. Omitting it preserves
existing configuration, including any injected ConfigProvider. The CLI
normally reads the process environment with .env as a fallback; an
explicit --env-file takes precedence over the process environment.
import * as Doppler from "alchemy/Doppler";
export default Alchemy.Stack( "MyApp", { providers: Cloudflare.providers(), state: Cloudflare.state(), secrets: [ Doppler.Secrets({ project: "my-app", config: "dev" }), Alchemy.Secrets.DotEnv(), ], }, Effect.gen(function* () { const dbUrl = yield* Config.Redacted("DATABASE_URL"); // ... }),);Precedence
Section titled “Precedence”Later entries override earlier ones. In the example above, process
environment variables override .env, which overrides Doppler.
The process environment is the implicit last entry, so a one-off
DATABASE_URL=... alchemy deploy and a CI runner’s exported tokens
work with any list. List Secrets.ProcessEnv() yourself to rank it
elsewhere, typically first so it is only a fallback for what the
secrets manager holds:
secrets: [ Alchemy.Secrets.ProcessEnv(), Doppler.Secrets({ project: "my-app", config: "prd" }),],Secrets.ProcessEnv({ disabled: true }) leaves the shell out
entirely. Cloud credentials such as CLOUDFLARE_API_TOKEN go through
the same list, so with the shell disabled they must come from a
provider. A secrets manager’s own credentials are the exception: it
reads DOPPLER_TOKEN, INFISICAL_IDENTITY_ID, and CI from the
process environment only, whatever the list says, so a runner’s
exported variables always reach it and no file can impersonate them.
For stacks that declare secrets, one key is exempt: ALCHEMY_PROFILE. Which
profile a deploy authenticates with comes
from --profile or the real process environment only, so no provider
can redirect the credentials every other provider uses.
Per stage
Section titled “Per stage”Both the secrets option and every provider’s options accept a
callback instead of a value. It receives the
stage, the stack, and the Alchemy context, so
one stack definition serves dev, pr-42, and prod from different
places:
secrets: ({ stage }) => stage === "dev" ? Alchemy.Secrets.DotEnv() : Doppler.Secrets({ project: "my-app", config: stage }),The same shape narrows a single provider:
secrets: [ Alchemy.Secrets.DotEnv(({ stage }) => ({ path: [".env", `.env.${stage}`] })),],Dotenv details
Section titled “Dotenv details”Secrets.DotEnv reads one file or an ordered list. The implicit
default .env may be absent; a file you name must exist. Empty
values are real values and override earlier files. Pass
expandVariables: true to expand ${OTHER} references.
Nothing is written to process.env. Values live in Effect’s
ConfigProvider, so two
stacks in one process never see each other’s files.
--env-file retains its existing behavior when secrets is omitted.
It is refused when the stack declares secrets; put
Secrets.DotEnv({ path }) in the list instead.
Seeing what loaded
Section titled “Seeing what loaded”With --log-level debug, dotenv, Doppler, and Infisical report the
variable names they loaded, never their values. The process environment
reports only a count. These messages describe loaded sources; process
environment overrides and custom layers can still change the final values.
Loaded 3 secrets from dotenv (.env, .env.prod): DATABASE_URL, PORT, SENTRY_DSNLoaded 12 secrets from Doppler (project 'app' config 'prd'): API_KEY, DATABASE_URL, ...Loaded 87 secrets from the process environmentBring your own source
Section titled “Bring your own source”Any Effect Layer that provides a ConfigProvider is a valid entry.
Use ConfigProvider.layerAdd with asPrimary to merge with what
came before, the way the built-in providers do; a plain
ConfigProvider.layer replaces everything listed before it:
secrets: [ ConfigProvider.layerAdd(ConfigProvider.fromUnknown(defaults), { asPrimary: true }), Alchemy.Secrets.DotEnv(),],The Doppler and Infisical providers skip remote loading during auth-provider discovery, which builds the stack offline just to find out which clouds it uses.
Where next
Section titled “Where next”- Doppler — browser login locally, service tokens in CI.
- Infisical — machine identities locally, platform OIDC in CI.
- Secrets & Config — how a
Configvalue ends up bound to a Worker or Lambda.