Skip to content
PlatformAny language, deployed as a Docker image

Fly.io secrets and environment variables

Fly.io stores secrets in an encrypted vault and injects them as environment variables at runtime, never into builds. How that works, and where crunu run fits.

Fly.ioAt a glance

Env files, highest priority first
No .env files: values come from the process environment or the platform.
Public prefix
None

No names are copied into browser code by a prefix.

Values applied
At runtime

Values are read from the environment when the app runs.

Built-in secret store
Fly secrets: an encrypted vault, injected into Machines as environment variables

docs.fly.io/apps/secrets

Crunu command
crunu run --env api/production -- /app/serverRecipeCrunu has no dedicated integration here: this is the general setup.

Common pitfalls

  1. fly secrets set restarts every Machine of the app and resets its ephemeral file system. Use --stage to defer the restart.
  2. App secrets and [env] values are not available during the Docker build. Build secrets are passed separately with --build-secret.
  3. A secret overrides an [env] entry with the same name in fly.toml.
  4. Anyone with deploy access can ship code that reads secrets and prints them to the logs.
  5. fly secrets list shows names and digests only, so Fly can't tell you what a secret's value is.
On this page

On Fly.io, an app's environment variables come from two places: secrets set with fly secrets set, stored encrypted and injected into every Machine of the app at runtime, and plain values in the [env] section of fly.toml. When both define a name, the secret wins. Neither reaches the Docker build. Setting a secret updates and restarts the app's Machines, so a running process always gets new values through a restart. These rules are from the Fly.io secrets documentation and the fly.toml reference, checked October 30, 2026.

Secrets, [env] and build arguments

MechanismSet withAvailable at runtimeAvailable to the buildReadable later
Secretsfly secrets setyes, as environment variablesnono, names and digests only
[env] in fly.tomlthe config fileyesnoyes, it's in your repository
[build.args] and --build-argthe config file or the CLInoyesdepends on the image
Build secretsfly deploy --build-secretnoonly in the RUN step that mounts themno

[env] is for non-sensitive settings. Names can't start with FLY_, values must be strings, and the reference sends anything sensitive to the secrets command instead. Build arguments are the mirror image: they exist during the build and not in the running container, and the reference notes that application environment variables and secrets are likewise not available to builds.

What setting a secret does

fly secrets set NAME=value writes the value to the app's vault and then updates each Machine of the app. That update is a restart, and the documentation points out that it resets the Machine's ephemeral file system. Two options help when that's inconvenient:

  • --stage writes the secret to the vault without touching Machines. Machines started or updated afterwards get it.
  • fly secrets deploy later redeploys the current release with the staged secrets, so several changes land in one restart.

fly secrets unset removes values, fly secrets list shows names with a digest and a date, and the CLI can read secrets from stdin, which keeps values out of your shell history.

Who can read a Fly secret

Fly describes the path in some detail. Secrets are stored in an encrypted vault. The API servers can only encrypt, not decrypt. The Fly agent on the host that runs your Machine uses a token to decrypt the app's secrets and inject them into the Machine, and secret values are never logged. There is deliberately no command that reads a secret's plaintext back.

The documentation also states the limit honestly: people with deploy access can deploy code that reads secret values and prints them to logs. In other words, a secret is protected from casual reading, not from anyone who can change what runs. That's normal for platform secret stores, and it's the right mental model when you decide who gets deploy rights.

Secrets during the build

Because app secrets exist only at runtime, a build step that needs one, such as installing a private package, has to get it another way. The build secrets page shows the pattern: mount the secret in the Dockerfile with RUN --mount=type=secret,id=NAME, read it from /run/secrets/NAME inside that step, and pass the value with fly deploy --build-secret NAME=value. Don't use a build argument for this: build arguments can be recorded in the image, and whatever a build step writes into a layer stays there for anyone who pulls it.

Where crunu run fits on Fly.io

Crunu has no built-in sync into Fly secrets. The recommended setup keeps Fly secrets as the store your Machines read and makes Crunu the place where values are edited, shared and audited. Running crunu inside the image is a fallback for cases where that doesn't work.

Recommended: push values from Crunu into Fly secrets. Let a trusted pipeline or an admin's terminal update the Fly vault when something changes. Run the Fly CLI under crunu run and feed it the values through stdin rather than as arguments. Each update restarts the Machines, or you stage the changes and deploy once. The app then reads ordinary environment variables, and nothing about the image changes.

