In this article we demonstrate how you can run Headscale and Headscale UI using Docker Containers. Our recent article was specific to installation of Headscale on Ubuntu Linux system using .deb package. The link to the post is shared in the link below.
If you don’t prefer package based installation of Headscale then this article is designed for you. Headscale is a very powerful and open source alternative solution to Tailscale coordination server. It is built to be self-hosted in your own infrastructure. Headscale is independently developed and doesn’t have any ralationship with Tailscale company except being re-implemented version of Tailscale coordination server.
Follow the steps shared in this blog post to setup a dedicated Headscale server running in Docker container. The same procedure can be re-used for Podman / Podman compose setup with few modifications. We’ve also included installation of Headscale UI in docker.
1) Install Docker Engine
Begin your setup by ensuring Docker Engine is installed and working. Refer to the following guide on installation of Docker on Linux systems.
Once installed you can check version.
$ docker --version
Docker version 24.0.7, build afdd53bAlso check if compose plugin is installed.
$ docker compose version
Docker Compose version v2.21.02) Define Docker Compose file
Let’s create a directory that stores Headscale configurations and data.
mkdir -p ~/headscale && cd ~/headscaleCreate an empty SQlite datebase in the headscale directory:
mkdir ./config
touch ./config/db.sqliteCreate a new file called docker-compose.yml
vim docker-compose.ymlPaste and modify below contents. If you don’t need Headscale running in Docker container, then remove its sections.
version: '3.9'
services:
headscale:
container_name: headscale
image: headscale/headscale:latest
command: headscale serve
restart: unless-stopped
ports:
- 8080:8080
- 9090:9090
volumes:
- ./config:/etc/headscale/
- ./data:/var/lib/headscale/
headscale-ui:
container_name: headscale-ui
image: ghcr.io/gurucomputing/headscale-ui:latest
pull_policy: always
restart: unless-stopped
ports:
- 9080:80In our compose file we are:
- Mounting
config/under/etc/headscale - Mounting
data/under/var/lib/headscale/ - Forwarding port 8080 out of the headscale container so the
headscaleinstance becomes available - Forwarding port 9080 out of the headscale-ui container so the
headscale-uiinstance becomes available
If using https on the UI you can map separate ports as below.
- 9443:4433) Start Headscale containers
Download configuration template file.
mkdir {config,data}
wget https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml -O config/config.yamlModify the config file to your preferences before launching Docker container. Here are some settings that you likely want:
# Change to your hostname or host IP
# server_url: //vpn.computingforgeeks.com:8080
server_url: http://192.168.20.11:8080
# Listen to 0.0.0.0 so it's accessible outside the container
metrics_listen_addr: 0.0.0.0:9090Start the containers using docker compose command. The -d option means detach so they run in the background.
$ docker compose up -d
[+] Running 19/19
✔ headscale-ui 13 layers [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿] 0B/0B Pulled 3.2s
✔ 8921db27df28 Pull complete 0.5s
✔ 5e0dab5e7f18 Pull complete 0.5s
✔ e7466e9238c6 Pull complete 0.5s
✔ 9aca5eef2d98 Pull complete 0.7s
✔ 4f4fb700ef54 Pull complete 0.8s
✔ 5007d4d43f68 Pull complete 0.8s
✔ 2ebc6f305da4 Pull complete 1.0s
✔ 712d06ffc498 Pull complete 1.1s
✔ ac473b17e437 Pull complete 1.7s
✔ 36f5767e6555 Pull complete 1.7s
✔ 7c6ac0210a21 Pull complete 1.8s
✔ 7765e166cec5 Pull complete 1.8s
✔ 896f16a8f15c Pull complete 2.0s
✔ headscale 4 layers [⣿⣿⣿⣿] 0B/0B Pulled 4.4s
✔ 9e3ea8720c6d Pull complete 2.3s
✔ 71273269cd97 Pull complete 2.4s
✔ 007b622b7a95 Pull complete 2.5s
✔ 50a37fbfee41 Pull complete 2.5s
[+] Running 3/3
✔ Network headscale_default Created 0.1s
✔ Container headscale-ui Started 0.6s
✔ Container headscale StartedCheck status the containers created.
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
headscale headscale/headscale:latest "headscale serve" headscale 45 seconds ago Up 43 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:9090->9090/tcp, :::9090->9090/tcp
headscale-ui ghcr.io/gurucomputing/headscale-ui:latest "/bin/sh -c '/bin/sh…" headscale-ui 45 seconds ago Up 43 seconds 443/tcp, 0.0.0.0:9080->80/tcp, :::9080->80/tcpYou can follow the container logs to see what’s happening inside by running.
$ docker logs --follow headscale
2023-10-05T23:29:19Z INF go/src/headscale/hscontrol/protocol_common.go:574 > Successfully sent auth url AuthURL=http://192.168.20.11:8080/register/nodekey:af52937e8bc375a0d73a01cd453fd2f814144796b0270cedb0d126a39518c306 machine=jammy noise=true
$ docker logs --follow headscale-ui
no Caddyfile detected, copying across default config
Starting Caddy
{"level":"info","ts":1696545638.4848695,"msg":"using provided configuration","config_file":"/data/Caddyfile","config_adapter":"caddyfile"}
{"level":"info","ts":1696545638.4858341,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
{"level":"info","ts":1696545638.4859416,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc000544fc0"}
{"level":"info","ts":1696545638.486509,"logger":"http","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
{"level":"warn","ts":1696545638.4865189,"logger":"http","msg":"automatic HTTP->HTTPS redirects are disabled","server_name":"srv0"}
{"level":"warn","ts":1696545638.4865253,"logger":"http","msg":"server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server","server_name":"srv1","http_port":80}
{"level":"info","ts":1696545638.4865966,"logger":"pki.ca.local","msg":"root certificate trust store installation disabled; unconfigured clients may show warnings","path":"storage:pki/authorities/local/root.crt"}
{"level":"warn","ts":1696545638.4866195,"logger":"tls","msg":"YOUR SERVER MAY BE VULNERABLE TO ABUSE: on-demand TLS is enabled, but no protections are in place","docs":"https://caddyserver.com/docs/automatic-https#on-demand-tls"}
{"level":"info","ts":1696545638.48668,"logger":"http.log","msg":"server running","name":"srv1","protocols":["h1","h2","h3"]}
{"level":"info","ts":1696545638.486689,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/home/appuser/.local/share/caddy"}
{"level":"info","ts":1696545638.4867213,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
{"level":"info","ts":1696545638.4867342,"logger":"tls","msg":"finished cleaning storage units"}
{"level":"info","ts":1696545638.4867566,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size for details."}
{"level":"info","ts":1696545638.4868112,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
{"level":"info","ts":1696545638.486932,"msg":"autosaved config (load with --resume flag)","file":"/home/appuser/.config/caddy/autosave.json"}
{"level":"info","ts":1696545638.4869497,"msg":"serving initial configuration"}The ss command can be used to check ports on the host are bound.
$ ss -tunlp|egrep '8080|9080'
tcp LISTEN 0 4096 0.0.0.0:9080 0.0.0.0:* users:(("docker-proxy",pid=364448,fd=4))
tcp LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=364428,fd=4))
tcp LISTEN 0 4096 [::]:9080 [::]:* users:(("docker-proxy",pid=364455,fd=4))
tcp LISTEN 0 4096 [::]:8080 [::]:* users:(("docker-proxy",pid=364434,fd=4))4) Access Container shell
You can access headscale container shell by running:
docker exec -ti headscale bashFor headscale-ui run:
docker exec -ti headscale-ui shWe can test the functionality by creating a user on Headscale instance.
root@4f2fbe02c029:/# headscale users create computingforgeeksA one line command execution without entering container’s shell will be.
docker exec headscale \
headscale users create myuser5) Join client devices to Headscale network
You need a user created on your Headscale server. We’ll create one named computingforgeeks.
$ docker exec headscale headscale users create computingforgeeks
User createdInstalling Tailscale clients on your devices
Installing Tailscale on Linux / BSD
For Linux and BSD based systems, run the commands:
curl -fsSL https://tailscale.com/install.sh | sudo shChecking the version of Tailscale can be done with the commands below.
$ tailscale --version
1.50.1
tailscale commit: f45c02bfcf5ee5790c3af278c9e974c9b9b0e771
other commit: 36a20760a45bd1936686879b34c35146cc0c4ec1
go version: go1.21.1Installing on macOS
You can install using Homebrew:
brew install tailscaleOr by running the script.
curl -fsSL https://tailscale.com/install.sh | sudo shRegister a machine (normal login)
On a client machine, execute the tailscale login command:
tailscale up --login-server YOUR_HEADSCALE_URLSee example below.
# tailscale up --login-server http://192.168.20.11:8080
To authenticate, visit:
http://192.168.20.11:8080/register/nodekey:410155d1792d0f81a5f39415a1a418f882208751570c2e5195f7a6842ca44e6aOpening the links gives commands to be executed in Headscale server to register the device.
