Getting started
Homerun needs three things at runtime: a Docker socket to manage containers, a
Postgres database, and Traefik for routing and TLS. Pick whichever install path
fits, both run entirely from prebuilt release binaries and Docker images, and
neither needs Bun, git, or a source checkout on the target host. Want to run
from source instead, to develop on it? See
CONTRIBUTING.md.
You configure Homerun from its own dashboard, not from files. The one-liner below sets up everything the container needs to start and then hands you a first-run wizard; from that point on, base domain, Docker, Traefik, email, sign-in methods and DNS are all settings pages you click through. There's no config file you have to maintain, see Configuration for the handful of exceptions and why they exist.
Supported systems
The one-liner installer (Option A) needs Linux on amd64 or arm64, with
systemd, and with apt-get, dnf or yum. It installs Docker through
get.docker.com, so it only works on distros Docker
itself publishes packages for.
| Distro | Status |
|---|---|
| Ubuntu 24.04 LTS, Ubuntu 26.04 LTS | Supported, the installer is tested against both on real VMs |
| Ubuntu 22.04 LTS, Debian 12+ | Should work, same apt path, not tested on real hardware |
| Fedora, RHEL 9 / Rocky / Alma / CentOS Stream | Best effort, the dnf/yum path exists but has never run on a real box, report what breaks |
| CentOS 7, other end-of-life releases | Not supported, Docker no longer ships packages for them |
| Alpine, NixOS, Arch, anything without systemd or apt/dnf/yum | Not supported by the installer, use Option B |
Option B (Docker Compose) doesn't care about the distro: any host that already runs Docker Engine with the Compose plugin works, rootful included.
Option A, the one-liner (fresh Linux server)
curl -fsSL https://raw.githubusercontent.com/orochibraru/homerun/main/cmd/installer/bootstrap.sh \
| sudo bash -s -- --mode=fullThis downloads the prebuilt homerun-installer-<arch> release binary for your
host's architecture and runs it, which:
- Installs Docker Engine and enables the system (rootful) daemon.
- Creates a dedicated system user (
homerunby default), in thedockergroup, that owns the install directory,/home/homerun/homerun. - Runs
docker swarm init, advertising the address of the host's default route (--advertise-addr=<ip>to pick another one on a multi-interface host), and creates thehomerunbridge network plus the attachablehomerun-swarmoverlay that swarm services join. - Asks what domain (or IP) this instance will be reached at, offering the
host's own address as the default. It is never
localhost: that address becomes the app'sORIGIN, and better-auth only trusts that one origin, so alocalhostvalue makes the first sign-up fail with "Invalid origin" from any browser that isn't on the box. Pass--domain=homerun.example.comto answer it up front, which acurl | bashinstall has to do (no terminal to prompt on, so it takes the detected address otherwise). - Writes a standalone compose file and runs
docker compose up -dagainst it on the system daemon: Traefik (with both its docker and swarm providers on), Postgres, the app itself and aworkercontainer from the same image (the only one of the four that touches the Docker socket, see Configuration), all pulled from published images, then prints the dashboard URL.
The instance starts in swarm mode: every service deploys as a replicated swarm service, and more machines can join as workers. The trade-off is that the Docker daemon runs as root, so anything with access to its socket (Homerun itself, and any service you give the socket to) is effectively root on the host. Rootless Docker avoids that, but it can't create overlay networks, so it can't run a swarm.
To keep the old rootless setup instead, add --docker=rootless: Docker runs
under the homerun user (get.docker.com/rootless, a systemd --user unit,
lingering enabled), nothing Homerun deploys runs as root, and the instance runs
in standalone mode (one container per service, no replicas, no extra nodes).
Moving a rootless install to rootful + swarm
An instance installed rootless (the installer's default before swarm became the default) can be moved in place:
curl -fsSL https://raw.githubusercontent.com/orochibraru/homerun/main/cmd/installer/bootstrap.sh \
| sudo bash -s -- --migrate-to-rootful --yesIt stops everything on the rootless daemon (the stack and every deployed
service), enables the system daemon, copies every named volume across with file
ownership as the containers saw it, initialises the swarm and the networks,
rewrites compose.yaml and homerun.yaml for the system socket (keeping
.env, and a copy of the old compose file as compose.rootless.yaml), starts
the stack, switches the instance to swarm mode and queues a redeploy of every
service, which then come back as swarm services. Finally it stops and disables
the rootless daemon, without deleting its data, and prints the commands that
remove it once you're happy. If a step fails, fix the cause and run the same
command again: finished volume copies and the instance switch are recorded in
/home/homerun/homerun/.rootful-migration and skipped.
Two things it can't do for you, and lists at the end: containers Homerun didn't
create (not the stack, not a service) aren't moved, and host paths bind-mounted
into services keep the ownership the rootless user's subuid mapping gave them,
so a service running as a non-root user may need a chown there. Pass
--domain= if the instance's address can't be read from the old compose file.
The dashboard is routed through Traefik like any deployed service, so it's reachable at the domain you gave it, with a real certificate. Installing against a bare IP instead gets you Traefik's own self-signed certificate, since ACME can't issue for an IP; port 3000 works directly either way.
Run --mode=agent instead of --mode=full if you only want this box to run the
Homerun Agent as a remote build
server for a different Homerun instance, not the full app. Add --dry-run to
print every command without running anything, --version=vX.Y.Z to pin a
release instead of the latest one, and see
cmd/installer/README.md for the rest of the
flags (--user=, --port=, --image=).
The installer's mutating steps (package install, rootful and rootless Docker setup, the swarm, systemd units, the rootless-to-rootful migration) are verified live for both
--mode=agentand--mode=fullagainst real disposable VMs, seecmd/installer/README.mdfor what was checked (and the real bugs that run found and fixed).--dry-runfirst is still a good habit on a box that matters.
Option B, Docker Compose
Already have Docker set up the way you want it (rootful is fine here) and just
want the stack? compose.prod.yaml runs Traefik +
Postgres + the app + a worker container (see
Configuration for what that is), all
pulled from published images, no installer, no rootless setup, no source
checkout:
curl -fsSLO https://raw.githubusercontent.com/orochibraru/homerun/main/compose.prod.yaml
curl -fsSLO https://raw.githubusercontent.com/orochibraru/homerun/main/.env.example
mv .env.example .env && $EDITOR .env
docker network create homerun
docker compose -f compose.prod.yaml up -dThere are exactly two values to fill in, both in .env, and then you're
done with files for good:
AUTH_SECRET, any long random string (openssl rand -hex 32). It signs your sessions and encrypts every secret Homerun stores, so don't leave it blank and don't change it later.ORIGIN, the scheme, host and port you actually type in the address bar to reach the dashboard:http://203.0.113.10:3000, orhttps://homerun.example.com. Leaving it atlocalhostwhile reaching the instance from another machine makes the first sign-up fail with "Invalid origin". See Configuration.
docker compose refuses to start without either one, on purpose, rather than
booting into a broken instance.
.env is the only file the stack needs. Everything a YAML config file could
hold is a /settings field in the dashboard instead, and you only mount one if
you'd specifically rather manage settings as a file, see
the optional YAML file.
Everything else in .env already has a working default. The ones worth knowing
about are DASHBOARD_DOMAIN (serve the dashboard on a real hostname through
Traefik with a real certificate, instead of port 3000 with a self-signed one)
and ACME_EMAIL, see
Compose-only variables.
compose.prod.yaml is self-contained: it doesn't extends: the in-repo
tools/compose/*.yaml fragments that compose.yaml shares, so a downloaded
copy works on its own. compose.yaml (the dev-only variant,
no app service) is the reference if you'd rather run each container by hand.
First boot
Visit the app at the address the installer printed, or the ORIGIN you set
(http://localhost:5173 in dev). The first account you create becomes admin
automatically. After that there's no public sign-up: every other account is
created by an admin from /users, either directly (name, email, role, no
password to hand over — they choose their own at their first sign-in, see
Users and roles) or by email invite once SMTP is
configured.
Signing in for the first time drops you into a six-step onboarding wizard:
- Core, your base domain (the DNS suffix deployed services are routed
under, so a service lands at
<slug>.<your domain>) and the dashboard URL. - Docker, the socket path and the shared network name. The detected defaults are almost always right.
- Traefik, which entrypoint and certificate resolver your services' routes should use.
- Email, optional SMTP, only needed for invite emails.
- DNS, optional Cloudflare or Pangolin automation, so every service you deploy gets its DNS record or tunnel route created for you. Each has a Test button that checks the credentials before you finish. For Pangolin, paste the site's Newt endpoint, ID and secret and Homerun runs the Newt tunnel client for you.
- Review, confirm and finish.
Everything it asks is also editable afterwards from /settings, and nothing it
skips is hidden in a file, see Configuration.
Your first service
- Services → Deploy a Service, or start from Templates and pick something from the built-in catalog, which is the fastest way to see it work end to end. Quick Deploy on a template card creates and deploys it with no further input.
- The wizard walks four steps: basic info, networking (container port and whether it gets a public subdomain), environment variables, and compute limits.
- Create and Deploy drops you on the service's Overview tab with live progress, pull, create, start, streaming as it happens.
- Once it's running, it's reachable at
<slug>.<your base domain>over TLS, with no DNS or reverse-proxy work on your side beyond pointing the domain at this host.
See the docs index for everything the wizard doesn't cover: git-based builds, volumes, logs, the web terminal, scheduled redeploys, and the per-app login wall.
Health check
GET /api/health returns a plain 200 OK, useful for a container healthcheck
or an uptime monitor, and unauthenticated by design (the same "describes shape,
not data" carve-out as /api/v1/openapi.json, see API & CLI).
Next steps
- Services, deploy your first service.
- Configuration, what's settable and where.
- Users and roles, inviting people, and Authentication providers, adding SSO.
- The dashboard, System Logs and Docker Cleanup, day-two housekeeping.
This guide lives in the project repo: edit it there, and this page follows within a day.