turboEnv

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.