Skip to content
mickeyPublic

About

The start page that builds itself from your Tailscale Services

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

tailhome

The start page that builds itself from your Tailscale Services.
Open http://home/ to browse your Tailscale Services, with icons and live health checks.

Release CI License: MIT

Quick start · How it works · Customize tiles · Containers · Troubleshooting

tailhome in a browser at home/, showing Services grouped into Home Lab, Media, Monitoring and Other
  • Automatic Service discovery. tailhome lists reachable Services without API credentials and loads icons from dashboard-icons. New Services appear on the next refresh.
  • One small binary, no host Tailscale needed. tailhome joins your tailnet through tsnet. The image is about 12 MB.
  • Health checks inside the tailnet. A green dot means tailhome can reach the Service.

tailhome lists Tailscale Services, not machines. No Services yet? Define one in the admin console, or publish containers with DockTail.

tailhome is a launcher, not a dashboard: no widgets, no feeds. For those, use Homepage or Glance.


Quick start

Prerequisites: at least one Tailscale Service, Tailscale 1.86 or later on your browsing devices, and MagicDNS plus HTTPS Certificates enabled in the DNS settings.

Create compose.yaml, then start tailhome:

services:
  tailhome:
    image: ghcr.io/mickey/tailhome:latest
    env_file: [{ path: .env, required: false }] # optional, see "Groups and descriptions"
    environment:
      TZ: Etc/UTC # your time zone, for the "updated" time
      TAILHOME_OVERRIDES: /config/overrides.yaml
    volumes:
      - tailhome-data:/data
      - ./config:/config:ro # put overrides.yaml here
    restart: unless-stopped

volumes:
  tailhome-data:
docker compose up -d
docker compose logs -f

Open the login link in the logs to add tailhome to your tailnet. When the logs show serving https://home.<tailnet>.ts.net, open http://home/ from a tailnet device. Keep the trailing slash so your browser doesn't search for "home".

tailhome joins under your account as a device named home:

  • Disable key expiry on the Machines page. Untagged nodes expire after 180 days by default; the page shows the date until you disable it.
  • Check the address in the logs. If a device or Service already uses home, Tailscale assigns another name, such as home-1.

To skip browser login, set TS_AUTHKEY to an auth key. A tagged key also avoids key expiry, but its tag needs a tagOwners entry.

Without Docker: binary, Go or systemd

Download a binary from Releases, or install it with Go 1.27.3 or later:

go install github.com/mickey/tailhome@latest
tailhome

For systemd, create .env (empty without credentials), then run as root:

install -m 755 tailhome /usr/local/bin/
install -d /etc/tailhome
install -m 600 .env /etc/tailhome/env
install -m 644 contrib/tailhome.service /etc/systemd/system/
systemctl enable --now tailhome

The unit stores state in /var/lib/tailhome and reads /etc/tailhome/overrides.yaml. Use journalctl -u tailhome to find the login link.

Note

Keep the state directory (/data in Docker). Deleting it makes tailhome join as a new node; the old node remains in the admin console.


How it works

  • Tiles and "Other". A Service becomes a tile if it advertises port 443 or 80, has a url or web: true override, or answers HTTP on its first TCP port. Without a usable port (such as do-not-validate), tailhome tries HTTPS on 443, then HTTP on 80. Services needing HTTP detection stay under "Other" until they answer. Other Services, such as databases, stay there; click one to copy its hostname. Use web: false to keep a Service under "Other".
  • Health.
    • Green: an HTTP response below 500, including redirects; a successful TCP connection for "Other" and non-HTTP links (ssh://, vnc://).
    • Red: the check failed. Hover over the dot for the reason. A tile that goes down stays in its group.
    • Grey: nothing to check, such as a Service with no TCP port.
  • Refresh. tailhome reloads and checks Services every 5 minutes. Select refresh to check now, at most once every 10 seconds. If discovery or overrides fail, the page keeps the last list and shows the error.
  • Notices. Without credentials, the page warns when the node's key will expire or when the node got another name, such as home-1. It rechecks both on every refresh.
  • JSON. /api/services.json returns the same data as the page, notices included.

Customize tiles

Default tile fields:

Field Source
Label The Service's display name, or its name in title case
Description The Service's description, with an OAuth client
Group The Service's first tag, with an OAuth client: tag:home-lab becomes "Home Lab". Other Services use "Services".
Icon The dashboard-icons entry named after the Service, or the label's first letter

To change them, create config/overrides.yaml next to compose.yaml. All fields are optional; overrides.example.yaml lists them:

jellyfin:
  group: Media
  order: -1                 # lower sorts first; unset counts as 0, ties sort by label
grafana:
  label: Grafana
  icon: grafana             # a dashboard-icons name, or a full image URL
  url: https://grafana.example.ts.net/d/main
nas:
  url: vnc://nas.example.ts.net:5900   # also ssh, rdp, smb, ftp, sftp, telnet
postgres:
  hidden: true

Make the file readable by everyone (chmod 644). Without Docker, tailhome reads overrides.yaml from its working directory; set TAILHOME_OVERRIDES to use another path.

tailhome rereads overrides on every refresh, so edits need no restart. Keys are Service names, with or without svc:. Unknown fields fail the refresh: correct them and refresh again. tailhome ignores unmatched names and logs a warning; check their spelling.


Containers

DockTail and TSDProxy v3 (services: true) publish Docker containers as Services, so their tiles appear on their own. TSDProxy v2, tsbridge, and ScaleTail create devices instead; tailhome doesn't list them.

Known quirks: groups, labels, paths, stopped containers
  • Groups. With an OAuth client, DockTail's default tag:container and TSDProxy's shared tags put tiles in one group. Set docktail.tags per container (each tag needs tagOwners and autoApprovers entries), or use group overrides.
  • Labels. These tools omit display names and may clear names you set in the admin console. Use label overrides.
  • Descriptions. With an OAuth client, docktail.service.description appears on the tile.
  • Paths. Tiles open / because the API omits docktail.service.path. Set url overrides.
  • Stopped containers. DockTail keeps their Services, leaving red tiles. Set DELETE_UNUSED_SERVICES=true in DockTail or hidden: true in overrides. TSDProxy deletes stopped containers' Services.

Groups and descriptions

Without credentials (default) With an OAuth client
Setup Login link or auth key ACL entries, a Service definition and an OAuth client; needs admin access
Services listed Those tailhome can reach All Services in the tailnet
Groups One, unless you set group overrides From each Service's first tag
Descriptions From overrides only From the admin console
Page address The node's name, home The Service svc:home
Set up an OAuth client
  1. Allow tag:tailhome in your access controls:

    "tagOwners": {
      "tag:tailhome": ["autogroup:admin"]
    },
    "autoApprovers": {
      "services": { "svc:home": ["tag:tailhome"] }
    }

    Tailscale's default policy allows everyone to reach everything. With a stricter policy, also add:

    "grants": [
      // Who can open the start page
      { "src": ["autogroup:member"], "dst": ["svc:home"], "ip": ["*"] },
      // What tailhome may health-check
      { "src": ["tag:tailhome"], "dst": ["*"], "ip": ["*"] }
    ]
  2. Define the home Service: on the Services page, select Advertise → Define a Service, name it home, enter ports 443,80, leave tags empty, and select Define Service. First rename or remove any device named home, including a tailhome node that joined without credentials.

  3. Create an OAuth client: in Trust credentials, add a credential with custom scopes General → Services → Read to list Services and Keys → Auth Keys → Write with tag:tailhome to join the tailnet. Copy the client ID and secret; Tailscale shows the secret only once.

  4. Save the credentials in .env next to compose.yaml (see .env.example), then restart. If tailhome already joined without credentials, reset its state first so it joins again with its tag: docker compose down -v && docker compose up -d. Overrides in ./config are kept.

    TAILHOME_CLIENT_ID='your-client-id'
    TAILHOME_CLIENT_SECRET='tskey-client-your-secret'

Without Docker, set the same two variables before starting tailhome.


Configuration

Variable Default Description
TAILHOME_CLIENT_ID unset OAuth client ID. Set both or neither.
TAILHOME_CLIENT_SECRET unset OAuth client secret
TAILHOME_STATE_DIR /data in Docker, else tsnet-tailhome in your user config dir Where tailhome keeps its tailnet identity
TAILHOME_OVERRIDES /config/overrides.yaml with the compose file above, /data/overrides.yaml in the bare image, else overrides.yaml Path to the overrides file
TAILHOME_REFRESH 5m How often to reload and recheck Services
TAILHOME_SERVICE svc:home Service that serves the page (OAuth only)
TAILHOME_TAG tag:tailhome Node tag (OAuth only)
TAILHOME_HOSTNAME home, or tailhome with OAuth Node name in the tailnet
TAILHOME_TAILNET the OAuth client's tailnet Tailnet to list (OAuth only)
TS_AUTHKEY unset Auth key instead of browser login or an OAuth-minted key. With OAuth, it must allow TAILHOME_TAG (default tag:tailhome).
TS_NO_LOGS_NO_SUPPORT unset Set to true to stop uploading tsnet diagnostic logs to Tailscale

Troubleshooting

Symptom Cause and next step
The page lists no Services It shows what to check: tailhome lists Services, not machines, and the node must be allowed to reach them. If the tailnet doesn't yet share Services with nodes, use an OAuth client.
home/ doesn't load Without OAuth, check the logged address for a name such as home-1. With OAuth, define svc:home (step 2), add autoApprovers, or approve its host on the Services page. Logs warn if the Service is missing.
home/ doesn't resolve on one device Enable MagicDNS and use Tailscale 1.86 or later. On Linux before 1.94, run sudo tailscale set --accept-routes.
https://home/ won't connect HTTPS requires the full name. Open http://home/ to redirect there.
https://home.<tailnet>.ts.net/ won't connect Enable HTTPS Certificates in the DNS settings.
The first HTTPS load is slow or times out Tailscale may still be issuing the certificate. Retry in a minute.
tailhome exits with this node joined without a tag It joined without credentials earlier. Reset its state (step 4) so it joins again with its tag.
tailhome exits with joining the tailnet: creating an auth key for tag:tailhome: … Give the OAuth client Auth Keys: Write for tag:tailhome and add the tag to tagOwners.
"Refresh failed: …" mentions oauth2 or 403 Give the OAuth client Services: Read.
A dot is red but the Service opens Check whether policy lets tailhome reach the Service. With OAuth, add the grants from step 1.
An override has no effect Check the logs for a name warning, and that the file is readable (chmod 644).

Security and privacy

  • Service names. Anyone who can open tailhome sees all listed Service names on the page and in /api/services.json, even Services they can't open. Restrict access to tailhome if needed.
  • Icons. Browsers request icons from cdn.jsdelivr.net, revealing Service names to jsDelivr. Use icon overrides with your own URLs to avoid this.
  • Logs. tailhome uploads diagnostic logs to Tailscale, like other Tailscale nodes. Set TS_NO_LOGS_NO_SUPPORT=true to stop uploads.

Report vulnerabilities as described in SECURITY.md. To contribute, see CONTRIBUTING.md. tailhome is released under the MIT license.

About

The start page that builds itself from your Tailscale Services

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages