Documentation
Self-hosting
Halo runs on your own infrastructure with no external services: a Go server, a Next.js web interface, PostgreSQL, and a reverse proxy that terminates TLS. The repository ships everything needed to run it on one host with Docker Compose.
Follow deploy/README.md for the step-by-step guide. It covers DNS, the environment file, starting the services, creating the first administrator, backups, upgrades, health checks and rotating HALO_SECRET_KEY. On a fresh host, curl -fsSL https://halo.scala.gg/install.sh | sh runs the setup steps for you; see Install with the script.
What runs
internet ──► caddy :80 :443 ──► halo-web :3200 ──► halo-server :8080 ──► postgres :5432
- Caddy obtains a certificate for your domain and forwards every request to the web interface.
- halo-web serves the console, sign-in pages and account portal, and forwards
/api,/oauth2,/.well-known,/samland/scimto the Go server, so everything shares one address. - halo-server runs
halo serve. It applies database migrations every time it starts. - PostgreSQL 17 stores all data.
Only Caddy is reachable from outside. The images are published to ghcr.io/scalastudios for linux/amd64 and linux/arm64: halo-server is a static Go binary on a distroless base that runs as a non-root user, and halo-web is a Next.js standalone server on Node.js 22. Both are built from deploy/Dockerfile.server and deploy/Dockerfile.web.
Decide before you install
- The domain. Halo's address is the OpenID Connect issuer that every application stores, and passkeys only work on the hostname they were created for. Moving Halo to a new hostname later means reconfiguring every application and giving everyone a new setup link. Pick a name you intend to keep, such as
auth.example.com. - The secret key.
HALO_SECRET_KEYencrypts the token signing keys, authenticator-app secrets and other stored secrets, and a database backup is useless without the key that was in use when it was taken. Store it in your password manager when you create it. To replace it, stop Halo and runhalo rotate-secret-keyas described in Rotate HALO_SECRET_KEY; recovery codes do not survive a rotation, so the people who had them generate new ones. - One server. Run a single
halo-server. Email and webhook delivery coordinate through the database, but other background jobs, such as outbound SCIM, access expiry and lifecycle rules, run in every server process without coordinating with other processes. - Email. Halo works without email: invite and reset links are shown to the administrator who creates them, to pass on. With an SMTP server in the
HALO_SMTP_*variables, Halo also emails those links and people can sign in with magic links.
Other ways to run Halo
The Compose files are one way to run the same three programs. To run them another way, such as on Kubernetes:
- Use the published images, whose web image forwards to
http://halo-server:8080. To use another address for the Go server, build the images with the commands in deploy/README.md, and pass that address asHALO_API_URLboth when you build the web image and when you run it. - Expose only the web interface, behind a proxy that terminates TLS and sets
X-Forwarded-For. - Point liveness and readiness probes at port 8080, path
/healthz, on the Go server. It answers200when the database responds. - Give the Go server the variables in Configuration, with
HALO_PUBLIC_URLset to the public https address.