Module 3 · Lesson 8

Expressions & Resource Configuration

The full $ reference grammar, and how to name, version, and configure the resources your app requires.

What you'll learn
  • The complete $ expression grammar — bare props, dot paths, templates, fallbacks, and escapes
  • How a reference is resolved — the namespace order
  • Naming resources to tell two instances of the same type apart
  • Version constraints and config hints on required resources
  • The property vocabulary — and how validate catches typos

One grammar, not five tricks

You've been using expressions since Lesson 2 — $url on a database, ${secrets.app-key|base64} in Lesson 3, $storage.data.path in Lesson 4, $components.backend.url in Lesson 6. Each looked like its own trick. They're not: every one is the same $ reference grammar, resolved by one set of rules. This lesson puts the whole system in one place — and covers the resource fields that make expressions necessary: name, version, and config.

Build it up

1
The form you know from Lesson 2. Inside a resource's set_env, a bare $prop resolves against that resource — $url here is the Postgres connection string, because Postgres is the enclosing resource.
requires:
  - type: postgres
    set_env:
      DATABASE_URL: $url
2
Expressions compose. The braced form ${prop} means the same as $prop but can sit inside a longer string — here a JDBC URL assembled from three properties. ${port:-5432} adds a fallback: the literal after :- is used when the reference has no value.
requires:
  - type: postgres
    set_env:
      JDBC_URL: "jdbc:postgresql://${host}:${port}/${name}"
      DB_PORT: "${port:-5432}"
3
Two Postgres instances would be ambiguous — which one is $postgres.host? The name field disambiguates: references elsewhere in the file become $primary-db.host and $analytics-db.host. Without name, a resource's name defaults to its type.
requires:
  - type: postgres
    name: primary-db
    set_env:
      PRIMARY_DB_URL: $url
  - type: postgres
    name: analytics-db
    set_env:
      ANALYTICS_DB_URL: $url
4
Two more resource fields. version constrains what the platform may provision, using semver range syntax — ">=15", "^7.0", "20.x". config passes provisioning hints whose keys are resource-type-specific; a platform that doesn't understand a key ignores it.
requires:
  - type: postgres
    name: primary-db
    version: ">=15"
    config:
      extensions: [pgvector]
    set_env:
      PRIMARY_DB_URL: $url

The grammar at a glance

SyntaxMeaning
$propProperty of the enclosing resource
$resource.propProperty of a named resource (name defaults to type)
$components.name.propAnother component's endpoint (Lesson 6)
$secrets.nameApp-wide generated secret (Lesson 3)
$app.propPlatform-injected app property, like $app.url (Lesson 3)
$storage.name.pathProvider-resolved storage path (Lesson 4)
${prop}Braced form — same reference, composable inside strings
${prop:-default}Reference with a fallback value
$ref|transformAny reference piped through a transform, like |base64 (Lesson 3)
$$A literal $ — the escape for values like $$HOME/bin

How a reference is resolved

When the provider meets a dotted path, it checks the namespaces in a fixed order: app.* first, then secrets.*, then components.*, then storage.*. Only then does it treat the first segment as a resource name. A single bare segment — $host, $port — never goes through that lookup: it always means the enclosing resource.

app and storage are reserved

Because the namespaces are checked first, a resource named app or a volume named storage can't be reached by expression — $app.url still resolves to the platform-injected app property, not your resource. Pick a different name.

The property vocabulary

What can go after the $? Each resource type exposes a standard property set: postgres gives you url, host, port, user, password, and name; redis gives url, host, port, password; and so on. url is always the fully-formed connection string; the rest are its pieces, for apps that want them separately.

One string — most apps
requires:
  - type: postgres
    set_env:
      DATABASE_URL: $url
The pieces — apps that insist
requires:
  - type: postgres
    set_env:
      DB_HOST: $host
      DB_PORT: $port
      DB_USER: $user
      DB_PASSWORD: $password
      DB_NAME: $name
Why typos don't fail — and don't hide either

The vocabulary is standard but open: a provider may expose extra properties, so a reference outside the standard set isn't invalid — it might be a deliberate extension. But it might be a typo, and an unknown property resolves to empty string. Write $hoost and your app gets an empty DB_HOST — a failure that surfaces at first connection, far from its cause. That's why launchfile validate reports any reference outside the standard vocabulary as an advisory warning, never an error. Run it before you deploy.

In the wild

Metabase is a business-intelligence tool that refuses the single-URL shortcut — its config wants every database detail in its own variable. Its Launchfile is the property vocabulary at full stretch.

metabase/LaunchfileView on GitHub
version: launch/v1
name: metabase
description: "Business intelligence and analytics tool"
repository: https://github.com/metabase/metabase
logo: https://raw.githubusercontent.com/metabase/metabase/master/resources/frontend_client/app/assets/img/logo.svg

image: metabase/metabase:latest
provides:
  - protocol: http
    port: 3000
    exposed: true
requires:
  - type: postgres
    set_env:
      MB_DB_HOST: $host
      MB_DB_PORT: $port
      MB_DB_DBNAME: $name
      MB_DB_USER: $user
      MB_DB_PASS: $password
env:
  MB_DB_TYPE:
    default: "postgres"
  MB_ENCRYPTION_SECRET_KEY:
    generator: secret
    sensitive: true
    description: "Secret key for encrypting database credentials"
health:
  path: /api/health
  start_period: 120s
  interval: 10s
  timeout: 5s
  retries: 12
restart: always
MB_DB_HOST: $host
Bare $prop resolves against the enclosing resource — this is the Postgres host, not the app's.
MB_DB_DBNAME: $name
$name is the database name from the vocabulary — one letter off ($naame) and it would resolve to empty string, which is exactly what validate's advisory warning exists to catch.
MB_DB_TYPE:
A plain literal default — not every value needs an expression.
generator: secret
The Lesson 3 machinery, side by side with resource expressions — one grammar across the whole file.

Try it: npx launchfile up metabase to launch this app locally.View in catalog

Check your understanding

Inside a requires entry's set_env, what does a bare $host resolve to?
Key takeaways
  • Every expression in a Launchfile is one grammar: bare $prop for the enclosing resource, dotted paths for everything else, ${...} to compose inside strings, ${prop:-default} for fallbacks, $$ for a literal dollar.
  • Dotted paths resolve namespaces in a fixed order — app, secrets, components, storage, then resource names. app and storage are reserved.
  • Use name: when you require two resources of the same type — references become $primary-db.host instead of an ambiguous $postgres.host.
  • version: constrains provisioning with semver ranges; config: passes type-specific hints that unsupporting platforms ignore.
  • Each resource type has a standard property vocabulary. An unknown property resolves to empty string — launchfile validate warns about it before your app fails at first connection.

Course complete!

You're ready to write Launchfiles for any application.

esc
Type to search the docs