Deploying and CI
Loading turboEnv variables on a server, in Docker, and from a CI pipeline, using a scoped token instead of an interactive login.
turboenv login needs a browser and a loopback port on the same machine, so it
does not work on a server, in a container, or over a plain SSH session. That is
the intended outcome rather than a gap: those places should hold a token scoped
to exactly what they run.
Create a deploy token
In the dashboard, open your project's Tokens tab and create a token scoped to the single environment that deployment uses. You will see the value once.
Put it wherever your platform keeps secrets — GitHub Actions secrets, a
Kubernetes secret, your PaaS config panel — as TURBOENV_TOKEN. With that set,
no flags are needed, because the token already knows what it can reach:
export TURBOENV_TOKEN=tenv_live_...
turboenv run -- node server.js
Use one token per deployment target rather than one shared everywhere. They cost nothing, and it means revoking a staging box does not take production down with it.
As a container entrypoint
turboenv run forwards SIGINT, SIGTERM and SIGHUP to the process it
starts, and exits with exactly that process's status, so docker stop shuts
your app down properly and your orchestrator's restart and health-check
behaviour is unchanged.
FROM node:24-slim
RUN npm install -g turboenv-cli
COPY . /app
WORKDIR /app
CMD ["turboenv", "run", "--", "node", "server.js"]
Pass TURBOENV_TOKEN in at run time. Do not bake it into the image — a token in
a layer is a token in your registry, and it stays in the history even if a later
layer removes it.
docker run -e TURBOENV_TOKEN=tenv_live_... your-image
It is not an init process, so if your application spawns children of its own you
still want --init or a real init as PID 1 to reap them, exactly as you would
without turboEnv.
On start it writes one line to stderr naming what it loaded, which is what you want in a deploy log when something is misconfigured:
[turboenv] 12 variables from checkout-service/production v8
Variables are fetched when the container starts, so a change in turboEnv reaches it on its next restart rather than immediately. That is usually what you want: a config change should be a deliberate rollout, not something that reaches half your fleet the moment somebody presses save.
GitHub Actions
- name: Run tests
env:
TURBOENV_TOKEN: ${{ secrets.TURBOENV_TOKEN }}
run: npx turboenv-cli run -- npm test
Use a token scoped to a test or development environment here, never
production. A pull request from a fork should not be one careless printenv
away from your live database.
Remember that the CLI refuses a stored login credential when it detects CI, so this must be a project token. If you see it decline, that is the check working rather than a bug.
systemd
[Service]
Environment=TURBOENV_TOKEN=tenv_live_...
ExecStart=/usr/bin/turboenv run -- /opt/app/bin/api
Restart=always
Put the token in an EnvironmentFile= that is root-owned and mode 0600
rather than inline, so it does not appear in systemctl show output for every
user on the box.
Pinning a version
By default you get the latest version of an environment. For a deployment you usually want the exact set of values you tested:
turboenv run --version 12 -- ./bin/api
Pinning makes a deploy reproducible and turns a bad configuration change into a rollback rather than an incident. The trade-off is that a genuine configuration fix needs a new deploy to take effect, which is the same trade-off you already accept for application code.
When turboEnv is unreachable
The CLI writes the last successful response to ~/.turboenv/cache, mode 0600,
keyed by a hash of the credential, project, environment and version. If the API
cannot be reached at startup, that cached copy is used and a warning goes to
stderr.
So an outage on our side does not stop your app from booting. It does mean a
container with no persistent filesystem has no cache to fall back on the first
time it starts, which is worth knowing before you decide how much this matters
to you. --no-cache turns both reading and writing off.
Without the CLI
If you would rather not install anything, one authenticated request returns the whole environment:
curl -s https://app.turboenv.workers.dev/api/v1/env \
-H "Authorization: Bearer $TURBOENV_TOKEN"
See HTTP API for the rest of the endpoints and for generating a typed client in your language.
A note on print
turboenv print > .env
This exists because some tooling genuinely needs a file. It writes decrypted
values to disk, so prefer run where you have the choice — with run, values
only ever exist in the environment of the child process, and there is no file to
accidentally commit, back up, or leave behind in a build cache.
If you do write one, make sure .env is in .gitignore before you generate it,
not after. Removing a committed secret from Git history is considerably more
work than avoiding it, and the value
has to be rotated regardless.