# VHostLoom A Docker Hosting Framework: Traefik + Authelia + Modular Sites This repository provides a reusable framework for running multiple virtual-hosted web services on a single server using: - [Traefik](https://traefik.io/) as a reverse proxy and TLS terminator - [Authelia](https://www.authelia.com/) as an SSO / authentication gateway - Docker Compose for container orchestration - Modular per-site stacks with bind-mounted volumes The design goals are: > **Add or change sites without constantly editing core proxy config** > and keep application data available natively on the host filesystem. Under the view that hacking is inevitable, it seemed to me that making it so a hacker has to hack each container separately makes the system less brittle and less exposed to threats like ransomware. With modest backup discipline, this system should permit porting or getting back up expeditiously. --- ## Features - **Core proxy stack** (`core-proxy/`) with Traefik and Authelia - Let’s Encrypt HTTP-01 certificates - Traefik dashboard at `https://traefik.example.com`, protected by Authelia - Authelia at `https://auth.example.com` for login - **Modular site stacks** under `sites/`: - `static-site`: static file hosting at `https://example.com` - `wordpress-site`: WordPress at `https://example.com/wp` - `forgejo`: Forgejo (Git hosting) + CI runner at `https://git.example.com` - `nextcloud`: Nextcloud cloud storage at `https://cloud.example.com` - **Security model** - Default-deny firewall template (`firewall/nftables.conf.example`) - Only 80/443 exposed publicly (and optionally a few others) - Authelia used to put an extra auth layer in front of sensitive apps - Easy integration with ZeroTier or other VPNs for “VPN-only” services - **Host-friendly data layout** - Each site’s data lives in bind-mounted directories under `sites/` - Easy to back up, rsync, or inspect without entering containers --- ## Prerequisites - Linux host (e.g., Ubuntu Server 22.04+) - Docker and Docker Compose plugin installed - A public IP address (static or effectively static) - Control over DNS records for your domains For HTTPS, you’ll need DNS `A` (and/or `AAAA`) records pointing to your server: - `example.com` → server IP - `auth.example.com` → server IP - `traefik.example.com` → server IP - `git.example.com` → server IP - `cloud.example.com` → server IP --- ## Quick Start 1. **Clone the repo** ```bash git clone https://example.com/your/hosting-framework.git cd hosting-framework 2. **Create shared Docker network** ```bash docker network create traefik_proxy ``` 3. **Prepare Traefik ACME storage** ```bash cd core-proxy mkdir -p traefik/dynamic touch traefik/acme.json chmod 600 traefik/acme.json ``` 4. **Edit core config** * `core-proxy/docker-compose.yml` * Change `admin@example.com` to a real email * Adjust domains in Traefik labels for your use case * `core-proxy/traefik/dynamic/authelia.yml` * Set `auth.example.com` (or equivalent) * `core-proxy/authelia/configuration.yml` * Change `example.com`, secrets, etc. * `core-proxy/authelia/users_database.yml` * Generate a password hash: ```bash docker run --rm authelia/authelia:latest authelia hash-password 'yourpassword' ``` Paste the hash into `password:`. 5. **Start the core stack** ```bash cd core-proxy docker compose up -d ``` 6. **Configure DNS** Create DNS records pointing your domains to the server’s public IP. Let’s Encrypt will fail if DNS is wrong or not propagated. 7. **Bring up example sites** ```bash # Static site cd ../sites/static-site docker compose up -d # WordPress cd ../wordpress-site docker compose up -d # Forgejo cd ../forgejo docker compose up -d # Nextcloud cd ../nextcloud docker compose up -d ``` 8. **Test** * `https://traefik.` → Authelia login → Traefik dashboard * `https://auth.` → Authelia portal * `https://example.com` → static site * `https://example.com/wp` → WordPress installer * `https://git.` → Authelia login → Forgejo setup * `https://cloud.` → Authelia login → Nextcloud setup --- ## Rationale & Design ### Separation of concerns * **Core proxy** (Traefik + Authelia) is stable and rarely changed. * **Sites** live in separate directories with their own `docker-compose.yml`. * **Firewall** is independent of Docker and enforces network boundaries. This makes it easy to: * Add new virtual hosts (just add a new `sites//docker-compose.yml`) * Share the framework without embedding secrets * Back up only what matters (`sites/**`, Authelia DB, maybe Traefik `acme.json`) ### Minimal coupling The only shared assumptions between stacks: * A Docker network named `traefik_proxy` * Authelia’s forward-auth middleware named `authelia-auth@file` * Traefik’s Let’s Encrypt resolver named `letsencrypt` Everything else is per-site. --- ## Configuration Details ### Traefik labels Each service defines its routing behavior entirely via labels: * Match host and path: `traefik.http.routers..rule=Host(`example.com`) && PathPrefix(`/wp`)` * Bind to entrypoint: `...entrypoints=web` or `websecure` * Enable HTTPS / certificates: `...tls.certresolver=letsencrypt` * HTTP→HTTPS redirect using a middleware: * Define middleware: `traefik.http.middlewares.foo-https-redirect.redirectscheme.scheme=https` * Attach it to an HTTP router: `traefik.http.routers.foo-http.middlewares=foo-https-redirect` ### Authelia protection To require login via Authelia before an app: ```yaml labels: - "traefik.http.routers.-https.middlewares=authelia-auth@file" ``` Remove or comment this label to make a site public. ### Authelia users Defined in `core-proxy/authelia/users_database.yml`: ```yaml users: admin: displayname: "Admin User" email: "admin@example.com" groups: [admins] password: "" ``` You can use groups and more complex access_control rules if desired; the default config in this repo simply treats any authenticated user as allowed. --- ## Security & Firewall The `firewall/nftables.conf.example` file contains a default-deny firewall with: * Loopback and established connections allowed * ICMP allowed (optional but recommended) * SSH allowed only over a VPN interface (e.g., ZeroTier) * 80/443 open for Traefik * Example ports restricted to the VPN interface You can adapt it and enable with: ```bash sudo cp firewall/nftables.conf.example /etc/nftables.conf sudo nft -f /etc/nftables.conf sudo systemctl enable nftables ``` Always test SSH access before locking down too far. --- ## Maintenance ### Updating containers For each stack: ```bash cd core-proxy docker compose pull docker compose up -d cd ../sites/ docker compose pull docker compose up -d ``` ### Backups At minimum, back up: * `core-proxy/traefik/acme.json` (certificates) * `core-proxy/authelia/` (configuration + DB) * `sites/**/` data directories: * `sites/static-site/html/` * `sites/wordpress-site/db/` and `wp/` * `sites/forgejo/data/`, `db/`, `runner/` * `sites/nextcloud/nextcloud/`, `db/`, `redis/` Use `rsync`, `borg`, `restic`, or your favorite solution. ### Logs * Traefik logs: stdout (use `docker logs traefik` or a log collector) * Authelia logs: stdout + `notification.log` (if configured) * Site logs: as exposed via each container --- ## Extending the Framework To add a new site: 1. Create `sites//docker-compose.yml`. 2. Attach it to `traefik_proxy`. 3. Add Traefik labels for hostnames, HTTPS, redirect, and optionally Authelia. 4. Bind volumes for data so they live on the host. 5. `docker compose up -d` in that directory. 6. Add DNS records for the new hostname(s). You never need to edit the core proxy configuration for new sites. --- ## License This software is licensed under the MIT license. ## Disclaimer Beyond the MIT license statement, this software was produced by iterative prompting of OpenAI's GPT 5.1 LLM. ## Notes For myself, I only want the core-proxy items on the boot drive. The 'sites' directory I am sym-linking from a mount that has much more disk space. Also, Docker containers can be large. Consider sym-linking the Docker image directory to a larger disk. ## Secure Private Access (WireGuard Module) Some services in VHostLoom are **not public-facing** by design: * Stable Diffusion interfaces * Llamafile demos & research endpoints * Ollama/vLLM * Internal dashboards * Forgejo SSH * Anything experimental or non-web To protect these services, VHostLoom supports an optional **WireGuard VPN module** that restricts private-service ports so they are reachable **only from authenticated VPN clients**, and *never* from the public Internet. WireGuard may be used instead of, or alongside, ZeroTier. --- ### Why WireGuard? WireGuard is: * extremely fast (kernel-level cryptography) * small and security-audited * widely supported across platforms * perfect for “private access only” services VHostLoom’s WireGuard module: * Exposes WireGuard only on WAN (`udp/51820`) * Creates a private VPN subnet (`10.20.0.0/24`) * Restricts critical ports to the WireGuard interface * Leaves Traefik-managed public services untouched --- ## Installing WireGuard Install on the server: ```bash sudo apt install wireguard wireguard-tools ``` Copy in the example config: ```bash sudo mkdir -p /etc/wireguard sudo cp wireguard/wg0.conf.example /etc/wireguard/wg0.conf sudo chmod 600 /etc/wireguard/wg0.conf ``` Generate server keypair: ```bash wg genkey | tee server.key | wg pubkey > server.pub ``` Add the server private key to: ```ini PrivateKey = ``` --- ## Starting WireGuard ```bash sudo systemctl enable wg-quick@wg0 sudo systemctl start wg-quick@wg0 ``` Verify: ```bash ip addr show wg0 wg show ``` --- ## Firewall Configuration Choose one: * **`firewall/nftables-wireguard.conf.example`** — basic WireGuard + Traefik + private-services setup * **`firewall/nftables-wireguard-zt.conf.example`** — *combined* WireGuard + ZeroTier rules Install a firewall: ```bash sudo cp firewall/nftables-wireguard.conf.example /etc/nftables.conf sudo nft -f /etc/nftables.conf sudo systemctl enable nftables ``` This: * Allows public web (80/443) * Accepts WireGuard on WAN (UDP 51820) * Allows private services (**only** via wg0) * Default-denies everything else --- ## Adding a New WireGuard Client Use the helper script: ```bash wireguard/gen-wg-peer.sh clientname ``` It will generate: * `clientname.key` * `clientname.pub` * `clientname.conf` (the client config) Add the peer entry to `/etc/wireguard/wg0.conf` **automatically**. Then send the generated `.conf` file to your device. --- ## Client Template Clients use a simple config: ```ini [Interface] PrivateKey = Address = 10.20.0.X/32 DNS = 1.1.1.1 [Peer] PublicKey = Endpoint = :51820 AllowedIPs = 10.20.0.0/24 PersistentKeepalive = 25 ``` Import into: * WireGuard app (iOS/Android) * `wg-quick` * Desktop clients --- ## Coexisting with ZeroTier If you want both overlay networks: * ZeroTier mesh connections * WireGuard direct VPN * Shared access control for private ports Use: ``` firewall/nftables-wireguard-zt.conf.example ``` It grants private-port access to **either**: * WireGuard (`wg0`) * ZeroTier (`zt*`) You can also restrict different services to different VPNs. --- ## Summary WireGuard gives VHostLoom: * Strong isolation of private services * Minimal attack surface * Predictable firewalling * Fast, encrypted access It integrates fully with the project’s model: * **Traefik/Authelia** for public-facing authenticated web * **WireGuard** (and/or ZeroTier) for *non-web private services*