How to setup headscale server in 5 minutes using docker-compose
This headscale setup is using sqlite
- with a much lighter memory & CPU footprint than PostgreSQL for simple usecases, I recommend this for almost any installation: Headscale doesn’t have to manage that many requests and using sqlite3 is fine for all but the most demanding setups.
First, create the directory where headscale and all the data will reside in (we use /opt/headscale
in this example).
sudo mkdir -p /opt/headscale
Now run the following script **in /opt/headscale
**to initialize the files and directories headscale requires:
mkdir -p ./config
touch ./config/db.sqlite
curl https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml -o ./config/config.yaml
docker-compose config
***Note:***We have an alternate docker-compose
config for use with Traefik as an reverse proxy, see Headscale docker-compose config for Traefik HTTPS reverse proxy
Now it’s time to create /opt/headscale/docker-compose.yml
:
services:
headscale:
image: headscale/headscale:latest
volumes:
- ./config:/etc/headscale/
- ./data:/var/lib/headscale
ports:
- 27896:8080
command: headscale serve
restart: unless-stopped
This will configure headscale to run its HTTP server on port 27896
. You can reverse proxy this port to the domain of your choice.
Configuration
Now we should edit the server name in config/config.yaml
:
server_url: https://headscale.mydomain.com
Note that you need to restart tailscale after each
Next, see How to create namespace on headscale server for details on how you can create a namespace. Once you have created a namespace (comparable to an account on the commercial tailscale service), you can continue connecting clients (the client software is called tailscale), see e.g. How to connect tailscale to headscale server on Linux
Autostart
Using the method described in our previous post Create a systemd service for your docker-compose project in 10 seconds we will now setup autostart on boot for headscale using systemd
. This command will also start it immediately:
curl -fsSL https://techoverflow.net/scripts/create-docker-compose-service.sh | sudo bash /dev/stdin
How to view the logs
Use this command to view & follow the logs:
docker-compose logs -f
Example output
headscale_1 | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
headscale_1 |
headscale_1 | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
headscale_1 | - using env: export GIN_MODE=release
headscale_1 | - using code: gin.SetMode(gin.ReleaseMode)
headscale_1 |
headscale_1 | [GIN-debug] GET /metrics --> github.com/zsais/go-gin-prometheus.prometheusHandler.func1 (4 handlers)
headscale_1 | [GIN-debug] GET /health --> github.com/juanfont/headscale.(*Headscale).Serve.func2 (4 handlers)
headscale_1 | [GIN-debug] GET /key --> github.com/juanfont/headscale.(*Headscale).KeyHandler-fm (4 handlers)
headscale_1 | [GIN-debug] GET /register --> github.com/juanfont/headscale.(*Headscale).RegisterWebAPI-fm (4 handlers)
headscale_1 | [GIN-debug] POST /machine/:id/map --> github.com/juanfont/headscale.(*Headscale).PollNetMapHandler-fm (4 handlers)
headscale_1 | [GIN-debug] POST /machine/:id --> github.com/juanfont/headscale.(*Headscale).RegistrationHandler-fm (4 handlers)
headscale_1 | [GIN-debug] GET /oidc/register/:mkey --> github.com/juanfont/headscale.(*Headscale).RegisterOIDC-fm (4 handlers)
headscale_1 | [GIN-debug] GET /oidc/callback --> github.com/juanfont/headscale.(*Headscale).OIDCCallback-fm (4 handlers)
headscale_1 | [GIN-debug] GET /apple --> github.com/juanfont/headscale.(*Headscale).AppleMobileConfig-fm (4 handlers)
headscale_1 | [GIN-debug] GET /apple/:platform --> github.com/juanfont/headscale.(*Headscale).ApplePlatformConfig-fm (4 handlers)
headscale_1 | [GIN-debug] GET /swagger --> github.com/juanfont/headscale.SwaggerUI (4 handlers)
headscale_1 | [GIN-debug] GET /swagger/v1/openapiv2.json --> github.com/juanfont/headscale.SwaggerAPIv1 (4 handlers)
headscale_1 | [GIN-debug] GET /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] POST /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] PUT /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] PATCH /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] HEAD /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] OPTIONS /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] DELETE /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] CONNECT /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | [GIN-debug] TRACE /api/v1/*any --> github.com/gin-gonic/gin.WrapF.func1 (5 handlers)
headscale_1 | 2022-01-16T19:04:04Z WRN Listening without TLS but ServerURL does not start with http://
headscale_1 | 2022-01-16T19:04:04Z INF listening and serving (multiplexed HTTP and gRPC) on: 0.0.0.0:8080
headscale_1 | 2022-01-16T19:04:04Z INF Setting up a DERPMap update worker frequency=86400000