111 lines
3.5 KiB
Markdown
111 lines
3.5 KiB
Markdown
# Headscale + Headplane + Traefik
|
|
|
|
This repository contains privacy-safe example files for running a self-hosted
|
|
Headscale control plane with the Headplane web UI behind Traefik and automatic
|
|
Let's Encrypt certificates.
|
|
|
|
Only `README.md`, `.gitignore`, and files ending in `.example` are published.
|
|
Real configuration, credentials, databases, generated keys, certificates,
|
|
backups, and container images are ignored by design.
|
|
|
|
## Architecture
|
|
|
|
- **Headscale** provides the Tailscale-compatible control plane and embedded DERP/STUN.
|
|
- **Headplane** provides the administration UI at `/admin`.
|
|
- **Traefik** terminates HTTPS and routes requests to Headscale or Headplane.
|
|
|
|
The example pins Headscale `v0.29.2`, Headplane `0.7.0`, and Traefik `v3.7.9`.
|
|
|
|
## Requirements
|
|
|
|
- A Linux server with Docker Engine and Docker Compose v2
|
|
- A public IPv4 address
|
|
- A domain such as `headscale.example.com` pointing to that address
|
|
- Inbound `80/tcp`, `443/tcp`, and `3478/udp` allowed by the firewall
|
|
|
|
## Prepare configuration
|
|
|
|
Clone the repository, then create the ignored runtime files from the examples:
|
|
|
|
```bash
|
|
cp docker-compose.yml.example docker-compose.yml
|
|
cp headscale/config/config.yaml.example headscale/config/config.yaml
|
|
cp headscale/config/acl.hujson.example headscale/config/acl.hujson
|
|
cp headplane/config/config.yaml.example headplane/config/config.yaml
|
|
cp traefik/traefik.yml.example traefik/traefik.yml
|
|
cp traefik/dynamic/headscale.yml.example traefik/dynamic/headscale.yml
|
|
|
|
mkdir -p headscale/data headscale/run headplane/data traefik/letsencrypt
|
|
touch traefik/letsencrypt/acme.json
|
|
chmod 600 traefik/letsencrypt/acme.json
|
|
```
|
|
|
|
Replace the example values in the copied files:
|
|
|
|
- `headscale.example.com`: the public control-plane domain
|
|
- `dns.example.com`: the MagicDNS base domain
|
|
- `203.0.113.10`: the server's public IPv4 address
|
|
- `admin@example.com`: the Let's Encrypt account email
|
|
- `REPLACE_WITH_OPENSSL_RAND_HEX_32`: output of `openssl rand -hex 32`
|
|
|
|
Do not put real credentials in a file ending in `.example`.
|
|
|
|
## Start and configure
|
|
|
|
Validate the Compose model, then start Headscale and Traefik first:
|
|
|
|
```bash
|
|
docker compose config --quiet
|
|
docker compose up -d headscale traefik
|
|
```
|
|
|
|
Create an API key for Headplane:
|
|
|
|
```bash
|
|
docker compose exec headscale headscale apikeys create --expiration 365d
|
|
```
|
|
|
|
Set the resulting key as `headscale.api_key` in the ignored
|
|
`headplane/config/config.yaml`, set a random cookie secret, and start Headplane:
|
|
|
|
```bash
|
|
docker compose up -d headplane
|
|
docker compose ps
|
|
```
|
|
|
|
Headscale is served at `https://headscale.example.com`; Headplane is served at
|
|
`https://headscale.example.com/admin`.
|
|
|
|
## Add a user and client
|
|
|
|
```bash
|
|
docker compose exec headscale headscale users create default
|
|
docker compose exec headscale headscale preauthkeys create \
|
|
--user default --reusable --expiration 24h
|
|
```
|
|
|
|
Join a client with the generated pre-auth key:
|
|
|
|
```bash
|
|
tailscale up \
|
|
--login-server https://headscale.example.com \
|
|
--auth-key YOUR_PREAUTH_KEY
|
|
```
|
|
|
|
## Optional OIDC
|
|
|
|
The Headscale and Headplane examples include commented OIDC sections. Copy and
|
|
uncomment them only in the ignored runtime configuration files. Keep the OIDC
|
|
client secret out of Git and use the same public issuer and client details in
|
|
both services.
|
|
|
|
## Operations
|
|
|
|
```bash
|
|
docker compose logs -f headscale headplane traefik
|
|
docker compose pull
|
|
docker compose up -d
|
|
```
|
|
|
|
Back up `headscale/data`, `headplane/data`, and `traefik/letsencrypt` securely.
|
|
These directories contain private state and are intentionally never published.
|