Two ways to sync Doppler secrets into a Trigger.dev project, in one example:
- A build extension that pulls from Doppler and syncs at deploy time.
- A webhook + task that syncs at runtime, the moment a secret changes in Doppler, with no redeploy.
Both paths share one function, fetchSyncableDopplerSecrets, so there's a
single place that knows how to talk to Doppler and which keys are syncable. Synced variables are
marked as secret in Trigger.dev (redacted in the dashboard/API), and Doppler's three reserved
metadata keys (DOPPLER_PROJECT, DOPPLER_CONFIG, DOPPLER_ENVIRONMENT) are filtered out.
This is a proof of concept for discussion, not a supported package.
syncDopplerEnvVars wraps the built-in
syncEnvVars extension. On every deploy it
reads the Doppler config for the environment being deployed and syncs the secrets into Trigger.dev.
import { defineConfig } from "@trigger.dev/sdk";
import { syncDopplerEnvVars } from "./src/extensions/syncDopplerEnvVars";
export default defineConfig({
project: "<your-project-ref>",
dirs: ["./src/trigger"],
maxDuration: 300,
build: {
extensions: [
syncDopplerEnvVars({
project: "my-app",
configForEnvironment: (environment) => (environment === "prod" ? "prd" : "dev"),
}),
],
},
});Set DOPPLER_TOKEN (a read-only service token) where
you deploy, then:
npx trigger.dev@latest deployThe token is only read on the deploy machine and never ships in the image. Re-sync by deploying again.
Deploy-time sync only picks up changes when you deploy. To reflect a Doppler change immediately, this
example listens for Doppler's config.secrets.update webhook and triggers a sync task.
sync-doppler-env-vars takes no payload: it downloads and
syncs all syncable env vars for its own environment, exactly like the build extension. It uses the
in-task envvars.upload call, so it writes to
its own project/environment with the run's own credentials (no extra Trigger.dev key needed at
runtime, only DOPPLER_TOKEN).
It has concurrencyLimit: 1, so only one sync ever runs at a time.
app/api/doppler-webhook/route.ts verifies Doppler's
X-Doppler-Signature (HMAC-SHA256) and triggers the task. The trigger uses debounce so a burst of
webhooks collapses into a single sync of the latest state instead of one run per webhook:
await tasks.trigger<typeof syncDopplerEnvVarsTask>("sync-doppler-env-vars", undefined, {
debounce: { key: "doppler-sync-<config>", delay: "10s", mode: "trailing", maxDelay: "2m" },
});trailing means the run fires ~10s after the last webhook in a burst; maxDelay bounds how long a
continuous stream of changes can keep pushing it back. Combined with the task's concurrencyLimit: 1,
frequent webhooks never pile up runs.
-
Set
DOPPLER_TOKENandDOPPLER_PROJECTas env vars in your Trigger.dev project (so the deployed task can download at runtime). Deploy the task withnpm run trigger:deploy. -
Give the Next.js app a
TRIGGER_SECRET_KEYfor the environment you want to sync, plusDOPPLER_WEBHOOK_SECRET(the signing secret you'll use on the Doppler webhook). See.env.example. -
Run the Next.js app:
npm run dev(serves the webhook atPOST /api/doppler-webhook). -
Expose it publicly. For local development, ngrok:
ngrok http 3000
-
In Doppler, create a webhook on your project (Project -> Webhooks) pointing at
https://<your-public-url>/api/doppler-webhook, choose the configs to watch, and set the same signing secret asDOPPLER_WEBHOOK_SECRET.
Now change a secret in Doppler. The webhook fires, the route triggers the sync task, and the task pulls the latest secrets from Doppler and updates your Trigger.dev env vars.
syncDopplerEnvVars(options) (build extension) and fetchSyncableDopplerSecrets(env, source) (shared)
take:
| Option | Description |
|---|---|
dopplerToken |
Doppler token. Defaults to process.env.DOPPLER_TOKEN. |
project |
Doppler project slug. Defaults to process.env.DOPPLER_PROJECT. |
config |
Doppler config to read. Overrides configForEnvironment. |
configForEnvironment |
Maps the Trigger.dev environment (prod/staging/preview/dev) to a Doppler config. |
apiUrl |
Doppler API base URL. Defaults to https://api.doppler.com. |
markAsSecret |
(build extension) Mark synced variables as secret. Defaults to true. |
Marking a variable as secret only takes effect when it is first created. If a variable already exists as non-secret, syncing updates its value but does not flip it to secret.