Menu
Homerun docs

These are the canary docs, read from main: they can describe changes no release has shipped yet. Read the v1.0.64 docs.

Architecture

What runs where, who talks to whom, over what, and why. It's the map to read before debugging a request that crosses two processes, or before adding a new integration.

The shape in one paragraph: the SvelteKit app owns the database, the business rules and every user-facing surface (dashboard, REST API, MCP server, webhooks). It never touches Docker itself. The Go worker is the only process with a read-write Docker socket: it runs the long jobs it leases from Postgres (deploys, builds, scans, backups, cron runs, cleanups) and serves a small authenticated HTTP API the app calls for everything Docker that a page is waiting on. Traefik routes every hostname, reading container labels for services and files the app writes for everything else. Everything outside the host is optional and turned on from the dashboard.

#Diagram

flowchart LR
  subgraph clients["Clients"]
    browser["Browser (dashboard)"]
    cli["homerun CLI"]
    mcp["MCP clients (Claude, agents)"]
    installer["Installer"]
  end
  subgraph external["External services"]
    git["Git providers (GitHub, GitLab, Gitea, Bitbucket)"]
    dns["DNS provider APIs"]
    pangolin["Pangolin"]
    s3["S3 storage"]
    fileserver["SFTP, SMB and WebDAV storage (NAS, Storage Box)"]
    notify["Notification targets (Discord, Slack, Telegram, webhook, SMTP, browser push services)"]
    acme["Let's Encrypt"]
    registries["Container registries (Docker Hub, GHCR, build cache)"]
  end
  subgraph host["Homerun host"]
    traefik["Traefik (traefik:v3)"]
    app["SvelteKit app"]
    worker["Go worker (homerun-worker)"]
    postgres["Postgres (postgres:18-alpine)"]
    docker["Docker daemon"]
    services["Deployed services"]
    registry["Built-in registry (homerun-mirror)"]
    newt["Newt tunnel (homerun-newt)"]
  end
  subgraph remote["Other machines"]
    buildhost["Build servers / remote hosts"]
    swarmnodes["Swarm worker nodes"]
  end
  browser -->|"HTTPS on ports 80/443, HTTP/3 on 443/udp"| traefik
  traefik -->|"HTTP to port 3000"| app
  traefik -->|"forwardAuth to /api/v1/auth-check (homerun-auth alias)"| app
  traefik -->|"HTTP on the homerun network"| services
  app -->|"YAML files in the traefik-dynamic volume (file provider, /etc/traefik/dynamic)"| traefik
  traefik -->|"ACME HTTP-01 challenge"| acme
  app -->|"HTTP to worker:7430 + Bearer WORKER_TOKEN (derived from AUTH_SECRET when unset)"| worker
  app -->|"SQL to postgres:5432 (Drizzle over bun-sql)"| postgres
  worker -->|"SQL to postgres:5432 (pgx)"| postgres
  worker -->|"Docker socket (unix://)"| docker
  traefik -->|"Docker socket, read-only"| docker
  docker -->|"containers or swarm services"| services
  docker -->|"registry:2 container on 127.0.0.1:5055"| registry
  worker -->|"Registry v2 HTTP (skopeo in helper containers)"| registry
  docker -->|"Registry v2 HTTPS"| registries
  worker -->|"HTTPS POST /v1/build + Bearer token (worker in agent mode)"| buildhost
  worker -->|"Docker Engine API over tcp:// (TLS) or ssh://"| buildhost
  worker -->|"SSH with Homerun's key"| buildhost
  app -->|"HTTPS GET /v1/stats + Bearer token"| buildhost
  buildhost -->|"HTTPS /api/v1/nodes/enroll + one-time hrn_ token"| app
  swarmnodes -->|"docker swarm join"| docker
  worker -->|"git clone in an alpine/git helper (on the building daemon)"| git
  app -->|"HTTPS REST with each user's OAuth token"| git
  git -->|"HTTPS webhook to /api/v1/webhooks/git/{serviceId}"| app
  app -->|"HTTPS provider APIs"| dns
  app -->|"HTTPS Pangolin API"| pangolin
  docker -->|"container or one-replica swarm service"| newt
  newt -->|"outbound tunnel"| pangolin
  newt -->|"HTTPS on the homerun network"| traefik
  worker -->|"S3 API multipart PUT and GET"| s3
  app -->|"S3 ListObjectsV2 (SigV4)"| s3
  worker -->|"SFTP, SMB or WebDAV from an rclone helper container"| fileserver
  app -->|"HTTPS webhooks, Telegram Bot API, SMTP, Web Push"| notify
  cli -->|"HTTPS REST /api/v1 + x-api-key"| app
  mcp -->|"Streamable HTTP /api/v1/mcp + OAuth access token or API key"| app
  services -->|"HTTPS to the dashboard URL"| app
  installer -->|"docker compose pull and up"| docker
