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 aplatform 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.
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-miniowas 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-shotopenbao-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
platformrealm - 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.comsince 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-incloudflare 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.