Skip to content

Instantly share code, notes, and snippets.

@Fijo
Last active July 11, 2024 12:06
Show Gist options
  • Save Fijo/b75d25bcb0be0c5ed8106319a99f6a79 to your computer and use it in GitHub Desktop.
Save Fijo/b75d25bcb0be0c5ed8106319a99f6a79 to your computer and use it in GitHub Desktop.
Assortment of Tailscale (client) docker stacks for different scenarios

Gistbased disclaimer

This file, as well as the .gitignore-file, should end up next to every stack. Some stacks only need certain environment variable (remove parts acordingly). I recommend putting each docker-compose.yml into a folder named based on the part before the underscore (_) in the gists .yml-filename.

You should not commit secrets to git.

Known improvements

Traefik should really be updated to the latest version I'm still using Version 1.7.

Setup

before running create a .env file in this folder with

TS_AUTHKEY=SOME_HEX_STRING # generate this key inside your headscale server container HOSTNAME=docker-home #prefix used to generated the hostname visible in tailscale

Bonus: Tailscale related findings/ research notes

Tailscale serve in the official docker image

The tailscale docs on docker talk about some neat support for tailscale serve. It seamed to me as this is no longer working, I think the docs may simply be outdated here. Ticket I've created: tailscale-dev/docker-guide-code-examples#9

Headscale limitations

HTTPS (tailscale serve)

The whole tailscale https feature currently doesn't work with headscale. Source: juanfont/headscale#1921

Tailscale funnel

There's no support for tailscales funnel feature in headscale either.

DNS

Personally I decided, I don't like most of the headscale DNS features. I'm handleing DNS myself and don't use magic DNS. I just set the the headscale DNS to my pihole. I handle all custom A records in pihole. For all devices that are permanently at home I've disabled the DNS feature of tailscale (at least on GUI clients, servers may be different). The reason for this is that tailscale has no working support for split DNS from my testing and I still want to reach device within my home network, that aren't on the tailnet via hostname (eg. fritz.box etc.). To accomplish that I've configured my pihole as upstream DNS in my fritz-box.

Thus this basically limits the use of headscale to just the bare basics of setting up the tunels, with a seperate ip range and "static" or at least sticky IPs for each client.

References:

