A homelab is great and all, until something takes out your internet, or power, or anything else that it depends on. As well as this, there’s quite high latency when trying to access it from another country - as the data has to travel all the way home, but in this case it’ll join Oracle’s much faster backbone.
To solve this, I’m going to be using distributed architecture, with Traefik and Tailscale at it’s core
In order to do this, you’re gonna first need to have Traefik and Tailscale setup. For Traefik I would suggest this guide.
Once you’ve done so, you’re gonna need some ingress nodes. I chose to use the E2.1 Micro VMs on Oracle Cloud’s Free Tier, as they are free and quite generous.
Routing the proxy network into the tailnet
Run the following script, changing the IP range to ensure it does not conflict with any other subnets or networks on other hosts in the tailnet.
1#!/bin/bash
2
3# --- Configuration ---
4# Set your desired subnet here
5PROXY_SUBNET="10.20.0.0/16" # TODO
6NETWORK_NAME="proxy"
7
8# --- 1. Enable IP Forwarding ---
9# This is a requirement for Tailscale subnet routing to work.
10echo "Enabling IP forwarding..."
11echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
12echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
13sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
14
15# --- 2. Create Docker Network ---
16# Check if the network already exists to avoid errors
17if [ "$(docker network ls | grep -w $NETWORK_NAME)" ]; then
18 echo "Docker network '$NETWORK_NAME' already exists. Skipping creation."
19else
20 echo "Creating Docker network '$NETWORK_NAME' with subnet $PROXY_SUBNET..."
21 sudo docker network create \
22 --driver bridge \
23 --subnet "$PROXY_SUBNET" \
24 "$NETWORK_NAME"
25fi
26
27# --- 3. Advertise Route via Tailscale ---
28echo "Advertising $PROXY_SUBNET via Tailscale..."
29# Note: This command replaces currently advertised routes.
30# If you have existing routes, append them here (e.g., --advertise-routes=10.0.0.0/24,$PROXY_SUBNET)
31sudo tailscale set --advertise-routes="$PROXY_SUBNET"
32sudo tailscale up
33
34echo "--------------------------------------------------------"
35echo "Done!"
36echo "1. Verify the network: docker network inspect $NETWORK_NAME"
37echo "2. IMPORTANT: You must manually approve this route in the"
38echo " Tailscale Admin Console (Machines -> [This Machine] -> Edit Route Settings)"
39echo " unless you have auto-approvers enabled."
40echo "--------------------------------------------------------"
After this, there will now be a network proxy, accessible via your tailnet, and other nodes on it. I would suggest tagging your servers and using ACLs to ensure only authorized nodes can directly access the container.
Note: In some cases, Docker may not know how to route the traffic back to the node which requested it, so the following script needs to be run on the same node:
1sudo iptables -t raw -I PREROUTING -i tailscale0 -j ACCEPT
2sudo iptables -I DOCKER-USER -i tailscale0 -j ACCEPT
3sudo iptables -I DOCKER-USER -o tailscale0 -j ACCEPT
4sudo iptables -t nat -I POSTROUTING -s 100.64.0.0/10 -j MASQUERADE
5sudo netfilter-persistent save
Setting up Traefik-Kop
This assumes you already have Redis setup in the architecture you wish - ideally a Redis sentinel or other alternative. You need one instance of traefik-kop on all nodes running services. The docker socket has been exposed to the container, in RO mode, to prevent malicious use. You can also use a docker socket proxy if you want.
1services:
2 traefik-kop:
3 image: ghcr.io/jittering/traefik-kop:latest
4 container_name: traefik-kop
5 restart: unless-stopped
6 volumes:
7 - /var/run/docker.sock:/var/run/docker.sock:ro
8 environment:
9 REDIS_ADDR: "redis:6379" # TODO
10 TRAEFIK_REDIS_ROOT_KEY: traefik
11 SKIP_REPLACE: 1
12 KOP_HOSTNAME: "node-1"
13 DOCKER_CONFIG: |
14 ---
15 docker:
16 network: proxy
17 exposedByDefault: false
You need to change the REDIS_ADDR env variable to your Redis instance.
Configuring Traefik
You must use a DNS-01 challenge, not a HTTP challenge provider, as the HTTP challenge will not work in this situation.
ForwardAuth
If you have any ForwardAuth entries, or anything similar, docker’s native DNS resolution will not function. This means you need to set a static IP for that container and specify the specific IP for that.
For example,
1http:
2 middlewares:
3 # https://github.com/goauthentik/authentik/issues/2366
4 middlewares-authentik:
5 forwardAuth:
6 address: "http://10.20.12.1:9000/outpost.goauthentik.io/auth/traefik" # Static IP used
7 trustForwardHeader: true
8 authResponseHeaders:
9 - X-authentik-username
10 - X-authentik-groups
11 - X-authentik-email
12 - X-authentik-name
13 - X-authentik-uid
14 - X-authentik-jwt
15 - X-authentik-meta-jwks
16 - X-authentik-meta-outpost
17 - X-authentik-meta-provider
18 - X-authentik-meta-app
19 - X-authentik-meta-version
And, how to set it in Docker Compose…
1 networks:
2 proxy:
3 ipv4_address: 10.20.12.1
Synchronising Config
I chose to use SyncThing to synchronise my config files across all nodes. This had all the benefits of replication, while removing the overhead and low latency required for synchronous replication.
compose.yaml
1services:
2 syncthing:
3 image: lscr.io/linuxserver/syncthing:latest
4 container_name: syncthing
5 hostname: node-1 # The name that will appear in the Syncthing UI
6 environment:
7 - PUID=0 # Run 'id -u' on your host to get this
8 - PGID=0 # Run 'id -g' on your host to get this
9 - TZ=Etc/UTC
10 volumes:
11 - /lab/nodes/ultimate/syncthing:/config
12 - /lab:/lab
13 ports:
14 - 8384:8384 # Web GUI
15 - 22000:22000/tcp # File transfer (TCP)
16 - 22000:22000/udp # File transfer (QUIC)
17 - 21027:21027/udp # Local discovery
18 restart: unless-stopped
Head to the WebUI at https://SERVER_IP:8384 and configure syncthing to synchronise the neccessary folders between all nodes.
Redis Provider
You now need to configure Traefik to use the Redis provider, outlined in the Traefik docs here.
Traefik Start-up
You should now be able to start up Traefik, on all ingress nodes, with the Redis provider configured.
DNS Configuration
For now, I’m using Round-Robin DNS, by just specifying multiple A records with the IP of each VPS. However, in the future, I plan to move towards using Geo-Based DNS and automatic failover of unhealthy nodes.
Final Thoughts
Overall, this works quite well, but stay tuned to see how to self-host nameservers with PowerDNS for Geo Scaling and Redundancy.
Loading comments...