Skip to content
Get early access

Recipes

The mechanism is the same everywhere: cendarum run puts values in the child’s process environment, and your runtime reads them the way it always has. What differs between stacks is only naming, and that’s yours to choose.

Terminal window
cendarum run --env dev -- node server.js
cendarum run --env dev -- npm run dev
cendarum run --env dev -- pnpm dev

If your app calls dotenv.config(), leave it in. dotenv and its equivalents default to “an existing environment variable wins”, so your injected values take precedence and the .env load quietly no-ops for the keys we supply. You do not need to remove the library or change your code.

Quick proof:

Terminal window
cendarum run --env dev -- node -e 'console.log(process.env.DB_PASSWORD)'
Terminal window
cendarum run --env dev -- npm run dev

Vite only exposes VITE_-prefixed variables to browser code. So name the ones your client needs accordingly:

Key in Cendarum Reaches the browser?
VITE_API_URL Yes, as import.meta.env.VITE_API_URL
DATABASE_URL No — server-side only, which is correct

That prefix rule is Vite’s security feature, and we don’t interfere with it: we set exactly the names you chose.

Terminal window
cendarum run --env dev -- npm run dev
cendarum run --env production -- npm run build

Same idea with NEXT_PUBLIC_. Note the second line: injecting at build time matters for Next, because NEXT_PUBLIC_ values are inlined into the bundle when it is built, not when it is served.

Terminal window
cendarum run --env dev -- npm start

Prefix is REACT_APP_.

Terminal window
cendarum run --env dev -- python manage.py runserver
cendarum run --env dev -- python -c 'import os; print(os.environ["DB_PASSWORD"])'

python-dotenv follows the same “existing environment wins” default as Node’s dotenv, so an existing load_dotenv() call stays harmless.

Terminal window
cendarum run --env dev -- bin/rails server
cendarum run --env dev -- ruby -e 'puts ENV["DB_PASSWORD"]'

Note that RUBYOPT is on the loader denylist — it configures the interpreter rather than your app, so a stored value for it is skipped with a warning unless you name it in --only.

Terminal window
cendarum run --env dev -- go run .
cendarum run --env dev -- ./bin/server

Nothing to configure; os.Getenv reads the block it was handed.

Terminal window
cendarum run --env dev -- make deploy

Every recipe in the target inherits the environment, so $(DB_PASSWORD) and $$DB_PASSWORD both resolve. This is where run’s exit-code passthrough earns its keep: make sees the real status of whatever it ran.

For a Makefile that should always be run under Cendarum, wrap the target itself:

dev:
cendarum run --env dev -- npm run dev

Compose is the one case that needs a word of care. cendarum run injects into the docker compose client process — not into your containers. Containers get their environment from the Compose file, so you pass the names through explicitly:

services:
api:
environment:
- DB_PASSWORD # no value: takes it from the client's environment
- API_URL
Terminal window
cendarum run --env dev -- docker compose up

The valueless - KEY form is Compose’s own pass-through syntax. Listing names explicitly is better than a blanket forward anyway: the container gets exactly the variables it needs and nothing else.

--shell is off by default, so -- takes a program and its arguments rather than a shell string. When you need shell syntax, opt in and quote the whole thing:

Terminal window
cendarum run --env dev --shell -- 'echo $FEATURE_FLAG && printenv API_URL'

CI and any headless runner. A human types the master password; a build agent has nobody to type it. Supporting that properly needs a machine identity — a service-account keypair granted access like any other member — which is a load-bearing change to the key hierarchy and deliberately not bolted on. See the FAQ.

A tool that reads only a .env file and ignores its environment. That is what cendarum export is for; read that page first.