Project

Homelab Stacks (Public)

Repository: github.com/sgoley/homelab-stacks-public

This document is a reader-focused overview of the stack in a homelab stack definition repository (non-public). It explains how traffic flows across LAN and remote (Tailnet) access, how services are connected, and what each container does — without publishing secrets, environment values, or explicit port mappings.

---

1) What this stack is

This is a single-host Docker Compose homelab that runs:

Design goals:

  1. One Compose file as source of truth
  2. Reverse-proxy-first access pattern through Caddy
  3. LAN + Tailnet reachability with the same internal DNS names
  4. Minimal direct service exposure
  5. Host-attached storage shared over SMB for LAN/Tailnet file access

---

2) Topology at a glance

                                ┌───────────────────────────────┐
                                │         Tailnet (WAN)         │
                                │  Laptops / phones / remote PC │
                                └───────────────┬───────────────┘
                                                │
                                      (encrypted mesh VPN)
                                                │
┌───────────────────────────────┐               │              ┌───────────────────────────────┐
│            LAN                │               │              │      External Tailnet Node    │
│   Home devices + desktops     │               │              │       (Tailscale Aperture)    │
└───────────────┬───────────────┘               │              └───────────────┬───────────────┘
                │                               │                              │
        (local DNS rewrite)             (split DNS + subnet route)             │
                │                               │                              │
                └───────────────┬───────────────┴───────────────┬──────────────┘
                                │                               │
                         ┌──────▼───────────────────────────────▼──────┐
                         │           Homelab Host (Docker)             │
                         │                                              │
                         │  Caddy (primary ingress)                    │
                         │  + app containers on shared Docker network  │
                         │                                              │
                         │  Tailnet-native sidecars:                   │
                         │   - Golink                                  │
                         │   - Aperture→Langfuse relay                 │
                         └──────────────────────────────────────────────┘

---

3) Access model (LAN + remote)

LAN path

LAN Client
   -> local DNS wildcard for *.homelab.internal
   -> Caddy ingress on homelab host
   -> target container service

Remote path (via Tailscale)

Remote Tailnet Client
   -> Tailnet Split DNS for homelab.internal
   -> subnet route to home LAN
   -> Caddy ingress on homelab host
   -> target container service

SMB file-share path (via Samba)

LAN or Tailnet Client (Finder/Cyberduck)
   -> SMB to homelab host LAN IP
   -> Samba container (host networking)
   -> /mnt/data/{documents,shares,backups}

Tailnet-native path (does not require Caddy)

Tailnet Client / Tailnet Service
   -> MagicDNS name of tsnet service
   -> Golink OR Aperture→Langfuse relay

---

4) Service routing map

                   ┌──────────────────────┐
                   │        Caddy         │
                   │   reverse proxy +    │
                   │  policy enforcement  │
                   └───────┬──────────────┘
                           │
       ┌───────────────────┼───────────────────────────────────────────────┐
       │                   │                                               │
   ha.homelab.internal  airflow.homelab.internal                    n8n.homelab.internal
       │                   │                                               │
  Home Assistant       Airflow API server                         n8n editor/admin
                                                                  (Tailnet-auth gated)
       │
       ├── n8n-webhook.homelab.internal -> n8n webhook handlers (path-restricted)
       ├── portainer.homelab.internal   -> Portainer
       ├── sftpgo.homelab.internal      -> SFTPGo web/admin surface (network-restricted)
       ├── uptime.homelab.internal      -> Uptime Kuma
       ├── subnotes.homelab.internal    -> Subnotes frontend
       ├── subnotes-api.homelab.internal-> Subnotes backend
       ├── subnotes-pb.homelab.internal -> Subnotes PocketBase
       ├── opencode.homelab.internal    -> OpenCode (network-restricted)
       ├── langfuse.homelab.internal    -> Langfuse web (network-restricted)
       └── ollama.homelab.internal      -> Ollama (optional profile)

---

5) Full container inventory (current Compose)

Scope: all containers defined in docker-compose.yml in this repo.

