Skip to content

Docker Installation

TIP

If you are not looking to deploy with Docker, follow the Native Mode deployment guide.

The recommended way to deploy Headtower is through Docker. This method is quick, easy, and works in most environments. It requires that Headscale is also running with Docker.

Prerequisites

Installation

Running Headtower in with Docker is as simple as applying 1 compose file:

yaml
services:
  headtower:
    image: ghcr.io/rnihesh/headtower:latest
    container_name: headtower
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - "./config.yaml:/etc/headtower/config.yaml"
      - "./headtower-data:/var/lib/headtower"

It's important to mount your configuration file and also provide a persistent storage location for Headtower 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 Headtower 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:
  headtower:
    image: ghcr.io/rnihesh/headtower:latest
    healthcheck:
      test: ["CMD", "/bin/hp_healthcheck"]
      interval: 30s
      timeout: 5s
      start_period: 5s
      retries: 3

Accessing Headtower

After starting the container, you can access the Headtower 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 Headtower UI.

Prerequisites

Network management (and other configurable Headscale features) requires that Headtower and Headscale both run together in the same Docker machine. This is because Headtower needs the following permissions:

  • Access to read and write the Headscale configuration file through a shared volume used by both Headscale and Headtower.
  • 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 Headtower in the same Docker environment. Here is an example compose.yaml file that accomplishes this:

yaml
services:
  headtower:
    image: ghcr.io/rnihesh/headtower:latest
    container_name: headtower
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      # Same as before
      - "/path/to/your/config.yaml:/etc/headtower/config.yaml"
      - "/path/to/data/storage:/var/lib/headtower"

      # A shared path to the Headscale config file. It is important that the
      # path you mount this on matches `headscale.config_path` in your
      # Headtower 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 Headtower 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 Headtower 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 Headtower find Headscale.
      me.rnihesh.headtower.target: headscale
    ports:
      - "8080:8080"
    volumes:
      # Notice how these are on the exact same path as the host for both
      # Headscale and Headtower! 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 Headtower 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 Headtower configuration file:

FieldDescription
integration.docker.enabledSet to true to enable Docker integration.
headscale.config_pathPath to your Headscale configuration file within the container (e.g., /etc/headscale/config.yaml).
headscale.dns_records_pathOptional. Refer to the example configuration for details.

With these settings in place, restart Headtower. 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 Headtower 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 Headtower 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 Headtower 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 Headtower 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 Headtower is as simple as adding a handler to route any requests to /admin to the Headtower 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 Headtower 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 Headtower and Headscale.

yaml
services:
  # Same as before
  headtower:
    image: ghcr.io/rnihesh/headtower:latest
    container_name: headtower
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - "/path/to/your/config.yaml:/etc/headtower/config.yaml"
      - "/path/to/data/storage:/var/lib/headtower"
      - "/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.headtower.rule=Host(`headscale.example.com`) && PathPrefix(`/admin`)"
      - "traefik.http.routers.headtower.entrypoints=websecure"
      - "traefik.http.routers.headtower.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.rnihesh.headtower.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 Headtower 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"