Module 3 · Lesson 7

Specialized Patterns

Cron jobs, prebuilt images, host access, and optional dependencies.

What you'll learn
  • Scheduled tasks with schedule and restart: no
  • How runtime, build, and image interact — and platform pinning
  • Host access for CI runners and orchestrators
  • Optional dependencies with supports

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.

1
A minimal cron job. The schedule is a standard cron expression (midnight daily). restart: no tells the provider 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"
2
The full example adds a Postgres dependency. The provider spins up the task on schedule, injects the database URL, runs the script, and shuts it down.
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"
No provides needed

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.

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; tooling and buildpack-style providers use it.
  • 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 what build produces.
Docker Compose semantics

The build and image fields mirror Docker Compose:

  • image alone → pull the image from a registry.
  • build alone → build from a Dockerfile; the provider names the artifact.
  • build + image → build from source and tag the result as image (useful when you want to push to a registry).
  • runtime + anything → runtime is 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.

Platform pinning

Many third-party images only ship for linux/amd64. The platform field tells the provider which OCI architecture to pull — important on ARM hosts (Apple Silicon dev machines, Graviton, Raspberry Pi) so the right variant is fetched or emulation is arranged.

hedgedoc-backend/Launchfile View on GitHub
version: launch/v1
name: hedgedoc-backend
image: ghcr.io/hedgedoc/hedgedoc/backend:develop
platform: linux/amd64
provides:
  - protocol: http
    port: 3000
    bind: "0.0.0.0"
requires:
  - type: postgres
    set_env:
      HD_DATABASE_URL: $url
health:
  path: /api/private/config
  start_period: 60s
image:
A specific version tag, not :latest — prebuilt deploys should be reproducible.
platform:
Declares the target architecture. This image only ships amd64; providers on ARM hosts need to know so they can emulate or refuse.
requires:
Prebuilt images declare dependencies the same way as source-based apps — the provider injects env vars before starting the container.
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. The host field declares these requirements explicitly.

There are three host access types: host.docker for Docker daemon access, host.network for sharing the host network stack, and host.filesystem for reading or writing host paths.

Add host.privileged: true when the app needs elevated privileges — e.g. device access for monitoring agents or kernel-level tools. Providers treat this as the most sensitive host flag and may refuse it in managed environments.

Security-sensitive

Host access breaks the container isolation boundary. Providers may restrict or refuse apps that request it. Always document why your app needs host access in the Launchfile's description.

Here's a deployment tool that needs all three:

launchpad/Launchfile View on GitHub
version: launch/v1
name: launchpad
description: DevOps agent — give it a GitHub URL, get a running app
runtime: bun

host:
  docker: required        # Needs Docker daemon (not Docker-in-Docker)
  network: host           # Shares host network to manage container ports
  filesystem: read-write  # Persistent state in ~/.launchpad/

requires:
  - type: docker
    description: Orchestrates app containers on the host

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:
Declares exactly what host-level access this app requires.
docker: required
Needs the real Docker daemon — Docker-in-Docker won't work.
network: host
Shares the host network to manage container ports directly.
filesystem: read-write
Persistent state stored on the host filesystem.

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. The provider provisions them if available, but the app still starts without them.

Required (app won't start without it)
requires:
  - type: postgres
    set_env:
      DATABASE_URL: $url
Optional (app works without it)
supports:
  - type: redis
    set_env:
      REDIS_URL: $url

Your 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.

uptime-kuma/Launchfile View on GitHub
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: always
image:
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

What's the difference between `requires` and `supports`?
Key takeaways
  • Cron jobs use schedule + restart: no — no provides needed.
  • runtime, build, and image describe three different things and can coexist: runtime is metadata, build creates the image, image names it. Use platform: to pin OCI architecture.
  • Host access (host.docker, host.network, host.filesystem) is security-sensitive — declare it explicitly.
  • supports: is like requires: but optional — the app gracefully degrades without it.

Course complete!

You're ready to write Launchfiles for any application.

esc
Type to search the docs