| Container | Role | Exposure pattern | |---|---|---| | homeassistant | Home automation core | Via Caddy hostname | | airflow-postgres | Airflow metadata DB | Internal only | | airflow-init | Airflow bootstrap/migrations | Internal one-shot job | | airflow-apiserver | Airflow API/UI backend | Via Caddy hostname | | airflow-scheduler | Airflow scheduling | Internal only | | airflow-dag-processor | Airflow DAG parsing | Internal only | | airflow-triggerer | Airflow async trigger service | Internal only | | n8n-postgres | n8n DB | Internal only | | n8n | Automation engine | Via Caddy hostname(s) | | portainer | Docker management UI | Via Caddy hostname | | sftpgo | Managed SFTP service for workflow file exchange | SFTP endpoint + restricted Caddy hostname | | uptime-kuma | Monitoring UI/check engine | Via Caddy hostname | | caddy | Reverse proxy ingress | Primary entrypoint | | watchtower | Automated image update runner | Internal daemon | | samba | SMB/CIFS file sharing for host storage | LAN IP over SMB (reachable remotely via Tailnet subnet route) | | opencode | Headless coding/web assistant service | Via restricted Caddy route | | langfuse-postgres | Langfuse DB | Internal only | | langfuse-clickhouse | Langfuse analytics store | Internal only | | langfuse-redis | Langfuse cache/queue backend | Internal only | | langfuse-minio | Langfuse object storage backend | Internal only | | langfuse-worker | Langfuse async workers | Internal only | | langfuse-web | Langfuse web/API surface | Via restricted Caddy route | | aperture-langfuse-relay | Receives Aperture hooks, forwards ingestion to Langfuse | Tailnet-native endpoint | | golink | Private tailnet shortlinks | Tailnet-native endpoint | | ollama (optional) | Local model serving | Via Caddy hostname (when enabled) | | subnotes-pocketbase | Subnotes data backend | Via Caddy hostname | | subnotes-backend | Subnotes API/transcript processing | Via Caddy hostname | | subnotes-frontend | Subnotes UI | Via Caddy hostname |

---

6) Internal service mesh (Docker network view)

                    ┌───────────────────────────────────────────────┐
                    │          Shared Docker network                │
                    │                  "homelab"                    │
                    └───────────────────────────────────────────────┘

  [Ingress/Edge]         [Automation]             [AI / Observability]         [Content Apps]
  ───────────────        ────────────             ────────────────────         ──────────────
  Caddy                 Airflow API              Langfuse Web                  Subnotes Frontend
  Watchtower            Airflow Scheduler        Langfuse Worker               Subnotes Backend
  Portainer             Airflow DAG Proc         Langfuse Postgres             Subnotes PocketBase
  SFTPGo
  Uptime Kuma           Airflow Triggerer        Langfuse ClickHouse
  Home Assistant*       Airflow Postgres         Langfuse Redis
  n8n + n8n-postgres                            Langfuse MinIO
                                                OpenCode
                                                Ollama (optional)
                                                Aperture→Langfuse relay

  *Home Assistant uses host networking, but still participates in the overall ingress model.

---

7) Tailnet routes and roles

                           Tailnet
                              │
          ┌───────────────────┼───────────────────┐
          │                   │                   │
   Host subnet-router     Caddy tsnet node     Golink tsnet node
   (advertises home LAN)  (auth-aware ingress) (private shortlinks)
                              │
                              └───────────────┐
                                              │
                                  Aperture→Langfuse relay tsnet node
                                  (webhook receiver for Aperture)

What this enables:

  1. Remote clients can resolve and reach internal app hostnames over Tailnet.
  2. Certain services can be reached as direct Tailnet nodes (Golink, relay) without passing through Caddy.
  3. Sensitive app surfaces can require tailnet identity policy before access.

---

8) Golink shortcuts currently enabled

These private shortlinks are served by the golink Tailnet-native service and resolve to internal app URLs.

| Short | Destination | |---|---| | airflow | https://airflow.homelab.internal | | ha | https://ha.homelab.internal | | langfuse | https://langfuse.homelab.internal | | lf | https://langfuse.homelab.internal | | n8n | https://n8n.homelab.internal | | ollama | https://ollama.homelab.internal | | port | https://portainer.homelab.internal | | portainer | https://portainer.homelab.internal | | sftp | https://sftpgo.homelab.internal/ | | sftpgo | https://sftpgo.homelab.internal/ | | subnotes | https://subnotes.homelab.internal/ | | uptime | https://uptime.homelab.internal |

---

9) AI observability pipeline (Aperture -> relay -> Langfuse)

Model client(s) -> Tailscale Aperture -> webhook event
                                      -> Aperture→Langfuse relay (tailnet endpoint)
                                      -> Langfuse ingestion API
                                      -> Langfuse storage backends
                                         (Postgres + ClickHouse + Redis + MinIO)
                                      -> Langfuse UI/API

Why this split exists:

---

10) Security posture (public-safe summary)

---

11) Operational behavior

---

12) Migration in progress

---

13) Reader takeaway

This homelab is built as a private-first, reverse-proxied, Tailnet-enabled application platform: one host, many services, one ingress model, and a clear split between internal-only backends and user-facing entrypoints.