TPTito Pinardo Gutiérrez
← Back to the rack
Español
1U · HOMEPAGEHomepage · 29 services · generated In production

Slot 10 of 25 in the rack · In production

1U unit · Start page

Homepage · the door to everything

A single source of truth

A page with every homelab service, grouped by category with its icon. It isn't edited by hand: Ansible generates it from the same service list used to create Traefik's routes, so what's published and what shows on the dashboard can't drift apart.

Why generated
A hand-edited dashboard ages: services that no longer exist, broken links. If it comes from the service list, it's always current.
What it shows
29 services in eight categories and three tabs: control, library and community. No decorative health indicators: that's what monitoring is for.
Where it's seen
Only from home or the VPN, behind the internal door. It was taken off the internet: its descriptions say too much about the network.
  • 29services on the dashboard
  • 8categories, in three tabs
  • 0files edited by hand on the server
  • 5templates that generate everything

Its code, in the open Public version of the repo: no IPs or secrets.

Inside the dashboard

Nobody edits this dashboard by hand: it's generated. Pick a flow to see where each card comes from.

Hermesvisiting the unit

Click a part or pick a flow. If you came from a skill, the parts where I use it are already lit.

      I add a service
      1. Infrastructure repository — I add an entry to the service list, with its category and icon.
      2. Service list — The list is the same for the whole homelab.
      3. Traefik — Traefik creates its route, if it's public.
      4. Five templates — The services template puts it in its category.
      5. The three tabs — And it shows up in its dashboard tab, without touching anything else.
      I change the look
      1. Look — I change title, order or descriptions in the role's variables.
      2. Background image — And the background image, if needed.
      3. Syntax check — Syntax check.
      4. Dry run with diff — Dry run: what would change.
      5. Configuration tag — Only the configuration part is applied.
      6. Five templates — The five templates are deployed.
      7. Dated backups — Each replaced file leaves its dated copy.
      8. Regenerate the page — The static page is regenerated, with retries.
      9. Check in the browser — I check it in the browser, on desktop and mobile.
      10. Second pass with no changes — And a second pass changes nothing.
      I open the dashboard
      1. My laptop — I open the dashboard from home.
      2. Internal door — The internal door asks for two-factor.
      3. Allowed hosts — Homepage checks the requested name is an allowed one.
      4. systemd service — The service answers.
      5. The three tabs — With the three tabs.
      6. My laptop — And from there, to any service.
      I upgrade the version
      1. Infrastructure repository — I change the version in the repo.
      2. Build — Ansible builds the new one in its own folder.
      3. systemd service — It switches the link to the active one and restarts the service.
      4. Allowed hosts — With its list of allowed names.
      5. The three tabs — And the dashboard stays the same, on the new version. Rolling back is switching the link.
      1. Infrastructure repository

        The service list and the dashboard's look, versioned.

        • Git
      2. My laptop

        I open it when I start working.

      3. Internal door

        Only from home or the VPN, with two-factor.

      4. Traefik

        Uses the same list to create its public routes.

        • Traefik
      5. Service list

        Each service with its domain, category and icon. The same list for Traefik and the dashboard.

        • Ansible
      6. Local-only services

        Those that never go through the internet (monitoring, security, DNS…) on a separate list, so the dashboard has them without publishing them.

        • Ansible
      7. Look

        Title, order, names, descriptions and hidden services, in the role's variables.

        • Ansible
      8. Background image

        An image of my own of mountains at dusk, with translucent cards on top.

      9. Five templates

        Settings, services, widgets, bookmarks and styles. The services one groups by category on its own: a new category shows up without touching anything.

        • Ansible
      10. Configuration tag

        Changing the look doesn't reinstall packages or recompile: it only deploys the templates.

        • Ansible
      11. Dated backups

        Every replaced file leaves its copy alongside, to roll back in seconds.

        • Ansible
      12. Regenerate the page

        Restarting wasn't enough: the static page kept the old title. The role calls the regeneration with retries.

        • Ansible
      13. Build

        Homepage doesn't ship a binary: it's built with Node and Next.js, in a folder per version with a link to the active one.

        • JavaScript / TypeScript
        • Next.js
      14. systemd service

        Node.js with its systemd unit, no Docker.

        • Linux (Debian, Alpine, Arch)
      15. Allowed hosts

        Homepage rejects any request whose name isn't on its list. Its own protection, which has to be configured.

        • Linux / SSH hardening
      16. The three tabs

        Control, library and community, with shortcuts on all of them.

      17. Syntax check

        First of all, the playbook has to be valid.

        • Ansible
      18. Dry run with diff

        See what would change without changing it.

        • Ansible
      19. Check in the browser

        On desktop and mobile: cards, background, title, no overflow or JavaScript errors.

      20. Second pass with no changes

        Applying again mustn't change anything. If it does, something isn't idempotent.

        • Ansible

      What I use from my skill tree

      Pick a skill: the parts where I use it light up on the map and I tell you what for.

      Terraform
      Creates the dashboard's container.
      Ansible
      Generates the whole dashboard from the service list, with a tag for look changes, backups and regeneration.
      LXC
      Runs in its own container.
      Git
      The service list and the dashboard's look are versioned.
      Linux (Debian, Alpine, Arch)
      Node.js with systemd, versions in separate folders and a link to the active one.
      Traefik
      Shares the service list with the dashboard.
      Linux / SSH hardening
      Allowed-names list and access only through the internal door.
      JavaScript / TypeScript
      Homepage is a Next.js app built with Node.
      Next.js
      The static page that has to be regenerated after a configuration change.

      Incident log

      Real failures. Read the symptom, pick the cause you think it was and see how it was fixed.

      1. #1

        «markAsUncloneable is not a function»

        15-09-2026 · deploy

        The chosen Homepage version wouldn't start: a JavaScript error that said nothing about the dashboard.

        What do you think was going on?

        Show the fix
        Cause
        That Homepage version uses a Next.js that requires Node 22, and the distribution only ships Node 20. The error came from inside Node, not Homepage.
        Fix
        Install Node 22 from the official NodeSource repo and make sure the task replaces the previous version, already installed by an earlier attempt.
        Lesson
        Read whose error it is before chasing it: this one came from the engine, not the app. And an earlier attempt leaves leftovers the second run has to clean up.
      2. #2

        «Host validation failed» for everything

        15-09-2026 · start-up

        It now started, but answered 400 «Host validation failed» to every request, from anywhere.

        What do you think was going on?

        Show the fix
        Cause
        It's Homepage's own protection: if the name it's called by isn't on its list, it rejects the request.
        Fix
        Declare the allowed names in the systemd service, also generated by Ansible.
        Lesson
        Default protections are welcome, but you have to know they exist. The official docs came before the forum.
      3. #3

        The old title wouldn't go

        26-09-2026 · look

        I changed title, language and order. Ansible applied everything and restarted the service, but the browser still showed the old title.

        What do you think was going on?

        Show the fix
        Cause
        Next.js generates part of the HTML once and serves it ready-made. Restarting the service doesn't generate it again.
        Fix
        The role calls the regeneration route after restarting, with retries until it answers.
        Lesson
        «Applied without errors» isn't «looks right». You have to look at the result in the browser.

      How it was built

      1. 15-09-2026

        From Fenrus to Homepage

        • Homepage in its container, built and running under systemd.
        • A service list shared with Traefik, with category and icon.
      2. 26-09-2026

        Look and off the internet

        • My own design, three tabs and a new background.
        • Off the internet: now only behind the internal door.

      Decisions (and why)

      • Which dashboard

        Homepage

        Declarative YAML configuration: it fits generating everything from the service list.

      • Health indicators

        No decorative ones

        A green dot that measures nothing gives false comfort. Health lives in monitoring, one click away.

      What I learned

      • A single list for everything stops two places telling different stories.
      • Check in the browser what Ansible says it applied.

      What's next

      • Generate the internal door's configuration from the same list too.

      Shall we talk?

      I can walk through any of these decisions in detail in an interview.

      Download CV Get in touch ← Back to the rack

      Rebuilt from my documentation: no addresses, ports or internal names.