--- title: Docker description: Install Headplane with Docker. outline: [2, 3] --- # Docker Installation ::: tip If you are not looking to deploy with Docker, follow the [**Native Mode**](./native-mode.md) 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.27.0 or later installed and running - A [completed configuration file](./index.md#configuration) for Headplane. ## Installation Running Headplane in with Docker is as simple as applying 1 compose file: ```yaml 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. ## Health Checks The Docker image includes a built-in healthcheck that verifies the Headplane server is running and responding. Docker will automatically monitor the container and report its health status. No additional configuration is required. The healthcheck binary is located at `/bin/hp_healthcheck` inside the container. If you need to override the default healthcheck behavior, you can do so in your `compose.yaml`: ```yaml services: headplane: image: ghcr.io/tale/headplane:latest healthcheck: test: ["CMD", "/bin/hp_healthcheck"] interval: 30s timeout: 5s start_period: 5s retries: 3 ``` ## 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: ```bash # You may want to tweak the expiration duration as needed headscale apikeys create --expiration 90d ``` ## Enabling 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 Head**scale** 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](https://github.com/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: ```yaml 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_path in Headscale (recommended), # also mount that file here so Headplane can read and write it. If the # in-container path differs from Headscale's dns.extra_records_path, # set `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.27.1 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](https://github.com/tale/headplane/blob/main/config.example.yaml) 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 Head**scale** configuration file within the container (e.g., `/etc/headscale/config.yaml`). | | `headscale.dns_records_path` | _Optional_. Refer to the [example configuration](https://github.com/tale/headplane/blob/main/config.example.yaml) 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](https://tailscale.com/kb/1193/tailscale-ssh). This feature requires that Tailscale SSH is running on your nodes (done via `tailscale up --ssh`). This feature uses the [Headplane Agent](../features/agent.md) to facilitate the SSH connections. Refer to the [Agent documentation](../features/agent.md) 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](../features/sso.md) 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](https://headscale.net/stable/ref/integration/reverse-proxy/) 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](./native-mode.md#reverse-proxying) 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. ```yaml 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.27.1 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" ```