Fallback: run crunu inside the image. Store a single Fly secret, CRUNU_TOKEN, holding a Crunu service token that can read only the production environment. Make crunu run the container's command, so it decrypts the environment inside the Machine and starts your server with the values.

  1. Create a service token with access to api/production only, without a network rule, and store it as the Fly secret CRUNU_TOKEN, passing the value through stdin rather than on the command line. Put CRUNU_SERVER, your Crunu server address, in [env].
  2. In the Dockerfile, install crunu with the install script's --user-install mode in a build stage, and copy the binary and CA certificates into the final image.
  3. Set the command to crunu run --env api/production --restart on-change -- /app/server.
  4. Deploy with fly deploy. Later value changes in Crunu restart only the server process inside the Machine, not the Machine.

Crunu documents service tokens for CI and short-lived environments, and recommends the agent on a machine you control for long-running servers. Before you use a token this way, accept three limits:

  • Fly can decrypt this environment. Fly stores the token, its host agent decrypts it for the Machine, and the token decrypts the environment it was granted. For this environment, the promise that the server can't read your values no longer holds: the platform holding the token can.
  • An expired lease stops the app from starting. The token's lease is a window of inactivity from 1 hour to 30 days, and every sign-in extends it. If no Machine starts the app for longer than the lease, the token is refused at the next start, crunu run exits before your server starts, and the app stays down until an owner or admin resumes the token. Choose a lease longer than the longest time the app may stay stopped.
  • One token per environment, with the smallest grant and no network rule. Give each app and environment its own token that reads only that environment. Don't bind it to networks: the addresses a platform like Fly connects from can change between deploys, and a network rule would then lock the app out.

What you get in return: the individual values are no longer in Fly's vault, the token's page in Crunu shows which versions of that environment it received and when, and revoking the token cuts the app off at its next sign-in. A process that is already running keeps the values it has until it restarts. crunu run never passes CRUNU_TOKEN to your server process.

Which of the two to choose

Start with pushing into Fly secrets. The app, the image and the deploy stay as they are, Fly keeps doing what it's built for, and no Crunu credential is stored on Fly. It suits most teams that want one shared, audited place to edit values and a record of who changed what. Its cost is a Machine restart per update, and the values live in two vaults. In both setups Fly can decrypt the production values on the host that runs them; Crunu's blind server covers where values are edited and shared, not where Fly runs them.

Use the in-image fallback only when restarts per update are a real problem, for example with many secrets or frequent rotation. It keeps values in one place and lets a change reach the app without fly secrets set. Its cost is a second binary in the image, CA certificates in the final stage, a token that Fly can decrypt, and a lease you have to size for the longest pause.

Frequently asked questions

Something else? Write to support@crunu.com

Are Fly.io secrets available when my Docker image builds?

No. Fly's documentation says secrets are only available at runtime. For a build step that needs a secret, pass it with fly deploy --build-secret and mount it in the Dockerfile with RUN --mount=type=secret.

Who can decrypt Fly.io secrets?

Fly's API servers can only encrypt them. The Fly agent on the host that runs your Machine decrypts them and injects them. Your own code, and anyone who can deploy code, can read them as environment variables.

Does Crunu sync values into Fly secrets?

No, there's no built-in sync. Push values from a trusted pipeline with the Fly CLI, which can read secrets from stdin. As a fallback, run crunu inside the image so only a service token is stored in Fly, accepting that Fly can then decrypt that environment.

Can the crunu agent service run on a Fly Machine?

The agent service is a systemd unit for Linux hosts, and a Fly Machine runs your image's own process. The fallback is crunu run with a service token in CRUNU_TOKEN. Fly then holds a token that can decrypt that environment, and the token is refused if the app doesn't sign in for longer than its lease.

Sources

Every source this page relies on, with the date we last checked it.

  1. Fly.io Docs: Secrets · checked October 30, 2026
  2. Fly.io Docs: Build secrets · checked October 30, 2026
  3. Fly.io Docs: App configuration (fly.toml) · checked October 30, 2026

Keep your .env values in one place

Free during early access. Values are encrypted on your devices, each environment has its own key, and crunu run hands them to your app without writing a file.