Deploy and Host Headscale on Sealos
Headscale is an open source, self-hosted implementation of the Tailscale control server. This template deploys Headscale 0.29.2 with the Headplane 0.7.0 web UI, persistent storage, TLS-enabled public endpoints, and an optional KubeBlocks PostgreSQL database.

About Headscale
Headscale coordinates a private Tailscale-compatible network. It manages users, machines, routes, DNS, ACL policies, and device enrollment while compatible clients establish their data paths.
The template runs Headscale and Headplane together in one StatefulSet so Headplane can safely reload Headscale configuration through its Kubernetes integration. SQLite is the default database and matches the upstream recommendation for new deployments. The use_postgresql option provisions a dedicated PostgreSQL 16.4.0 cluster when your operating model requires an external database.
Common Use Cases
- Run a private control plane for Tailscale-compatible clients.
- Connect homelab, edge, and team devices across networks.
- Manage users, routes, DNS, ACLs, and pre-auth keys from Headplane.
- Keep control-plane state on persistent Sealos storage.
- Use a managed PostgreSQL database for database-specific operational requirements.
Dependencies
The template includes the complete server-side runtime: Headscale, Headplane, persistent volumes, Service and Ingress resources, scoped Kubernetes RBAC, and the conditional PostgreSQL resources.
- Headscale 0.29.2 provides the control server and APIs.
- Headplane 0.7.0 provides the administration UI.
- KubeBlocks provides PostgreSQL 16.4.0 when
use_postgresql is enabled.
- A compatible Tailscale client is required on each device joining the network.
Architecture
| Component | Version | Purpose | Minimum tested limit |
|---|
| Headscale | 0.29.2-debug | Control server, REST API, gRPC API, and metrics | 100m CPU / 128Mi memory |
| Headplane | 0.7.0 | Web administration UI | 100m CPU / 256Mi memory |
| SQLite | Embedded | Default Headscale database on persistent storage | Included with Headscale |
| PostgreSQL | 16.4.0 | Optional KubeBlocks-managed database | 500m CPU / 512Mi memory |
| PostgreSQL init | postgres:16-alpine | Creates and verifies the headscale database | 100m CPU / 128Mi memory |
The application uses three 512Mi persistent volumes:
| Path | Contents |
|---|
/var/lib/headscale | SQLite database, keys, and Headscale runtime state |
/etc/headscale | Validated, non-sensitive Headscale configuration |
/var/lib/headplane | Headplane state |
The main public domain serves the Headscale HTTP API and Headplane at /admin/. A dedicated public domain exposes the Headscale gRPC endpoint. Metrics remain available only inside the cluster on port 9090.
Why Deploy Headscale on Sealos?
- Deploy the complete Headscale and Headplane stack from one App Store template.
- Receive HTTPS application and gRPC domains with managed TLS certificates.
- Keep configuration, keys, and database state on persistent volumes.
- Select embedded SQLite or a KubeBlocks-managed PostgreSQL database during deployment.
- Inspect resources, logs, events, and container terminals from the Sealos Canvas.
- Start with the tested minimum resources and adjust capacity as the tailnet grows.
Deploy on Sealos
- Open the Headscale template and click Deploy Now.
- Keep
use_postgresql disabled for the default SQLite deployment. Enable it to provision the dedicated PostgreSQL cluster.
- Wait for all resources to become ready. SQLite usually starts in a few minutes. A new PostgreSQL cluster may take several minutes during its first initialization.
- Open the application URL. The root path redirects to the Headplane sign-in page at
/admin/.
Sign In to Headplane
Headplane authenticates with a Headscale API key. Generate one from the Headscale container in the Sealos terminal:
headscale apikeys create
The command displays the key once. Store it securely and paste it into the API Key field on the Headplane sign-in page. Headscale gives a new key a 90-day lifetime by default. Set an explicit lifetime when needed:
headscale apikeys create --expiration 365d
After signing in:
- Open Users, select Add user, and create the first Headscale user.
- Open Settings > Auth Keys, select Create pre-auth key, choose the user, and set the desired lifetime and key options.
Connect a Device
Install a compatible Tailscale client, then use the main Sealos application URL and the pre-auth key created in Headplane:
tailscale up \
--login-server=https://your-headscale-domain.example.com \
--authkey=<pre-auth-key>
The device appears under Machines in Headplane after enrollment.
Remote Headscale CLI
The template exposes gRPC on a dedicated TLS domain. Use a headscale CLI binary matching server version 0.29.2, then configure the endpoint and API key:
export HEADSCALE_CLI_ADDRESS=your-headscale-grpc-domain.example.com:443
export HEADSCALE_CLI_API_KEY=<api-key>
headscale users list
The Headplane UI covers the common administration workflow, so remote gRPC access is optional.
Database Options
SQLite
SQLite is enabled by default and stores its database at /var/lib/headscale/db.sqlite. Write-ahead logging is enabled, and the persistent volume keeps state across Pod replacement.
PostgreSQL
Enable use_postgresql during deployment to create a KubeBlocks PostgreSQL cluster. The template creates the headscale database and waits for it to accept authenticated queries. Headscale receives the host, port, username, and password directly from the KubeBlocks Secret through its official HEADSCALE_DATABASE_POSTGRES_* environment variables.
The Kubernetes Secret remains the credential source, while /etc/headscale/config.yaml contains static, non-sensitive database settings. PostgreSQL adds a database Pod and a 1Gi data volume. The initialization Job allows up to six minutes for a cold database start.
Configuration
- Use Headplane to manage users, machines, routes, DNS, ACL policies, and pre-auth keys.
- Headscale reads
/etc/headscale/config.yaml from persistent storage. The init container validates mode, placeholders, and the complete Headscale configuration before replacing an incomplete file with an atomic same-directory move.
- Headplane reads
/etc/headplane/config.yaml from the template ConfigMap.
- Headplane receives its generated cookie secret through
HEADPLANE_SERVER__COOKIE_SECRET, keeping the value out of the ConfigMap.
- PostgreSQL credentials are injected directly into the Headscale container from the KubeBlocks connection Secret and stay out of persistent volumes.
shareProcessNamespace and scoped Pod-read RBAC let Headplane signal Headscale after supported configuration changes.
- The Pod runs as UID/GID
1000 with RuntimeDefault seccomp and all Linux capabilities dropped.
Troubleshooting
Headplane rejects the API key
Create a fresh API key in the Headscale container and paste the complete value into the sign-in form. API keys are displayed once and expire according to their configured lifetime.
A client does not enroll
Confirm that --login-server uses the main HTTPS application URL and that the pre-auth key belongs to an existing user. Check the Machines and Auth Keys pages in Headplane.
PostgreSQL deployment is still initializing
Open the Sealos Canvas and inspect the PostgreSQL Cluster, the *-pg-init Job, and the Headscale StatefulSet. The application starts after the database accepts an authenticated query and the headscale database exists.
Health checks
Use these endpoints to verify the two application containers:
https://your-headscale-domain.example.com/health
https://your-headscale-domain.example.com/admin/healthz
Resources
License
Headscale is available under the BSD-3-Clause license. Headplane is available under the MIT license. This template follows the repository license for Sealos templates.