Deploying headscale to Fly.io for homelab setup
February 19, 2023 -This time I took my local headscale setup and deployed to Fly.io, to allow nodes from different locations to connect into my homelab mesh network.
This builds on top of work done in previous TIL, but moves away from local testing using Docker and some VMs to real internetz deployment.
▶️ Also made a recording of this that you can watch on YouTube here.
1. Adjusting existing configuration
Decided to do some adjustments to headscale.yml
configuration file in order
to make it easier for me to configure Fly's mount volumes:
-private_key_path: /var/lib/headscale/private.key
+private_key_path: /data/private.key
- private_key_path: /var/lib/headscale/noise_private.key
+ private_key_path: /data/noise_private.key
-db_path: /var/lib/headscale/headscale.sqlite3
+db_path: /data/headscale.sqlite3
2. Create an application in Fly
Used --generate-name
for testing, but you should give a good, memorable
name for your application 😉:
$ flyctl apps create --generate-name
New app created: dark-fire-450
And create the volume that we will use to store headscale's database:
$ flyctl volumes create --app dark-fire-450 --region cdg --size 1 headscale_data
ID: vol_2n0l9vllx58v635d
Name: headscale_data
App: dark-fire-450
Region: cdg
Zone: 0e8c
Size GB: 1
Encrypted: true
Created at: 19 Feb 23 12:23 UTC
3. Adjust configuration
The flyctl apps create
command didn't generate a configuration file, so
we are going to manually create it:
app = "dark-fire-450"
kill_signal = "SIGINT"
kill_timeout = 5
[metrics]
port = 9090
path = "/metrics"
[experimental]
auto_rollback = true
[[services]]
internal_port = 8080
protocol = "tcp"
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
[mounts]
source = "headscale_data"
destination = "/data"
Notice app
indicates the name of the application we just created (dark-fire-450
)
and source
mountpoint uses the name of the volume we created after.
4. Deploying and updating our configuration
We are ready to deploy our new application:
$ flyctl deploy --region cdg
==> Verifying app config
--> Verified app config
==> Building image
Remote builder fly-builder-old-fire-5640 ready
==> Creating build context
--> Creating build context done
==> Building image with Docker
...
==> Creating release
--> release v2 created
--> You can detach the terminal anytime without stopping the deployment
==> Monitoring deployment
Logs: https://fly.io/apps/dark-fire-450/monitoring
1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 critical]
Note that used cdg
for this application since is the closest region to my
location, but you could use other. See flyctl platform regions
for
alternatives.
After a few seconds, we should be able to SSH into our headscale deployment
$ flyctl ssh console
Connecting to fdaa:0:5fde:a7b:5b66:5:4cd:2... complete
And we can create our homelab network and keys to use with our nodes:
/ # headscale users create homelab
User created
/ # headscale --user homelab preauthkeys create --reusable --expiration 24h
df72670243f0f635bc2b5d73e5a42f1c40564d9c954dfd78
This time I used preauthorized keys so I could apply the same key to multiple nodes without having to approve each node manually.
Then, tested against a remote servers and connected them to the mesh network, using the same key:
$ tailscale up --login-server https://dark-fire-450.fly.dev:443 --authkey df72670243f0f635bc2b5d73e5a42f1c40564d9c954dfd78
5. What's next?
Now we have some data out there, what would be our backup strategy? How could we avoid losing all the configuration of our nodes and mesh network?
More of that in future posts! 😊