{"id":51457020,"url":"https://github.com/jpbaking/homelab-tailscale","last_synced_at":"2026-07-06T01:02:55.861Z","repository":{"id":367349413,"uuid":"1280386266","full_name":"jpbaking/homelab-tailscale","owner":"jpbaking","description":"Unprivileged Tailscale exit node + subnet router as a single Docker Compose service (userspace/netstack mode, no TUN/NET_ADMIN).","archived":false,"fork":false,"pushed_at":"2026-06-25T15:16:49.000Z","size":9,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-25T17:06:05.341Z","etag":null,"topics":["docker","docker-compose","exit-node","homelab","mesh-vpn","netstack","self-hosted","subnet-router","tailscale","userspace-networking","vpn","wireguard","wireguard-vpn"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"0bsd","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jpbaking.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-25T14:29:23.000Z","updated_at":"2026-06-25T15:10:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jpbaking/homelab-tailscale","commit_stats":null,"previous_names":["jpbaking/homelab-tailscale"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jpbaking/homelab-tailscale","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpbaking%2Fhomelab-tailscale","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpbaking%2Fhomelab-tailscale/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpbaking%2Fhomelab-tailscale/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpbaking%2Fhomelab-tailscale/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpbaking","download_url":"https://codeload.github.com/jpbaking/homelab-tailscale/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpbaking%2Fhomelab-tailscale/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35174071,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-compose","exit-node","homelab","mesh-vpn","netstack","self-hosted","subnet-router","tailscale","userspace-networking","vpn","wireguard","wireguard-vpn"],"created_at":"2026-07-06T01:02:55.038Z","updated_at":"2026-07-06T01:02:55.786Z","avatar_url":"https://github.com/jpbaking.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# homelab-tailscale\n\n\u003e A drop-in **Tailscale exit node + subnet router** for your homelab — runs as a single,\n\u003e **unprivileged** Docker container (no `--privileged`, no `NET_ADMIN`, no `/dev/net/tun`),\n\u003e configured entirely from one `.env` file.\n\n[![License: 0BSD](https://img.shields.io/badge/license-0BSD-blue.svg)](LICENSE)\n![Docker Compose](https://img.shields.io/badge/Docker-Compose-2496ED?logo=docker\u0026logoColor=white)\n![Tailscale](https://img.shields.io/badge/Tailscale-WireGuard-242424?logo=tailscale\u0026logoColor=white)\n\nRoute your devices' internet traffic out through your home connection from anywhere, and\nreach machines on your home LAN over an encrypted [WireGuard](https://www.wireguard.com/)\nmesh — without poking holes in your firewall or exposing any ports.\n\n## Why use this\n\n- **🌐 Exit node, the easy way.** Browse from your laptop or phone as if you were home —\n  same public IP, same geo, encrypted end-to-end.\n- **🏠 Reach your LAN remotely.** Advertise your home subnets (`TS_ROUTES`) to hit NAS,\n  printers, dashboards, or your local DNS resolver from anywhere on the tailnet.\n- **🔒 Unprivileged by default.** Userspace (netstack) networking means **no elevated\n  capabilities and no host TUN device** — a smaller blast radius than typical Tailscale\n  container guides. `no-new-privileges` is on. Flip a single flag if you want kernel-mode\n  throughput instead.\n- **🧩 One-file config.** Everything lives in `.env`; the compose file is fully\n  parameterized and heavily commented.\n- **🛠️ Zero-friction ops.** A thin `compose-helper.sh` wrapper gives you `up`, `restart`,\n  `logs`, and friends without remembering `docker compose` incantations.\n- **♻️ Survives restarts.** Node identity persists in a named volume — no re-auth churn.\n\n## How the networking works\n\nThe **primary job is exit node**: tailnet clients route their internet traffic through\nthis container so it egresses from your home network's public IP. Secondarily, `TS_ROUTES`\nmakes it a **subnet router** so clients can reach hosts on your home LAN (e.g. a local DNS\nresolver).\n\n```mermaid\nflowchart LR\n    C[\"Tailnet clients\u003cbr/\u003e(laptop, phone, …)\"]\n\n    subgraph HOST[\"Docker host\"]\n        TS[\"tailscale container\u003cbr/\u003euserspace / netstack mode\u003cbr/\u003e\u003cb\u003eexit node\u003c/b\u003e + subnet router\"]\n    end\n\n    subgraph LAN[\"Home LAN\"]\n        H[\"LAN hosts e.g.\u003cbr/\u003eNAS · DNS · dashboards\u003cbr/\u003e192.168.x.0/24\"]\n    end\n\n    NET([\"🌐 Internet\"])\n\n    C -- \"encrypted WireGuard tunnel\" --\u003e TS\n\n    %% PRIMARY: exit-node path — all internet traffic egresses here\n    TS == \"EXIT NODE:\u003cbr/\u003eall internet traffic egresses here\" ==\u003e NET\n\n    %% Secondary: subnet-router path, controlled by TS_ROUTES\n    TS -. \"TS_ROUTES:\u003cbr/\u003ereach LAN subnets\" .-\u003e H\n\n    classDef primary fill:#dcfce7,stroke:#16a34a,stroke-width:3px,color:#000;\n    classDef routes fill:#dbeafe,stroke:#2563eb,stroke-width:1px,color:#000;\n    class TS,NET primary;\n    class H routes;\n```\n\n- **Thick green path = the main use:** internet egress via the exit node\n  (`--advertise-exit-node` in `TS_EXTRA_ARGS`).\n- **Dashed blue path = `TS_ROUTES`:** optional subnet routing to LAN hosts; leave it empty\n  to run exit-node-only. Both the exit node and each route must be **approved in the admin\n  console** before clients can use them.\n\n## Quick start\n\n\u003e Requires Docker + the Compose plugin, and a [Tailscale](https://tailscale.com/) account.\n\n```bash\ngit clone https://github.com/jpbaking/homelab-tailscale.git\ncd homelab-tailscale\n\ncp .env.example .env        # then edit: set TS_AUTHKEY and your TS_ROUTES\n./compose-helper.sh up      # build/pull, start detached, follow logs\n```\n\nThen, in the [admin console](https://login.tailscale.com/admin/machines), **approve the\nadvertised subnet routes and the exit node** for this machine — they stay off until approved.\n\n1. **Get an auth key** at \u003chttps://login.tailscale.com/admin/settings/keys\u003e. A *reusable +\n   pre-approved + tagged* key is recommended for unattended containers.\n2. **Configure** by copying `.env.example` to `.env` (gitignored) and filling it in — see\n   [`.env.example`](.env.example) for every variable and what it does.\n3. **Run** with `./compose-helper.sh up`.\n4. **Approve** the node's routes/exit-node in the admin console.\n\n## Configuration (`.env`)\n\nThis image is configured through environment variables — a TLDR of the\n[official Tailscale Docker docs](https://tailscale.com/docs/features/containers/docker/how-to/connect-docker-container):\n\n| Variable | What it does |\n|---|---|\n| `TS_AUTHKEY` | Auth key that logs the node into your tailnet. **Required.** |\n| `TS_HOSTNAME` | Name the node shows up as in the tailnet. |\n| `TS_STATE_DIR` | Where tailscaled persists login/state inside the container (backed by a named volume so it stays logged in across restarts). |\n| `TS_USERSPACE` | `true` = userspace/netstack networking (no TUN/`NET_ADMIN`); `false` = kernel networking (needs the TUN device + `NET_ADMIN`, higher throughput). |\n| `TS_ROUTES` | Comma-separated CIDRs to advertise as a **subnet router**. Empty = exit-node-only. |\n| `TS_ACCEPT_DNS` | Whether this node accepts DNS config pushed by the tailnet. |\n| `TS_EXTRA_ARGS` | Any extra `tailscale up` flags, e.g. `--advertise-exit-node`. |\n| `TS_IMAGE_TAG` | `tailscale/tailscale` image tag to run. |\n\n## Files\n\n| File | Purpose |\n|---|---|\n| `docker-compose.yaml` | The Tailscale service definition (single service, fully commented). |\n| `.env.example` | Template for `.env`. Copy it; `.env` itself is gitignored (holds the auth key). |\n| `compose-helper.env` | Config for the helper script (project name, timeouts, log tail). |\n| `compose-helper.sh` | Wrapper around `docker compose` with consistent project naming and shorthand commands. |\n\n## Helper commands\n\n```bash\n./compose-helper.sh up        # rebuild/pull, start detached, then follow logs\n./compose-helper.sh start     # start detached (no pull/build)\n./compose-helper.sh restart   # down then up\n./compose-helper.sh stop      # stop + remove orphans\n./compose-helper.sh down      # stop + remove orphans AND named volumes (wipes state!)\n./compose-helper.sh logs      # follow logs\n```\n\nAnything else passes straight through to `docker compose`, e.g.\n`./compose-helper.sh ps` or `./compose-helper.sh exec tailscale tailscale status`.\n\n\u003e ⚠️ `down` removes the `tailscale` named volume, which holds node state. After that the\n\u003e container re-authenticates with `TS_AUTHKEY` on next start. Use `stop`/`restart` for\n\u003e routine operations.\n\n## Notes \u0026 gotchas\n\n- **State** lives in the `tailscale` named volume at `/var/lib/tailscale`. Don't `down -v`\n  unless a clean re-auth is intended.\n- **Networking is deliberate.** This runs in userspace/netstack mode (`TS_USERSPACE=true`),\n  so the `/dev/net/tun` device, `cap_add: NET_ADMIN`, and forwarding sysctls are\n  intentionally commented out — netstack forwards subnet-router and exit-node traffic in\n  userspace without them. The cost is throughput, not capability. To switch to kernel\n  networking for line-rate forwarding, set `TS_USERSPACE=false` and uncomment the\n  `devices`, `cap_add`, and `sysctls` blocks in `docker-compose.yaml` (drop the ipv6\n  forwarding sysctl if the host has IPv6 disabled). `NET_RAW` is never needed.\n- **Split DNS for LAN hostnames** (resolve `*.lan` etc. through a home resolver) is set up\n  in the Tailscale admin console (DNS → restricted nameserver), not in this repo. It rides\n  on an advertised+approved subnet route to that resolver's IP.\n- **Linux clients** must run `tailscale up --accept-routes` to use advertised subnets; GUI\n  clients accept them by default.\n- `compose-helper.sh` is intended for local/homelab use — `down` deletes volumes and `.env`\n  is sourced into the process. Review before using it in CI.\n- Changing advertised routes or exit-node status requires **re-approval** in the admin console.\n\n## License\n\n[0BSD](LICENSE) — do whatever you want with it, no attribution required.\n\n## Acknowledgements\n\nBuilt on [Tailscale](https://tailscale.com/)'s official\n[`tailscale/tailscale`](https://hub.docker.com/r/tailscale/tailscale) image. See their\n[Docker how-to](https://tailscale.com/docs/features/containers/docker/how-to/connect-docker-container)\nand [example configs](https://github.com/tailscale-dev/docker-guide-code-examples) for more.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpbaking%2Fhomelab-tailscale","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpbaking%2Fhomelab-tailscale","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpbaking%2Fhomelab-tailscale/lists"}