FromToHowWhy
Browser (dashboard)Traefik (traefik:v3)HTTPS on ports 80/443, HTTP/3 on 443/udpReach the dashboard and every deployed app by hostname, with TLS.
Traefik (traefik:v3)SvelteKit appHTTP to port 3000Route the dashboard hostname to the app.
Traefik (traefik:v3)SvelteKit appforwardAuth to /api/v1/auth-check (homerun-auth alias)The per-app login wall: every request to a gated service is checked first.
Traefik (traefik:v3)Deployed servicesHTTP on the homerun networkRoute slug.baseDomain and custom domains to each service's container port.
SvelteKit appTraefik (traefik:v3)YAML files in the traefik-dynamic volume (file provider, /etc/traefik/dynamic)Dashboard router, redirects and custom certificates, which need no container and keep working when Docker is wedged.
Traefik (traefik:v3)Let's EncryptACME HTTP-01 challengeIssue and renew TLS certificates for every routed hostname.
SvelteKit appGo worker (homerun-worker)HTTP to worker:7430 + Bearer WORKER_TOKEN (derived from AUTH_SECRET when unset)The app holds no Docker socket: status, start/stop, logs, terminal, stats and swarm calls all go through the worker.
SvelteKit appPostgres (postgres:18-alpine)SQL to postgres:5432 (Drizzle over bun-sql)All state, and the job queue: the app queues and prepares jobs, then finalizes them.
Go worker (homerun-worker)Postgres (postgres:18-alpine)SQL to postgres:5432 (pgx)Leases execute-stage job rows, heartbeats, writes deploy logs and results, so no job is lost on a restart.
Go worker (homerun-worker)Docker daemonDocker socket (unix://)The only read-write socket holder: deploys, builds, scans, backups, cron jobs, exec and logs.
Traefik (traefik:v3)Docker daemonDocker socket, read-onlyDiscover routers from container labels (Docker provider).
Docker daemonDeployed servicescontainers or swarm servicesRuns what you deploy.
Docker daemonBuilt-in registry (homerun-mirror)registry:2 container on 127.0.0.1:5055Created on demand as the image mirror, optionally a private registry.
Go worker (homerun-worker)Built-in registry (homerun-mirror)Registry v2 HTTP (skopeo in helper containers)Copies each deploy's image through the mirror so Trivy scans it before it runs.
Docker daemonContainer registries (Docker Hub, GHCR, build cache)Registry v2 HTTPSPull service images; builds push to a build cache registry when one is set.
Go worker (homerun-worker)Build servers / remote hostsHTTPS POST /v1/build + Bearer token (worker in agent mode)Build a git service on another machine without exposing its Docker daemon.
Go worker (homerun-worker)Build servers / remote hostsDocker Engine API over tcp:// (TLS) or ssh://The other build-server kind: build straight on a remote daemon.
Go worker (homerun-worker)Build servers / remote hostsSSH with Homerun's keyMachine terminals: a browser shell on this server and every remote host.
SvelteKit appBuild servers / remote hostsHTTPS GET /v1/stats + Bearer tokenVerify an agent's token before saving it as a build server.
Build servers / remote hostsSvelteKit appHTTPS /api/v1/nodes/enroll + one-time hrn_ tokenThe Add a server command installs and registers the machine itself.
Swarm worker nodesDocker daemondocker swarm joinExtra capacity: swarm places replicas on joined nodes.
Go worker (homerun-worker)Git providers (GitHub, GitLab, Gitea, Bitbucket)git clone in an alpine/git helper (on the building daemon)Fetch the source for a git-based build.
SvelteKit appGit providers (GitHub, GitLab, Gitea, Bitbucket)HTTPS REST with each user's OAuth tokenConnect accounts, browse repos, register webhooks, poll branches, read status checks.
Git providers (GitHub, GitLab, Gitea, Bitbucket)SvelteKit appHTTPS webhook to /api/v1/webhooks/git/{serviceId}Deploy on push and pull request previews.
SvelteKit appDNS provider APIsHTTPS provider APIsCreate and update records for every hostname a service answers on.
SvelteKit appPangolinHTTPS Pangolin APICreate a resource and target per hostname when Pangolin is the DNS provider.
Docker daemonNewt tunnel (homerun-newt)container or one-replica swarm serviceRuns the tunnel client when Pangolin's Newt credentials are set.
Newt tunnel (homerun-newt)Pangolinoutbound tunnelPublish this host's routes with no inbound ports open.
Newt tunnel (homerun-newt)Traefik (traefik:v3)HTTPS on the homerun networkHand tunnelled requests to the usual Traefik routers.
Go worker (homerun-worker)S3 storageS3 API multipart PUT and GETStream volume backups up and restores down.
SvelteKit appS3 storageS3 ListObjectsV2 (SigV4)List existing backups for the restore picker.
Go worker (homerun-worker)SFTP, SMB and WebDAV storage (NAS, Storage Box)SFTP, SMB or WebDAV from an rclone helper containerStream volume backups up and restores down, and list them for the restore picker.
SvelteKit appNotification targets (Discord, Slack, Telegram, webhook, SMTP, browser push services)HTTPS webhooks, Telegram Bot API, SMTP, Web PushNotifications (to a closed browser too), invites and emailed sign-in codes.
homerun CLISvelteKit appHTTPS REST /api/v1 + x-api-keyDeploy, inspect and manage from a terminal or CI.
MCP clients (Claude, agents)SvelteKit appStreamable HTTP /api/v1/mcp + OAuth access token or API keyAI agents manage services; each tool calls the REST API as the caller.
Deployed servicesSvelteKit appHTTPS to the dashboard URLSentry-compatible error ingest and Sign in with Homerun (OIDC).
InstallerDocker daemondocker compose pull and upInstall and upgrade the app, worker, Traefik and Postgres stack.

#Reading it

  • Two ways into the worker, on purpose. Anything that must survive a restart (a deploy, a backup) is a row in the job table that the worker leases and heartbeats. Anything a page is waiting on right now, or that streams (logs, the terminal, host stats), is an HTTP call to the worker. See Scheduling and the job queue.
  • Two ways into Traefik. Services are routed from their container labels, which Traefik reads through its read-only Docker socket. The dashboard's own route, redirects and custom certificates are YAML files the app writes into a volume Traefik watches, so they don't depend on a container being recreated or on Docker answering. See Networking and Redirects.
  • Build servers are for building only. A service always runs on this host (or on swarm nodes joined to it). See Build servers & the Homerun Agent and Swarm mode.
  • Pangolin or a DNS provider, not both. One DNS integration runs at a time. See DNS automation.

#How it's generated

scripts/generate-architecture.ts builds the section between the markers from one typed list of components and edges, and runs as part of bun run gen, so CI fails when the committed page is stale. It reads the installer's compose file for what it can: the stack's services and images, the worker URL and port, the database host, the Traefik flags and shared dynamic-config volume, and which containers mount the Docker socket or get a DATABASE_URL. A new compose service, socket holder or database client without an entry in the model fails the script instead of silently going missing. Edit the model in the script, never the generated block; the prose around it is hand-written.

This guide lives in the project repo: edit it there, and this page follows within a day.