Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting started

This page walks through going from nothing to a running service, using hll the whole way.

Installing hllc

Every merge to main cuts a tagged release with a prebuilt Linux x86-64 hllc binary attached—no Rust toolchain required:

curl -Lo hllc https://github.com/travisboettcher/hl-lang/releases/latest/download/hllc-linux-x86_64
chmod +x hllc
./hllc --version

Put hllc somewhere on your PATH, for example ~/.local/bin, so the rest of this page can just call hllc directly. Pin to a specific release tag instead of latest for anything you intend to keep reproducible, such as CI or a deploy script.

Linux x86-64 is the only platform this project tests or supports—see the main repository’s README. If you’re on a different platform, building from source—also covered in the README—may work, but it’s untested.

Your first service

Create a file called jellyfin.hll:

service jellyfin {
  image "jellyfin/jellyfin:latest"
  expose 8096
  volume "/mnt/media" -> "/data"
  env PUID = "1000"
  restart unless-stopped
}

This declares one service named jellyfin, running the jellyfin/jellyfin:latest image, reachable on container port 8096, with a bind mount, one environment variable, and a restart policy of unless-stopped.

Reaching it from a hostname is a separate matter, and deliberately not part of the language—see Routing once the basics here make sense.

Compile it:

hllc build jellyfin.hll

With no --out, build prints the generated Compose YAML straight to your terminal—a Compose services: block for jellyfin, with image, expose, volumes, environment, restart and networks. Skim it—the mapping from the .hll fields you wrote to the YAML fields it produces should be fairly direct.

The first two lines are a # Generated by hllc comment. Compose ignores it, but it marks the file as compiler output—and hllc refuses to overwrite a file that doesn’t have it, so pointing build at a directory can’t quietly eat a docker-compose.yml you wrote by hand (see the command-line tool page).

To write the result to disk instead of printing it:

hllc build jellyfin.hll --out docker-compose.yml
docker compose -f docker-compose.yml up -d

That’s a complete, deployable service from six lines of hll.

Adding a second service

A single .hll file can declare more than one service, and a real homelab usually wants several. Add a second service to the same file, or start a new one—either works, since hllc build treats one input file as one Compose document that may hold multiple services:

volume uptime-kuma-data {}

service jellyfin {
  image "jellyfin/jellyfin:latest"
  expose 8096
  volume "/mnt/media" -> "/data"
  env PUID = "1000"
  restart unless-stopped
}

service uptime-kuma {
  image "louislam/uptime-kuma:latest"
  expose 3001
  volume uptime-kuma-data -> "/app/data"
  restart unless-stopped
}

uptime-kuma-data names a Docker-managed named volume rather than a host path, so it needs the top-level volume uptime-kuma-data {} declaration at the top of the file—the same way a networks [x] entry needs a network x { ... }. The /mnt/media that jellyfin mounts starts with a /, which makes it a bind mount, and bind mounts need no declaration. See volume for the full rule.

Removing repetition with a template

Both preceding services repeat restart unless-stopped, and a real homelab tends to repeat far more than that across every service—the same proxy network, the same routing labels, the same PUID/PGID pair. That repetition is what template and with are for:

template baseline {
  restart unless-stopped
}

volume uptime-kuma-data {}

service jellyfin {
  with baseline
  image "jellyfin/jellyfin:latest"
  expose 8096
  volume "/mnt/media" -> "/data"
  env PUID = "1000"
}

service uptime-kuma {
  with baseline
  image "louislam/uptime-kuma:latest"
  expose 3001
  volume uptime-kuma-data -> "/app/data"
}

Both services now pick up restart unless-stopped from one place. A template applies only where a with names it, so nothing happens behind your back and a service can opt out by leaving the line off. Templates & Composition covers parameters and the merge rules in full. Imports covers sharing templates like this across every .hll file in your homelab instead of just within one file.

Where to go next

  • Syntax Basics—how a statement, a body, and the primary-value shorthand from the preceding example (expose 8096 instead of writing the full expose { port: 8096 } body) actually work.
  • Built-in Fields—every field hll understands, what it accepts, and its defaults.
  • Routing—getting a hostname to reach one of these services, with the Traefik templates hllc ships.
  • The hllc command-line tool—building a whole directory of services at once, not just one file.