// bonus file with some hints on setting up some of the headscale acls.
// only contains some more tricky parts relevant to the shared stacks.
{
"Hosts": {
# ips have been changed and may not be consistent thoughout the gist
"docker-home-traefik.my-user.mydomain": "100.64.0.95/32",
"exit--docker-home.my-user.mydomain": "100.64.0.99/0"
},
"groups": {
"group:owned": ["myuser@mydomain", "myuser2@mydomain"], // also includes server users
"group:owned-client": ["myuser@mydomain"]
},
"acls": [
// All my owned devices can see each other (omit this or adjust it to other groups)
{ "action": "accept", "src": ["group:owned"], "dst": ["group:owned:*"] },
// All my own devices can access internal servers
{
"action": "accept",
"src": ["group:owned"],
"dst": [
"docker-home-traefik.my-user.mydomain:*",
]
},
// All my own clients can use exits nodes (and access the internet though them)
{
"action": "accept",
"src": ["group:owned-clients"],
"dst": [
"autogroup:internet:*",
"exit--docker-home.my-user.mydomain:*"
]
}
]
}
version: '3'
services:
# just a client for accessing other other things on the tailnet from docker-containers sharing the containers networking
tailscale:
image: tailscale/tailscale:stable
container_name: lan-proxy-tailscale
hostname: docker-home-lan-client
volumes:
- tailscale-data:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
security_opt:
- no-new-privileges=true
cap_drop:
- ALL
cap_add:
- NET_ADMIN
- NET_RAW
environment:
- TS_STATE_DIR=/var/lib/tailscale
- TS_EXTRA_ARGS=--login-server=https://INSERT_HEADSCALE_ADDR --shields-up
- TS_NO_LOGS_NO_SUPPORT=true
# outgoing traffic to the tailnet seams to only work without userspace. I wasn't able to get any of the proxies to work.
- TS_USERSPACE=false
- TS_AUTHKEY=${TS_AUTHKEY}
networks:
# required for the proxies below (sharing this containers networking)
- web
restart: unless-stopped
proxmox-proxy:
build: ../../build/simple-reverse-proxy
container_name: proxmox-proxy
environment:
# tailnet IP for pve proxmox host
- NGINX_REMOTE_URL=https://100.64.0.94:8006/
# adjust port for additonal proxies using the same tailnet client in this stack
- NGINX_PORT=81
labels:
- "traefik.enable=true"
- "traefik.port=81"
- "traefik.frontend.rule=Host:my-awsome-proxmox-host.mydomain"
- "traefik.frontend.whiteList.sourceRange=127.0.0.1/32"
network_mode: service:tailscale
restart: unless-stopped
# to ensure tailnet is not reachable from docker-network "web"
web-network-ping-to-tailnet-fails:
image: willfarrell/ping
environment:
HOSTNAME: 100.64.0.94
TIMEOUT: 1
networks:
- web
profiles: [debug]
# would also work running in a seperate compose stack
router-proxy:
build: ../../build/simple-reverse-proxy
container_name: router-proxy
environment:
- NGINX_REMOTE_URL=http://fritz.box/
labels:
- "traefik.enable=true"
- "traefik.frontend.rule=Host:my-router.mydomain"
# only allow access from traefiks sidecar style tailnet client
- "traefik.frontend.whiteList.sourceRange=127.0.0.1/32"
networks:
- web
restart: unless-stopped
volumes:
tailscale-data:
networks:
web:
external: true
# works standalone
# litterally just an exit node so I can drop into some network using my phone or sth similar.
version: '3'
services:
tailscale:
image: tailscale/tailscale:stable
container_name: tailscale
hostname: docker-home
volumes:
- tailscale-data:/var/lib/tailscale
network_mode: host
security_opt:
- no-new-privileges=true
cap_drop:
- ALL
cap_add:
- NET_ADMIN
- NET_RAW
environment:
- TS_STATE_DIR=/var/lib/tailscale
- TS_EXTRA_ARGS=--login-server=https://INSERT_HEADSCALE_ADDR --advertise-exit-node
- TS_ROUTES=0.0.0.0/0,::/0
- TS_ACCEPT_DNS=true
- TS_NO_LOGS_NO_SUPPORT=true
- TS_USERSPACE=true
- TS_AUTHKEY=${TS_AUTHKEY}
restart: unless-stopped
volumes:
tailscale-data:
# works standalone
# About: Trying to mirror the behaviour of tailscale being installed as a service on the host without doing so.
# Motivation: Simplified rollout using docker rather than a manual installation as well as a reduced attack surface on the host from 3rd party software (in this case tailscale) due to the use of container features with limited access.
# this is not perfect - it's complaining a lot in it's logs
# but it had enough permissions / access allow me to ping another device on my tailnet from the host while running.
version: '3'
services:
tailscale4the-host:
image: tailscale/tailscale:stable
container_name: tailscale4the-host
volumes:
- tailscale-data:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
network_mode: host
security_opt:
- no-new-privileges=true
cap_drop:
- ALL
cap_add:
- NET_ADMIN
- NET_RAW
environment:
# adjust hostname as required
- TS_HOSTNAME=${HOSTNAME}-4the-host
- TS_STATE_DIR=/var/lib/tailscale
- TS_EXTRA_ARGS=--login-server=https://INSERT_HEADSCALE_ADDR
- TS_ACCEPT_DNS=true
- TS_NO_LOGS_NO_SUPPORT=true
- TS_USERSPACE=false
- TS_AUTHKEY=${TS_AUTHKEY}
restart: unless-stopped
volumes:
tailscale-data:
# traefik reachable from tailnet (and accessed from localhost if requested from sidecar-style tailnet-client)
version: "3"
#name: traefik
services:
traefik:
image: traefik:1.7-alpine
container_name: traefik
expose:
- 8080
ports:
- 80:80
- 443:443
#- 8080:8080 # Management UI
dns: # DNS problems would cause certificates to expire
- 1.0.0.1
- 1.1.1.1
- 8.8.8.8
- 8.8.4.4
volumes:
# ./traefik.toml refers to traefik-with-tailscale_traefik.toml
- ./traefik.toml:/etc/traefik/traefik.toml:ro
- data:/etc/traefik/acme
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- web
restart: unless-stopped
# logging:
# driver: "local"
# options:
# max-size: 10m
deploy:
resources:
limits:
# this might cause the container to crash sometimes. Needs further looking into though
memory: 700M
ts-traefik:
image: tailscale/tailscale:stable
container_name: ts-traefik
security_opt:
- no-new-privileges=true
cap_drop:
- ALL
cap_add:
- NET_ADMIN
- NET_RAW
environment:
# adjust hostname as required
- TS_HOSTNAME=${HOSTNAME}-traefik
- TS_STATE_DIR=/var/lib/tailscale
- TS_EXTRA_ARGS=--login-server=https://INSERT_HEADSCALE_ADDR
- TS_NO_LOGS_NO_SUPPORT=true
- TS_USERSPACE=true
- TS_AUTHKEY=${TS_AUTHKEY}
volumes:
- traefik_tailscale-data:/var/lib/tailscale
network_mode: service:traefik
restart: unless-stopped
# logging:
# driver: "local"
# options:
# max-size: 10m
deploy:
resources:
limits:
# this might cause the container to crash sometimes. Needs further looking into though
memory: 200M
volumes:
data:
traefik_tailscale-data:
external: true
networks:
web:
external: true
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[api]
[accessLog]
[docker]
exposedByDefault = false
network = "web"
[metrics]
[metrics.prometheus]
[acme]
email = "INSERT_YOUR_EMAIL_ADDR"
storage = "/etc/traefik/acme/acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment