Cron jobs
Not every process is a long-running server. Sometimes you need a task that runs on a schedule — a nightly database sync, a weekly report, a cleanup job. Launchfile handles this with two fields: schedule and restart: no.
schedule is a standard cron expression (midnight daily). restart: no declares this is a one-shot task, not a daemon.name: daily-sync
runtime: node
schedule: "0 0 * * *"
restart: "no"
commands:
start: "node scripts/sync.js"schedule yet — a provider that doesn't must warn at launch, so a declared job is never silently skipped.version: launch/v1
name: daily-sync
runtime: node
schedule: "0 0 * * *"
restart: "no"
requires:
- type: postgres
set_env:
DATABASE_URL: $url
commands:
start: "node scripts/sync.js"Cron jobs don't have provides: — they're not servers, they don't listen on ports, and nothing connects to them. They just run, do their work, and exit.
A related knob: singleton: true tells the platform it must never run more than one instance of a component. Use it where two copies would collide — a scheduler double-firing jobs, a migration runner racing itself. It's a declaration about the app's nature, so it belongs in the file, not in deploy config.
Images, builds, and runtimes
Lesson 1 introduced two paths: runtime + commands.start for building from source, or image for pulling a prebuilt container. There's a third field, build, for Dockerfile-based builds — and these three can coexist.
Each answers a different question:
runtime:— what language is this? Metadata only; used by tooling and buildpack-style deploys.build:— how do I build from source? Dockerfile path, context, build args, and build-time secrets.image:— what's the image called? Either the image you pull, or the tag of whatbuildproduces.
The build and image fields mirror Docker Compose:
imagealone → pull the image from a registry.buildalone → build from a Dockerfile; the resulting image is named for you.build+image→ build from source and tag the result asimage(useful when you want to push to a registry).runtime+ anything →runtimeis metadata; it doesn't change how the container is made.
Lesson 5 distinguished commands.build (shell commands inside an already-built container) from the top-level build: block (Dockerfile-based image builds). They're different layers: commands.build runs inside the container, build: creates the container.
A Dockerfile and a prebuilt image are provider specializations — one family of providers knows what to do with them, others don't. If they're your app's only way to run — no runtime, no commands — then a provider that doesn't speak that specialization has nothing to fall back on. launchfile validate flags this as a reduced-portability warning: non-fatal, suppressible, and never emitted by operational commands like up. To clear it, keep a portable contract alongside the specialization — declare the runtime and commands that would let any provider build and run the app from source.
Platform pinning
Many third-party images only ship for linux/amd64. The platform field declares which OCI platform(s) the image supports (e.g. linux/amd64) — important on ARM hosts (Apple Silicon dev machines, Graviton, Raspberry Pi) so the right variant is fetched or emulation is arranged.
version: launch/v1
name: hedgedoc-backend
image: ghcr.io/hedgedoc/hedgedoc/backend:develop
platform: linux/amd64
provides:
- protocol: http
port: 3000
exposed: true
bind: "0.0.0.0"
requires:
- type: postgres
set_env:
HD_DATABASE_URL: $url
health:
path: /api/private/config
start_period: 60simage:- A specific version tag, not :latest — prebuilt deploys should be reproducible.
platform:- Declares the platform the image supports. This image only ships amd64, so on an ARM host it must be emulated or refused.
requires:- Prebuilt images declare dependencies the same way as source-based apps — env vars are injected before the container starts.
health:- Health checks apply to prebuilt images too. start_period gives the container time to initialize before checks begin.
Host access
Some apps need direct access to the host machine — CI runners that manage Docker containers, deployment tools that need the Docker socket, monitoring agents that read host metrics. You declare that access as a host capability entry: a host:-marked entry in requires or supports.
A capability is not a backing service. The provider doesn't provision anything — it either grants the capability (mounts or forwards the underlying coordinate) or refuses the deployment with a clear message. The value names an interface, not a product: container_runtime: docker means the Docker Engine API, so a Podman-compatible socket satisfies it too.
requires:
- host: { container_runtime: docker } # granted or refused
set_env:
DOCKER_HOST: $url # e.g. unix:///var/run/docker.sock
supports:
- host: { container_runtime: any } # optional — deploy, probe, degradeThe four standard capabilities are container_runtime (container-runtime API access), network: host (share the host network stack), filesystem (read-write or read-only host filesystem access), and privileged: true for elevated privileges — e.g. device access for monitoring agents or kernel-level tools. privileged is the most sensitive of the four; managed environments may refuse it.
Host access breaks the container isolation boundary, so it may be restricted or refused where it's deployed. Always document why your app needs host access in the Launchfile's description.
Here's a deployment tool that needs three of them:
version: launch/v1
name: launchpad
description: DevOps agent — give it a GitHub URL, get a running app
runtime: bun
requires:
- host: { container_runtime: docker } # the real Docker Engine API
set_env:
DOCKER_HOST: $url # e.g. unix:///var/run/docker.sock
- host: { network: host } # manages container ports directly
- host: { filesystem: read-write } # persistent state in ~/.launchpad/
provides:
- protocol: http
port: 3001
exposed: true
env:
ANTHROPIC_API_KEY:
required: true
LAUNCHPAD_HOME:
default: ~/.launchpad
commands:
install: bun install
build: bun run build
start: bun run src/server.ts
health: /api/health- host: { container_runtime: docker }- Needs the real Docker Engine API — Docker-in-Docker won't work.
DOCKER_HOST: $url- Wires the granted coordinate into the app's environment.
- host: { network: host }- Shares the host network to manage container ports directly.
- host: { filesystem: read-write }- Persistent state stored on the host filesystem.
A granted container_runtime exposes three coordinates you can wire with set_env: $socket (the runtime socket path, e.g. /var/run/docker.sock), $url (a DOCKER_HOST-style connection string), and $api (an HTTP API endpoint when the runtime is reachable over the network). Put the entry under requires when the app can't run without it, or supports when it degrades gracefully. launchfile validate prints every requested capability as a host capabilities requested: summary, so the privilege surface is always visible. See the host capability example.
The legacy host: block (deprecated)
Older Launchfiles declare the same needs in a top-level host: block. That block is deprecated in launch/v1 and removed in launch/v2. Files using it stay valid and keep their exact meaning for the whole of launch/v1 — launchfile validate reports each deprecated key with its migration and still exits 0. Write new files with capability entries; migrate old ones when convenient.
# Deprecated — the block form
host:
docker: required
network: host
filesystem: read-writeEach key maps to one entry:
| Legacy key | Capability entry |
|---|---|
docker: required | requires: [ - host: { container_runtime: docker } ] |
docker: optional | supports: [ - host: { container_runtime: docker } ] |
network: host | requires: [ - host: { network: host } ] |
filesystem: read-write | requires: [ - host: { filesystem: read-write } ] |
privileged: true | requires: [ - host: { privileged: true } ] |
A key set to its default (network: bridge, filesystem: none, privileged: false) declares no need at all — drop it instead of writing an entry.
Optional dependencies
You've been using requires: for dependencies your app can't run without. But some dependencies are optional — a Redis cache that improves performance but isn't mandatory, or an S3 bucket for file uploads that falls back to local storage.
Use supports: for these. They're provisioned if available, but the app still starts without them.
requires:
- type: postgres
set_env:
DATABASE_URL: $urlsupports:
- type: redis
set_env:
REDIS_URL: $urlYour app should check whether the environment variable is set and gracefully degrade when it isn't. The Launchfile just declares the intent — your code handles the fallback.
In the wild
Uptime Kuma is a self-hosted monitoring tool. It's one of the simplest prebuilt-image patterns in the catalog — just an image, a port, persistent storage, and a health check.
version: launch/v1
name: uptime-kuma
description: "Self-hosted monitoring tool"
repository: https://github.com/louislam/uptime-kuma
logo: https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.svg
image: louislam/uptime-kuma:1
provides:
- protocol: http
port: 3001
exposed: true
health: /
storage:
data:
path: /app/data
persistent: true
restart: alwaysimage:- Pulls the official image from Docker Hub — no source build needed.
storage:- Persistent storage for monitoring data. Survives container restarts.
restart: always- Monitoring tools should always be running — restart on crash.
Try it: npx launchfile up uptime-kuma to launch this app locally.View in catalog
Check your understanding
- Cron jobs use
schedule+restart: no— noprovidesneeded. runtime,build, andimagedescribe three different things and can coexist:runtimeis metadata,buildcreates the image,imagenames it. Useplatform:to pin OCI architecture.- An app whose only build path is a Dockerfile or prebuilt image gets a reduced-portability warning from
validate— keepruntime+commandsalongside it so any provider can run the app. - Host access is security-sensitive — declare it explicitly, as a capability entry (
- host: { container_runtime: docker }) inrequires/supports. It's granted or refused, and wired via$socket/$url/$api. - The old top-level
host:block (host.docker,host.network,host.filesystem) is deprecated inlaunch/v1and removed inlaunch/v2— still valid, butvalidatewill show you the migration. supports:is likerequires:but optional — the app gracefully degrades without it.