Docker image to run a self-hosted Headscale server (open-source Tailscale coordination server).
435
Docker image to run a Headscale server — a self-hosted, open-source implementation of the Tailscale coordination server. Connect all your devices using the official Tailscale client apps, with your own server in control.
Features:
hs_manage)linux/amd64, linux/arm64Also available:
A publicly reachable server with a domain name and TLS certificate is strongly recommended. See TLS and reverse proxy for setup options.
Create a vpn.env file. HS_SERVER_URL is the HTTPS URL that Tailscale clients use to connect to your server. See Environment variables for all options.
HS_SERVER_URL=https://hs.example.com
Run the container:
docker run \
--name headscale \
--restart=always \
-p 127.0.0.1:8080:8080/tcp \
-v headscale-data:/var/lib/headscale \
-v ./vpn.env:/vpn.env:ro \
-d hwdsl2/headscale-server
Note: With the above command, port 8080 is bound to localhost only. A reverse proxy on the host that handles TLS and forwards to 127.0.0.1:8080 is required for Tailscale clients to connect. See TLS and reverse proxy. To expose the port directly instead, replace 127.0.0.1:8080:8080 with 8080:8080.
Alternatively, you may set up Headscale without Docker. To learn more about how to use this image, read the sections below.
On first start, the container will:
admin)Retrieve the initial pre-auth key from the logs:
docker logs headscale

Connect a device using the official Tailscale client:
tailscale up --login-server https://hs.example.com --authkey <key-from-logs>
cp vpn.env.example vpn.env
nano vpn.env # Set HS_SERVER_URL at minimum
docker compose up -d
docker compose logs headscale
Example docker-compose.yml (already included):
services:
headscale:
image: hwdsl2/headscale-server
container_name: headscale
restart: always
ports:
- "127.0.0.1:8080:8080/tcp"
volumes:
- headscale-data:/var/lib/headscale
- ./vpn.env:/vpn.env:ro
volumes:
headscale-data:
Get the trusted build from the Docker Hub registry:
docker pull hwdsl2/headscale-server
Alternatively, you may download from Quay.io:
docker pull quay.io/hwdsl2/headscale-server
docker image tag quay.io/hwdsl2/headscale-server hwdsl2/headscale-server
Supported platforms: linux/amd64 and linux/arm64.
Refer to the Headscale documentation for instructions on connecting clients:
All variables are optional. HS_SERVER_URL is strongly recommended for production use.
| Variable | Default | Description |
|---|---|---|
HS_SERVER_URL | auto-detected | URL that Tailscale clients connect to (e.g. https://hs.example.com). Must be HTTPS for full client functionality. |
HS_LISTEN_PORT | 8080 | TCP port the server listens on. |
HS_METRICS_PORT | 9090 | Prometheus metrics port. Set to empty to disable. |
HS_BASE_DOMAIN | headscale.internal | Base domain for MagicDNS hostnames (e.g. myhost.headscale.internal). Must not equal or be a parent domain of the hostname in HS_SERVER_URL (e.g. if HS_SERVER_URL=https://hs.example.com, do not use example.com). |
HS_USERNAME | admin | Name of the first user created on initial setup. |
HS_DNS_SRV1 | 1.1.1.1 | Primary DNS server pushed to clients via MagicDNS. Accepts IPv4 or IPv6. |
HS_DNS_SRV2 | 1.0.0.1 | Secondary DNS server pushed to clients via MagicDNS. |
HS_LOG_LEVEL | info | Log verbosity: panic, fatal, error, warn, info, debug, trace. |
Note: In your env file, you may enclose values in single quotes, e.g. VAR='value'. Do not add spaces around =.
The configuration file is regenerated on each container start. To change a setting, update vpn.env and restart the container. The env file is bind-mounted into the container, so changes are picked up on every restart without recreating the container.
Tailscale clients work best with HTTPS. The recommended setup is to run a reverse proxy in front of Headscale that handles TLS termination, then set HS_SERVER_URL to your HTTPS URL.
Use one of the following addresses to reach the Headscale container from your reverse proxy:
headscale:8080 — if your reverse proxy runs as a container in the same Docker network as Headscale (e.g. defined in the same docker-compose.yml). Docker resolves the container name automatically.127.0.0.1:8080 — if your reverse proxy runs on the host and port 8080 is published (the default docker-compose.yml publishes it).Note: Do not use the container's internal IP address obtained from docker inspect. That IP address changes every time the container is recreated.
Example with Caddy (Docker image) (automatic TLS via Let's Encrypt, reverse proxy in the same Docker network):
Caddyfile:
hs.example.com {
reverse_proxy headscale:8080
}
Example with nginx (reverse proxy on the host):
server {
listen 443 ssl;
server_name hs.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Set HS_SERVER_URL=https://hs.example.com in your vpn.env and restart the container.
Ports to open in your firewall:
| Port | Protocol | Purpose |
|---|---|---|
8080 | TCP | Headscale coordination server (or your reverse proxy port) |
443 | TCP | HTTPS (if using a reverse proxy) |
9090 | TCP | Prometheus metrics (optional, not published by default) |
Use the hs_manage helper to manage users and nodes from the host without entering the container.
Register a node by its node key:
docker exec headscale hs_manage --registernode <key> --user admin
Add a user:
docker exec headscale hs_manage --adduser alice
Delete a user:
docker exec -it headscale hs_manage --deleteuser alice
# Or skip the confirmation prompt:
docker exec headscale hs_manage --deleteuser alice --yes
Create a pre-auth key for a user:
docker exec headscale hs_manage --createkey --user alice
List users:
docker exec headscale hs_manage --listusers
List all registered nodes:
docker exec headscale hs_manage --listnodes
List nodes for a specific user:
docker exec headscale hs_manage --listnodes --user alice
Delete a node by ID:
docker exec -it headscale hs_manage --deletenode 3
# Or skip the confirmation prompt:
docker exec headscale hs_manage --deletenode 3 --yes
List all pre-auth keys:
docker exec headscale hs_manage --listkeys
Show help:
docker exec headscale hs_manage --help
You can also run Headscale commands directly using docker exec headscale headscale <command>. Run docker exec headscale headscale -h or refer to the Headscale documentation for available commands.
To update the Docker image and container, first download the latest version:
docker pull hwdsl2/headscale-server
If the Docker image is already up to date, you should see:
Status: Image is up to date for hwdsl2/headscale-server:latest
Otherwise, it will download the latest version. Remove and re-create the container using instructions from Quick start. Your data is preserved in the headscale-data volume.
alpine:3.23/var/lib/headscale (Docker volume)vpn.env on every container start; update vpn.env and restart to apply changes (no container re-creation needed)8080/tcp (coordination server), 9090/tcp (Prometheus metrics, optional)linux/amd64, linux/arm64Note: The software components inside the pre-built image (such as Headscale) are under the respective licenses chosen by their respective copyright holders. As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within.
Copyright (C) 2026 Lin Song
This work is licensed under the MIT License.
Headscale is Copyright (c) 2020, Juan Font, and is distributed under the BSD 3-Clause License.
Tailscale® is a registered trademark of Tailscale Inc. This project is not affiliated with or endorsed by Tailscale Inc.
Content type
Image
Digest
sha256:c6adc30fd…
Size
29.1 MB
Last updated
1 day ago
docker pull hwdsl2/headscale-server