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.