A self-hosted, Docker-based platform for sharing my home apps — streaming, photos, chat, and more — with family and friends.
I built this to give the people I share with a polished experience that hides all the moving parts. No VPN to install, no pile of weird links to remember, no separate password for every app. They visit a friendly link on my domain, sign in once, and everything just works in a browser.
- Installing a VPN or any client software
- Knowing or caring which components are involved
- Tracking a dozen links to complicated apps
- A different login for every service
As an engineer who loves my friends but has very little time to spare, getting there meant building on a few non-negotiables:
- Enterprise identity (SSO) and access management
- Familiar git-based DevOps patterns
- Containerized runtime and deployment
- A support and login portal as the front door
Two ideas carry the whole platform:
- A Pangolin edge proxy authenticates and routes every request. Pangolin runs on a small VPS at the internet edge. Each stack at home runs a Newt tunnel client that dials out to Pangolin over WireGuard — so nothing at home needs an open inbound port. Pangolin terminates TLS, optionally checks the user's login, and forwards to the right container.
- Keycloak is the single identity provider. One account unlocks everything, with Google and Apple social login available.
Internet
│ HTTPS (automatic TLS)
▼
┌───────────────────────────────────┐
│ Pangolin · edge VPS │ Traefik (TLS) + Gerbil (WireGuard)
│ authenticates + routes requests │
└───────────────┬───────────────────┘
│ over a WireGuard tunnel that
│ Newt dials OUT to the edge —
│ no inbound ports at home
▼
┌───────────────────────────────────┐
│ Newt · home host │ finds containers by Docker label
└───────────────┬───────────────────┘
▼
Jellyfin · Immich · Matrix · Kanboard · Penpot · …
each an independent Docker Compose project
No client software is required. For the user it's just HTTPS in a browser.
Identity is managed by Keycloak. Services with native OIDC/SAML (Immich, Penpot, Pangolin itself) use Keycloak directly as their identity provider. Apps with no auth of their own get Pangolin's login put in front of them — backed by the same Keycloak, so it's still one account.
Jellyfin is the awkward one: it only federates auth through an LDAP directory. So I run OpenLDAP and configure Keycloak to federate with it, making OpenLDAP the password backend. Keycloak syncs its users and groups into OpenLDAP's schema, so when Jellyfin asks LDAP to authenticate someone, it also sees their Keycloak group membership:
- member of
streaming→ gets a Jellyfin account - member of
streamingandadmin→ becomes a Jellyfin admin
The result: users have one username and password for everything — whether the auth is proxied by Pangolin, handled natively by Keycloak, or checked by LDAP for Jellyfin — and they never need to know Pangolin or OpenLDAP exist. They only ever see Keycloak.
A Hugo static site (core → homepage) is the front door: support guides written in Markdown, login and help links, and — once a user is signed in through Pangolin's Keycloak-backed auth — an apps grid linking to everything they can reach, grouped by category (streaming, photos, AI, …). It's built in CI, published to a container registry, and pulled on deploy.
Every top-level directory is an independent Docker Compose project, stitched together by the root Makefile. They don't depend on one another at the compose level — they couple only through a few shared Docker networks (proxy, core, monitoring, grow-mqtt) and the shared Keycloak/LDAP identity. Grouped the way the project's architecture sees them:
Edge & identity — the foundation
| Stack | What it is |
|---|---|
pangolin/ |
The Pangolin reverse-proxy / control plane (Traefik + Gerbil WireGuard), deployed to the edge VPS via its own pangolin-edge Docker context |
core/ |
The shared backbone: Newt tunnel client, Keycloak (auth), OpenLDAP (ldap), MariaDB (db), the Hugo homepage, the WUD update notifier, and the MkDocs agent-kb docs site |
Media & streaming — the reason all the rest exists
| Stack | What it is |
|---|---|
media/ |
Jellyfin (streaming + GPU transcode → stream.${DOMAIN}), Sonarr, Radarr, NZBGet, and Jellyseerr (requests) |
immich/ |
Self-hosted photos & video at photos.${DOMAIN} — server, ML, PostgreSQL, Valkey, with Intel Arc acceleration |
channels/ |
Channels DVR (host networking + GPU transcode); config and recordings live on the Docker host |
iptv/ |
IPTVBoss — the noVNC management UI plus an XtremeCodes guide/playlist server, both behind SSO |
Communication & messaging
| Stack | What it is |
|---|---|
matrix/ |
Tuwunel homeserver (matrix.${DOMAIN}) + Element Web (chat.${DOMAIN}) for family/friends chat |
mqtt/ |
Single per-site Mosquitto broker — the local MQTT bus for the grow-control system (no central aggregator; remote access is at the app layer via Pangolin) |
grow/ |
LAN-local grow-app HMI on port 3080, per-site InfluxDB 2.7 time-series store, and a history-recorder sidecar that writes MQTT readings to InfluxDB |
Collaboration & knowledge
| Stack | What it is |
|---|---|
kanban/ |
Shared Kanboard tracker at kanban.ai.dephekt.net (LAN fallback http://containers.home.arpa:8097) |
penpot/ |
Self-hosted Penpot design workspace at design.ai.${DOMAIN} (Keycloak OIDC directly; Pangolin only does ingress/TLS) |
cci/ |
The CCI Black Book MCP retrieval service at cci.ai.${DOMAIN}/mcp — bearer auth, bounded cited evidence packs (the stack's one first-party application) |
Operations
| Stack | What it is |
|---|---|
monitoring/ |
A notification router (apprise-api) plus custom probes and a Docker-health watcher, backed by two off-box SaaS witnesses (UptimeRobot, Healthchecks.io) so that a dead watcher gets noticed too. Public status page: status.dephekt.net |
Everything goes through the root Makefile — never call docker compose directly. A bare invocation skips the env exports the Makefile sets, which would render ${DOMAIN} empty in the Pangolin labels and break routing. The Makefile generates per-stack and per-service targets and runs Compose against remote SSH Docker contexts (the media-server host for most stacks, pangolin-edge for the edge VPS). Secrets are pulled from 1Password at deploy time and surfaced to containers as Docker secrets.
make inject-secrets && make sync-secrets && make up # full deploy
make <stack>-up # one stack, e.g. core-up
make <svc>-restart # one service, e.g. auth-restart
make # list every generated targetThe full operator reference — shared networks, the secrets workflow, Blueprint/Newt internals, and linting — lives in AGENTS.md.
You don't edit the proxy to expose something new — you label the container and Newt autodiscovers it:
labels:
- "pangolin.proxy-resources.app.name=My App"
- "pangolin.proxy-resources.app.protocol=http"
- "pangolin.proxy-resources.app.full-domain=app.${DOMAIN}"
- "pangolin.proxy-resources.app.targets[0].enabled=true"
- "pangolin.proxy-resources.app.targets[0].port=8080"
# optional: put Keycloak SSO in front
- "pangolin.proxy-resources.app.auth.sso-enabled=true"
- "pangolin.proxy-resources.app.auth.sso-roles[0]=Member"Pangolin auto-provisions the branded domain and its TLS certificate. The exhaustive label schema is documented in AGENTS.md.
TLS termination and certificate provisioning/renewal are handled entirely by Pangolin (Traefik + Let's Encrypt via Cloudflare DNS-01) at the edge — there's nothing to manage per app.
Dashboard Icons — over 1,800 curated icons for services, applications, and tools, designed for dashboards and app directories. They power the homepage service icons. Browse the collection at dashboardicons.com.