Skip to main content

The Hill90 Homelab

Hill90 is homelab infrastructure on a single Hostinger VPS running AlmaLinux 10. It is the automation that takes a bare VPS to a running, TLS-terminated, observable, Tailscale-secured Docker host — and keeps it there. It is not a single-app host. An AI agent application runs on this VPS as a tenant of the platform, serving hill90.com from its own repository and consuming the platform’s identity, database and storage. See the AI Application section. The infrastructure lives in the jonhill90/Hill90 repository. This site is the narrative layer; the operational depth stays in that repo, beside the code it describes.

A personal cloud platform

The organising idea is capability parity with Azure. Jon works with Azure professionally, and each platform service here is the open-source counterpart of a primitive he uses at work — chosen so the concepts, failure modes and operational habits transfer in both directions. That framing is also the honest answer to why a homelab runs an identity provider at all. It is not single sign-on for four dashboards. It is capability parity plus real multi-user support: family accounts, demo accounts and test accounts in the one platform realm, with clients and roles separating consumers. The AI application is one tenant among several rather than the reason the platform exists. The reasoning behind the mapping, and the consequences that follow from it, are recorded in the Hill90 repository as Hill90 as an open-source mirror of the Azure platform primitives rather than restated here.

Single sign-on, and what it does not cover

Keycloak 26.4.0 serves a platform realm at https://auth.hill90.com/realms/platform. As of 2026-07-27 two services actually sign in through it: The local login is deliberately still there. Grafana runs with GF_AUTH_DISABLE_LOGIN_FORM=false and auto-login off, so the built-in admin account remains reachable. That is the point: wiring administration to an identity provider creates a dependency where an unhealthy Keycloak could lock you out of the tools you would use to fix it, and the fallback is what prevents that. Hill90 keeps a fallback runbook for exactly that case. Grafana also does not grant Keycloak users the Grafana admin role (ALLOW_ASSIGN_GRAFANA_ADMIN=false), so the highest privilege stays with the local account. Issue 530, which covered this work, was closed as completed on 2026-07-27 with the OpenBao third undone — so the remaining work is tracked as issue 552. MinIO now exists as a homelab service, so it joins that list.
Hill90’s own sso-fallback.md runbook states that SSO is wired into Grafana, Portainer and OpenBao. The vault has no OIDC auth method — verified from its mounted paths — so that runbook overstates the coverage. This page is the accurate one.

What is missing

The parity is partial, and the gaps are real rather than pending:
  • No container registry. Nothing corresponds to Azure Container Registry; images come from public registries and GHCR.
  • No messaging tier. Nothing corresponds to Service Bus or Event Hubs.
  • Object storage returned on 2026-07-31, and the cutover has since finished. MinIO went with the application teardown and a homelab MinIO now runs again. This entry used to say the tenant’s own MinIO was still running alongside it, so there were two, and that we should “expect this to be one once the cutover finishes”. It did finish: app-minio was stopped on 2026-07-31 and there is exactly one MinIO running — verified on the host 2026-08-06 07:45 UTC.

What runs

Sixteen homelab containers, deployed as six independent units, plus a one-shot openbao-init that runs and exits. Verified on the host 2026-08-09 03:53:04 UTC: 16 homelab containers, 7 tenant containers, 23 running in total, 0 unhealthy. The counts moved twice since this section was written and it recorded neither. It said thirteen was the long-standing baseline and that a fourteenth — the homelab MinIO — joined mid-cutover on 2026-07-31, with 8 tenant containers alongside. Two changes have landed since: alertmanager and blackbox-exporter were deployed, taking the homelab from 14 to 16, and the tenant’s app-minio was stopped at the end of the MinIO cutover, taking it from 8 to 7. The total is 23, not 22. Order matters: db before auth, because Keycloak will not start without its database — the deploy script refuses the auth deploy if it cannot query Postgres first. The edge unit owns all three Docker networks; everything else attaches to them as external. On a fresh host, edge deploys first.

Surfaces

Every administrative dashboard except Keycloak is reachable only over Tailscale (100.64.0.0/10), enforced by a Traefik IP-allowlist middleware on top of the host firewall. Ports 80 and 443 are the only ones carrying traffic from the internet. The firewall’s public zone also permits cockpit and dhcpv6-client, but nothing listens on the cockpit port — its socket is inactive — so the effective public surface is HTTP and HTTPS. SSH is not in the public zone at all. Keycloak is the exception, and deliberately so: an identity provider that users sign in to has to be reachable by those users. It is routed on the public entrypoint with an HTTP-01 certificate and no IP allowlist, which means its admin console at https://auth.hill90.com/admin/ is publicly reachable too, protected by credentials alone.

The stack

  • Host — AlmaLinux 10 on a Hostinger VPS
  • Runtime — Docker Engine and Docker Compose
  • Edge — Traefik v2.11 with Let’s Encrypt, HTTP-01 for public hostnames and DNS-01 for Tailscale-only ones
  • Observability — Prometheus, Grafana, Loki, Tempo, Alertmanager and blackbox-exporter, plus Promtail, node-exporter and cAdvisor. cAdvisor is running, but it emits zero Docker named-container series on this host; it is not evidence of per-container monitoring.
  • Identity — Keycloak 26.4.0, serving the platform realm
  • Database — PostgreSQL 16, the platform store behind Keycloak
  • Secrets — OpenBao as the intended runtime store, an age-encrypted file store for bootstrap and disaster recovery
  • Admin access — Tailscale VPN, SSH by key only
  • Provisioning — Ansible playbooks driven by the Hostinger and Tailscale APIs
  • CI/CD — GitHub Actions
  • DNS — Cloudflare, authoritative for hill90.com since 2026-07-27. Hostinger remains the VPS host and the mail provider, so the MX, SPF, DKIM, autoconfig and DMARC records still point there

No application code

The infrastructure repository runs no application services. What code it holds is CI checks and deploy scripting. DNS-01 challenges need no service of their own: DNS is on Cloudflare and Traefik solves them with lego’s built-in cloudflare provider — two lines of configuration and a scoped API token. See Certificates.

Where to go next

Architecture

Network topology, isolation boundaries, and what each service is responsible for.

Local development

Run the whole stack on a Mac with one command. No secrets, no VPS.

Operations

Deploy, rebuild, back up, recover.

Secrets

The vault model, and an honest account of what the vault holds today.