Module 1 · Lesson 3

Env Vars & Secrets

Configure your app with defaults, required values, and generated secrets.

What you'll learn
  • Declaring environment variables with defaults and descriptions
  • Using generators for automatic secret creation
  • The secrets block and $secrets.* expressions
  • Pipe transforms like |base64

The env block

Every app needs configuration — a port to listen on, an API key for a third-party service, a secret for signing sessions. In a Launchfile, the env block declares all of this in one place.

Each variable can have a default value, be marked as required, include a human-readable description, use a generator for automatic creation, or be flagged as sensitive so it's masked in logs.

Build it up

1
Start simple. A default value means the app works out of the box — the deployer can override it if needed.
env:
  PORT:
    default: "3000"
2
Mark a variable as required when the app can't start without it. The description tells the deployer what to provide. Nothing invents a value for you: if you don't supply one, the deploy stops and names the variable.
env:
  PORT:
    default: "3000"
  API_KEY:
    required: true
    description: "Third-party API key"
3
The generator: secret field declares that a cryptographic random value should be created at deploy time — always 32 random bytes, hex-encoded (64 characters). Marking it sensitive keeps it out of logs.
env:
  PORT:
    default: "3000"
  API_KEY:
    required: true
    description: "Third-party API key"
  SESSION_SECRET:
    generator: secret
    sensitive: true
4
For more complex cases, use a top-level secrets block. Reference secrets with ${secrets.app-key} and apply pipe transforms like |base64, which re-encodes the secret's underlying 32 bytes — exactly the key length Laravel-style apps expect.
secrets:
  app-key:
    generator: secret

env:
  PORT:
    default: "3000"
  APP_KEY: "base64:${secrets.app-key|base64}"

Secrets vs env

When to use which

There are two ways to generate secrets in a Launchfile:

  • Inline in env: Add generator: secret directly to an env var. Simple and self-contained.
  • Secrets block: Define named secrets at the top level, then reference them with ${secrets.name} in env. Useful when one secret feeds multiple env vars, or when you need transforms.

For most apps, the inline approach is all you need. Reach for the secrets block when you need to reuse or transform a generated value.

Inline generator
env:
  SESSION_SECRET:
    generator: secret
    sensitive: true
Secrets block + expression
secrets:
  session-key:
    generator: secret

env:
  SESSION_SECRET: "${secrets.session-key}"
What happens to these values over time?

Where a value comes from decides how the platform treats it later:

  • Generated values (generator: secret) are created once and then preserved — a redeploy never regenerates them, because that would invalidate sessions and encrypted data.
  • Expression defaults (like $app.url) are kept up to date by the platform — if your app's domain changes, the value follows, unless you've overridden it.
  • Literal defaults are just starting values — the platform or deployer can override them per environment.
  • Values you supply (required or optional, with no default) are yours alone — the platform never writes or changes them. A required: value you have not supplied stops the deploy and names the variable; no provider invents one for you.
Supplying a required value

A required: variable with no default:, no generator:, and no resource binding is a value you supply. Supply it through the launching environment:

API_KEY=sk-live-... launchfile up

Leave it out and the deploy stops before anything starts, naming the component and the variable:

Cannot launch: 1 required environment variable had no value.
  - default: API_KEY (sensitive)

That is deliberate. A substituted placeholder would satisfy the app's own presence check and push the failure out to the first login, the first query, or the first send — long past the point where you could diagnose it.

When an app wants the URL in pieces

Some apps won't take a full URL — their config expects the address split into separate fields: a host here, an SSL flag there. The $app.* family covers both shapes:

env:
  PUBLIC_URL: $app.url          # the single-string case — prefer this
  CMD_DOMAIN: $app.authority    # public host[:port], port omitted when default
  CMD_PROTOCOL_USESSL: $app.tls # "true" or "false" — for literal SSL on/off flags

$app.authority, $app.scheme, and $app.tls are all derived from $app.url, so any provider that can resolve the URL can resolve the pieces. Reach for them only when the app genuinely needs the address split — $app.url stays the default.

In the wild

Firefly III is a personal finance manager with one of the more interesting Launchfiles in the catalog. It uses a secrets block with a |base64 pipe transform, references $app.url for its public URL, and wires Postgres connection details individually.

firefly-iii/LaunchfileView on GitHub
version: launch/v1
name: firefly-iii
description: "Personal finance manager with budgeting and reporting"
repository: https://github.com/firefly-iii/firefly-iii
website: https://firefly-iii.org
logo: https://raw.githubusercontent.com/firefly-iii/firefly-iii/main/public/v1/images/logo.svg

image: fireflyiii/core:latest
provides:
  - protocol: http
    port: 8080
    exposed: true
requires:
  - type: postgres
    set_env:
      DB_CONNECTION: "pgsql"
      DB_HOST: $host
      DB_PORT: $port
      DB_DATABASE: $name
      DB_USERNAME: $user
      DB_PASSWORD: $password
secrets:
  app-key:
    generator: secret
env:
  APP_KEY: "base64:${secrets.app-key|base64}"
  APP_URL:
    default: $app.url
    description: "Public URL Firefly III is reachable on (used in emails, OAuth, links)"
health: /login
storage:
  uploads:
    path: /var/www/html/storage/upload
    persistent: true
restart: always
secrets:
Defines a named secret that gets generated once and reused.
APP_KEY:
References the generated secret with a |base64 pipe transform — the app expects a base64-encoded key.
APP_URL:
Uses $app.url — a built-in expression that resolves to the app's public URL.
set_env:
Wires Postgres credentials individually instead of using a single $url — because Firefly expects separate DB_HOST, DB_PORT, etc.
storage:
Persists the upload directory so user data survives redeployments.

Try it: npx launchfile up firefly-iii to launch this app locally.View in catalog

Check your understanding

What does generator: secret do?
Key takeaways
  • The env block declares all environment variables with optional default, required, description, generator, and sensitive fields.
  • generator: secret auto-creates a cryptographic random value at deploy time — 32 bytes, hex-encoded (64 characters), on every provider.
  • The secrets block defines reusable, named secrets referenced via ${secrets.name} expressions.
  • Pipe transforms like |base64 let you encode or transform values inline.
  • The $app.* properties resolve to the app's own public address — $app.url as one string, or $app.authority / $app.scheme / $app.tls when the app wants it in pieces.
esc
Type to search the docs