Environment variables
Keep your project's values in Doppler instead of sending .env files around. The repository lists the names, sixb env pull writes your .env, and your app reads process.env as it always has.
List what the project needs
sixb.env.ts holds names only, so commit it:
import { defineEnv } from "@sixb/core/env"
import { doppler } from "@sixb/env-doppler"
export default defineEnv({
source: doppler({ project: "client-a" }),
vars: {
DATABASE_URL: "required",
LINKEDIN_CLIENT_SECRET: "prod",
SENTRY_DSN: "optional",
},
})
| Rule | The variable must have a value |
|---|---|
required | Everywhere |
prod | In prod, and when NODE_ENV is production |
optional | Never. The name is only recorded |
sixb dev and sixb check stop before starting when a variable has no value, and an empty value counts as none. In a terminal, sixb dev offers to pull:
Missing RESEND_API_KEY, which sixb.env.ts requires.
Pull from dev now? (Y/n)
source is optional. Without it you keep your own .env, and still get these checks. A project without sixb.env.ts runs without them.
Move a project over
Create the project in Doppler, install the package and the Doppler CLI, then upload your current files:
bun add @sixb/env-doppler
bun sixb env init client-a --dev .env --prod .env.prod
This uploads the values and writes sixb.env.ts with a first guess at each rule. Check the rules, commit the file, and delete .env.prod. It stops without changing anything if Doppler already has a different value for a name.
Work with the values
| Task | How |
|---|---|
| Start on a project | bun sixb env pull. The first time, it signs you in to Doppler |
| Get a teammate's change | bun sixb env pull |
| Add or change a value | bun sixb env set <NAME> --env <dev|prod>, or Doppler's dashboard |
| See what's set | bun sixb env status, which never shows values |
| Add or remove a teammate | Doppler's dashboard. When someone leaves, rotate what they could read |
sixb env set asks for the value with a hidden prompt, or reads it from standard input, so it never lands in your shell's history.
sixb env status reads names only, so it shows a value that was blanked in Doppler as set. A pull and a deploy still stop on it.
Doppler accepts uppercase names only, and reads ${NAME} in a value as a reference to another variable, which it resolves before .env is written. It refuses a reference it can't resolve, along with any other name or value it won't take, and says why.
Your .env
sixb env pull replaces .env, so put your own overrides in .env.local, which takes priority. If you wrote the current .env by hand and it has values Doppler doesn't, the pull stops and names them rather than losing them. Pass --force to replace it anyway.
A pull stops, leaving .env as it was, when a variable sixb.env.ts requires isn't in Doppler. It also stops, naming the variable, for a value a .env file can't hold: one with a carriage return, one ending in a backslash, or one using all of ', " and `.
Production values
sixb env pull writes dev, and refuses prod on your computer so production values stay on your servers. To read one, use Doppler's dashboard. Deploying doesn't need your computer to hold them: the server pulls for itself.
Deploy
With source set, each deploy to a server writes that server's .env from prod, so there's nothing to edit on the server. Run setup once, from a computer signed in to Doppler:
bun sixb deploy setup
It creates a read-only token that can read this project's prd config and nothing else, and installs it on the server at ~/<name>/env-token. To replace it, revoke it in Doppler, delete that file, and run setup again.
A deploy then pulls before it builds. Because that's before the services stop, a pull that fails leaves your running release alone:
| What happens | The deploy |
|---|---|
sixb.env.ts requires a variable prod doesn't have | Stops and names it |
| Doppler refuses the token | Stops |
| Doppler can't be reached | Continues on the current .env with a warning, if it was generated by a pull and has every required variable. Otherwise stops |
The server's .env was written by hand, with values prod doesn't match | Stops and names them. Move what you want to keep to Doppler, then delete the file |
Your app reads only .env, so a running service or a restart never depends on Doppler. Changing a value in Doppler reaches the server at the next deploy. Rolling back with --ref keeps the current values, so a key you revoked doesn't come back.
Anyone who can sign in to the server can read .env and the token, and so can a job holding the deploy key. Limit server access to people you'd trust with production.
Tests in CI
Give a test job that needs values a read-only token for dev, created in Doppler:
- env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_DEV_TOKEN }}
run: bun sixb env pull