Project configuration
Commit a turboenv.json so nobody has to remember flags, then safely pull and push dotenv files.
Every command so far has needed -p and -e. Committing a small file removes
them, and gives your repository one obvious answer to "which environment does
this project use".
turboenv init
turboenv init
With no flags it lists the projects your team owns, asks you to pick one, then does the same for its environments. It writes:
{
"project": "checkout-service",
"environment": "development",
"encryption": "customer"
}
Commit it. It holds names, never values — there is nothing sensitive in it, and checking it in is what makes a fresh clone work without instructions.
encryption is written by turboenv init as a fail-closed expectation. It is
public metadata, never key material. The file cannot set
the API host: a committed file is editable by anyone who can land a commit or
hand you a repository, and a url in it would point the CLI at their server
with your token attached. The CLI refuses a config containing one. If you run
your own instance, set TURBOENV_URL in your environment instead, and the CLI
will print which host it is using whenever it is not the hosted one.
From then on the flags are optional, from that directory or any beneath it:
turboenv run -- npm start
turboenv print
If you already know the names, skip the questions:
turboenv init -p checkout-service -e development
Both are checked against the server either way. A misspelled environment fails here, in front of the person who can fix it, rather than during a deploy.
An existing turboenv.json is never overwritten without --force.
Which one is used
Most explicit first:
-pand-eTURBOENV_PROJECTandTURBOENV_ENVIRONMENTturboenv.json- The token's own scope, for a project token
The two resolve independently, so a committed project plus -e staging for one
command works as you would expect.
The file is found by walking up from the working directory, like package.json
or .git. In a monorepo, put one in each service directory and each gets its own
defaults.
Environments differ per developer
turboenv.json is shared, so it should name the environment most people want —
usually development. Anyone who needs another one overrides it for a single
command with -e, or for a whole shell:
export TURBOENV_ENVIRONMENT=staging
turboenv pull
pull merges a stored snapshot into .env:
turboenv pull
Remote values replace matching local values. Variables found only in the local
file remain, so machine-specific settings are not accidentally erased. Pass
--prune when the file should be an exact copy of the stored snapshot, or
--version 7 to restore an older version locally.
Before writing, the CLI shows a key-only diff and asks for confirmation. An
existing file is copied to .env.bak; the replacement is written atomically
with mode 0600. Comments and formatting are rewritten, and a malformed line is
refused rather than silently dropped.
turboenv push
Until now variables have gone in through the dashboard. push uploads a local
.env up instead, which suits the case the dashboard is worst at: you already
have a working file and want it stored.
turboenv push
It shows what would change and waits:
checkout-service/development is at version 7
Reading /home/you/checkout/.env
+ STRIPE_WEBHOOK_SECRET
~ DATABASE_URL
Save as version 8? [y/N]
+ is new, ~ is a changed value, - is a removal. Values are never
printed, only names — a diff is the sort of thing that ends up pasted into a
chat, and the keys are enough to see what you are about to do.
Confirming writes a new version. Nothing is edited in place, so the previous version stays readable and a mistake is undone by pinning it:
turboenv run --version 7 -- npm start
Only a person can push
push needs a credential from turboenv login. A project token is
read-only and gets a 403.
This is deliberate. Tokens live in CI, in container images and in agent configuration — the places most likely to leak. Keeping them read-only means a leaked token exposes configuration without letting anyone change what your next deployment reads. It also means every version has a name against it, which is what makes the history worth reading.
Removing things needs --prune
A variable on the server that your file does not mention is kept, and the CLI says so:
Already up to date. Nothing to push.
1 variable exists only on the server; pass --prune to remove it.
That default exists because the common reason for a missing key is a stale local file, not an intended deletion. When you do mean it:
turboenv push --prune
Concurrent saves are refused
push sends the version it built its diff from. If a teammate saved in between,
you get:
This environment is now at version 9, not 8. Somebody else saved while you
were working. Re-run to see the new diff.
Nothing is written. Re-run, look at the new diff, and decide again — which is the point: without this check the later of two pushes would silently erase the earlier one and both people would be told it worked.
Secret flags survive
A variable already marked non-secret stays non-secret. New keys are created as secrets, so a value is protected unless you have said otherwise in the dashboard.
In scripts
push refuses to run unprompted, since it changes stored configuration. Pass
--yes when there is no terminal:
turboenv push --yes -m "import from .env.production"
-m records why the version changed and shows up in the history. Worth setting
whenever the diff is not self-explanatory.
Other options:
turboenv push -f .env.production # read a different file
Pushing production always asks you to type the environment name, even
interactively. Pressing y by reflex is easy; typing production is not.
What it will not do
A file with no variables is refused rather than emptying the environment, since
an accidentally truncated .env is far more likely than a genuine wish to delete
everything. Lines it cannot parse are reported on stderr rather than skipped
silently, because a variable quietly dropped here is one missing at startup for
no visible reason.
turboenv sync remains available as a deprecated compatibility alias for
turboenv push.