314 lines
8.0 KiB
Markdown
314 lines
8.0 KiB
Markdown
# 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.<your-domain>` → Authelia login → Traefik dashboard
|
||
* `https://auth.<your-domain>` → Authelia portal
|
||
* `https://example.com` → static site
|
||
* `https://example.com/wp` → WordPress installer
|
||
* `https://git.<your-domain>` → Authelia login → Forgejo setup
|
||
* `https://cloud.<your-domain>` → 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/<name>/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.<name>.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.<name>-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: "<argon2id hash>"
|
||
```
|
||
|
||
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/<site-name>
|
||
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/<new-site>/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.
|