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.
cendarum run --env dev -- node server.jscendarum run --env dev -- npm run devcendarum run --env dev -- pnpm devIf 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:
cendarum run --env dev -- node -e 'console.log(process.env.DB_PASSWORD)'cendarum run --env dev -- npm run devVite 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.
Next.js
Section titled “Next.js”cendarum run --env dev -- npm run devcendarum run --env production -- npm run buildSame 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.
Create React App
Section titled “Create React App”cendarum run --env dev -- npm startPrefix is REACT_APP_.
Python / Django
Section titled “Python / Django”cendarum run --env dev -- python manage.py runservercendarum 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.
Ruby / Rails
Section titled “Ruby / Rails”cendarum run --env dev -- bin/rails servercendarum 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.
cendarum run --env dev -- go run .cendarum run --env dev -- ./bin/serverNothing to configure; os.Getenv reads the block it was handed.
cendarum run --env dev -- make deployEvery 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 devDocker Compose
Section titled “Docker Compose”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_URLcendarum run --env dev -- docker compose upThe 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 pipelines
Section titled “Shell pipelines”--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:
cendarum run --env dev --shell -- 'echo $FEATURE_FLAG && printenv API_URL'What doesn’t work
Section titled “What doesn’t work”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.