Docker Installation
TIP
If you are not looking to deploy with Docker, follow the Native Mode deployment guide.
The recommended way to deploy Headplane is through Docker. This method is quick, easy, and works in most environments. It requires that Headscale is also running with Docker.
Prerequisites
- Docker and Docker Compose
- Headscale version 0.26.0 or later installed and running
- A completed configuration file for Headplane.
Installation
Running Headplane in with Docker is as simple as applying 1 compose file:
services:
headplane:
image: ghcr.io/tale/headplane:latest
container_name: headplane
restart: unless-stopped
ports:
- '3000:3000'
volumes:
- './config.yaml:/etc/headplane/config.yaml'
- './headplane-data:/var/lib/headplane'It's important to mount your configuration file and also provide a persistent storage location for Headplane to store its own data. You can also change the port mapping if you want to run it on a different port.
Accessing Headplane
After starting the container, you can access the Headplane web interface by navigating to http://localhost:3000/admin in your web browser (replace localhost with your server's IP address or domain name if not running locally).
In order to log in, you'll need to supply a Headscale API key. You can create one by running the following command within your Headscale environment:
# You may want to tweak the expiration duration as needed
headscale apikeys create --expiration 90dEnabling advanced features
You've technically completed the installation, but read on if you would like to enable advanced features like the ability to edit network settings from the UI or remote SSH from the browser.
Network Management
Network management allows you to configure Tailnet settings such as DNS servers, custom A records, the tailnet domain name, and MagicDNS from the Headplane UI.
Prerequisites
Network management (and other configurable Headscale features) requires that Headplane and Headscale both run together in the same Docker machine. This is because Headplane needs the following permissions:
- Access to read and write the Headscale configuration file through a shared volume used by both Headscale and Headplane.
- Access to the Docker socket (usually
/var/run/docker.sock, you may also use a proxy such as Tecnativa/docker-socket-proxy).
Configuration
First you'll need to run both Headscale and Headplane in the same Docker environment. Here is an example compose.yaml file that accomplishes this:
services:
headplane:
image: ghcr.io/tale/headplane:latest
container_name: headplane
restart: unless-stopped
ports:
- '3000:3000'
volumes:
# Same as before
- '/path/to/your/config.yaml:/etc/headplane/config.yaml'
- '/path/to/data/storage:/var/lib/headplane'
# A shared path to the Headscale config file. It is important that the
# path you mount this on matches `headscale.config_path` in your
# Headplane config.yaml file.
- '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
# If you are using dns.extra_records in Headscale (recommended), you
# should also mount that file here so Headplane can read and write it.
# Ensure that the path matches `headscale.dns_records_path` in your
# Headplane config.yaml file.
- '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
# Read-only access to the Docker socket (or a proxy)
- '/var/run/docker.sock:/var/run/docker.sock:ro'
headscale:
image: headscale/headscale:0.26.0
container_name: headscale
restart: unless-stopped
command: serve
labels:
# This label is absolutely necessary to help Headplane find Headscale.
me.tale.headplane.target: headscale
ports:
- '8080:8080'
volumes:
# Notice how these are on the exact same path as the host for both
# Headscale and Headplane! This is very important.
- '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
- '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
- '/path/to/headscale/data/storage:/var/lib/headscale'INFO
With some effort, you can technically run Headscale and Headplane in separate Docker hosts and remotely connect to a Docker daemon. This is an advanced setup that is not covered in this documentation. Refer to the example configuration for more details on setting it up.
You'll also need to enable a few fields in your Headplane configuration file:
| Field | Description |
|---|---|
integration.docker.enabled | Set to true to enable Docker integration. |
headscale.config_path | Path to your Headscale configuration file within the container (e.g., /etc/headscale/config.yaml). |
headscale.dns_records_path | Optional. Refer to the example configuration for details. |
With these settings in place, restart Headplane. You should now see additional options in the UI navbar such as "DNS" and "Settings" where you can manage your Tailnet configuration.
Remote Web SSH
Remote Web SSH allows you to open a terminal session to your Tailscale nodes directly from the Headplane web interface via Tailscale SSH. This feature requires that Tailscale SSH is running on your nodes (done via tailscale up --ssh).
This feature uses the Headplane Agent to facilitate the SSH connections. Refer to the Agent documentation for setup instructions.
Single Sign-On (SSO)
Single Sign-On (SSO) authentication allows users to log in to Headplane using external identity providers such as Google, GitHub, or any provider that supports OpenID Connect (OIDC).
To get started with SSO, refer to the SSO documentation for detailed setup instructions.
Reverse Proxying
You should run Headplane behind a reverse proxy such as Nginx or Caddy in production. Additionally, putting Headscale beind the reverse proxy allows you to access both services via the same domain and TLS certificate.
Configuration
Headscale supports integrating with several reverse proxies such as Nginx, Caddy, Apache, etc. Deploying Headplane is as simple as adding a handler to route any requests to /admin to the Headplane service. Refer to the Traefik example below for a reference configuration. A similar setup via Nginx without Docker is available in the Native Mode installation documentation.
Example Traefik Configuration
The following configuration will set up Traefik to proxy all Headscale requests on headscale.example.com and serve the Headplane UI under the /admin path. This is identical to how Tailscale's own admin console is served.
Keep in mind this won't work on its own as you'll need to configure Traefik and TLS certificates as needed. This is just a snippet to show how to configure the routing for Headplane and Headscale.
services:
# Same as before
headplane:
image: ghcr.io/tale/headplane:latest
container_name: headplane
restart: unless-stopped
ports:
- '3000:3000'
volumes:
- '/path/to/your/config.yaml:/etc/headplane/config.yaml'
- '/path/to/data/storage:/var/lib/headplane'
- '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
- '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
- '/var/run/docker.sock:/var/run/docker.sock:ro'
labels:
# Expose the admin UI at /admin
- 'traefik.enable=true'
- 'traefik.http.routers.headplane.rule=Host(`headscale.example.com`) && PathPrefix(`/admin`)'
- 'traefik.http.routers.headplane.entrypoints=websecure'
- 'traefik.http.routers.headplane.tls=true'
headscale:
image: headscale/headscale:0.26.0
container_name: headscale
restart: unless-stopped
command: serve
ports:
- '8080:8080'
volumes:
- '/path/to/headscale/config.yaml:/etc/headscale/config.yaml'
- '/path/to/headscale/dns_records.json:/etc/headscale/dns_records.json'
- '/path/to/headscale/data/storage:/var/lib/headscale'
labels:
- 'me.tale.headplane.target=headscale'
# Traefik labels to expose Headscale at headscale.example.com
- 'traefik.enable=true'
- 'traefik.http.routers.headscale.rule=Host(`headscale.example.com`)'
- 'traefik.http.routers.headscale.entrypoints=websecure'
- 'traefik.http.routers.headscale.tls=true'
# This middleware is essential to ensuring Headplane works correctly
- 'traefik.http.routers.headscale.middlewares=cors'
- 'traefik.http.middlewares.cors.headers.accesscontrolallowheaders=*'
- 'traefik.http.middlewares.cors.headers.accesscontrolallowmethods=GET,POST,PUT'
- 'traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=https://headscale.example.com'
- 'traefik.http.middlewares.cors.headers.accesscontrolmaxage=100'
- 'traefik.http.middlewares.cors.headers.addvaryheader=true'
# If you would optionally like to automatically redirect / to /admin
- 'traefik.http.routers.rewrite.rule=Host(`headscale.example.com`) && Path(`/`)'
- 'traefik.http.routers.rewrite.service=headscale'
- 'traefik.http.routers.rewrite.middlewares=rewrite'
- 'traefik.http.middlewares.rewrite.addprefix.prefix=/admin'
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
# Example volumes/setup, please configure Traefik as needed
- '/var/run/docker.sock:/var/run/docker.sock:ro'
- '/path/to/certs/storage:/certs'