Code

Local hosting behind a VPS

I want to replicate the architecture used by autistici/inventati, which hosts services on machines that are not directly accessible from the internet, and external traffic is routed to them from a VPS using a VPN.

The architecture includes:

The wireguard container on the VPS looks like:

services:
  vpn:
    image: linuxserver/wireguard
    cap_add: 
      - NET_ADMIN
      - SYS_MODULE
    restart: unless-stopped
    sysctls:
      net.ipv4.ip_forward: 1
    ports:
      - "80:80"
      - "443:443"
      - "51820:51820/udp"
    volumes:
      - ./wg-config/wg0.conf:/config/wg_confs/wg0.conf
    environment:
      - PUID=1000
      - PGID=1000

The wg0.conf file has the following contents:

[Interface]
Address = 10.0.0.1/24
PrivateKey = SERVER_PRIVATE_KEY
ListenPort = 51820

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp   = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

On the home server, the container has the following contents:

 services:
  vpn:
    image: linuxserver/wireguard
    cap_add: 
      - NET_ADMIN
      - SYS_MODULE
    restart: unless-stopped
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    volumes:
      - ./wg-config/wg0.conf:/config/wg_confs/wg0.conf
      - /lib/modules:/lib/modules
    healthcheck:
      test: ["CMD-SHELL", "ping -c 1 -w 5 10.0.0.1 || exit 1"]
      interval: 30s

The wg0.conf file has the following contents:

[Interface]
Address = 10.0.0.2/24
PrivateKey = APP_PRIVATE_KEY
PostUp   = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = 1.2.3.4:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

Give a different IP address to each service that you add.

The container for the app running on the home server needs to be configured with network_mode: "service:vpn".

The private keys for the two parties have to be generated with:

wg genkey

The public keys can then be generated with:

echo PRIVATE_KEY | wg pubkey

Adding apps to the VPS

Each app must be added to the configuration of wireguard container running on the VPS:

[Peer]
PublicKey = APP_PUBLIC_KEY
AllowedIPs = 10.0.0.2/32

To add a new peer to the VPS without restarting the wireguard container you can use:

docker compose exec vpn wg set wg0 peer APP_PUBLIC_KEY allowed-ips 10.0.0.2/32

The reverse proxy on the VPS looks like this:

services:
  reverse-proxy:
    image: caddy:latest
    restart: unless-stopped
    network_mode: "service:vpn"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - ./caddy/data:/data
      - ./caddy/config:/config

The Caddyfile configuration should contain an entry for each app that you want to expose:

website.example.com {
	reverse_proxy 10.0.0.2:80
}

If your app needs to receive traffic on ports other than 80 and 443, you need to:

PostUp   = iptables -t nat -A PREROUTING -p tcp --dport 22 -j DNAT --to-destination 10.0.0.2:22
PostDown = iptables -t nat -D PREROUTING -p tcp --dport 22 -j DNAT --to-destination 10.0.0.2:22

Open questions: