added nginx to traefik migration changes and docs
This commit is contained in:
@@ -13,7 +13,9 @@ Documentation for a self-hosted homelab running entirely on Docker Compose. This
|
||||
- [Storage Health](Storage-Health.md)
|
||||
- [Lessons Learned](Lessons-Learned.md)
|
||||
- [AI Coding Agent](AI-Agent.md)
|
||||
- [Reverse Proxy](Reverse-Proxy.md)
|
||||
- [Reverse Proxy](Reverse-Proxy.md) (historical — Nginx Proxy Manager)
|
||||
- [Traefik](Traefik.md) (current reverse proxy)
|
||||
- [Vaultwarden](Vaultwarden.md) (password manager)
|
||||
- [Semaphore](Semaphore.md)
|
||||
- [DockScope](DockScope.md)
|
||||
- [Ansible Playbooks](AnsiblePlaybooks.md)
|
||||
@@ -30,7 +32,7 @@ Documentation for a self-hosted homelab running entirely on Docker Compose. This
|
||||
| **Orchestration** | Docker Compose, one stack per service |
|
||||
| **Management** | Portainer, **DockScope** (3D visual dashboard) |
|
||||
| **Monitoring** | Prometheus + Grafana + cAdvisor + node-exporter + **Loki + Alloy** (centralized logs) |
|
||||
| **Network** | Pi-hole (DNS filtering), Samba (file sharing), nginx (proxy managing) |
|
||||
| **Network** | Pi-hole (DNS filtering), Samba (file sharing), **Traefik** (reverse proxy, config-as-code) |
|
||||
| **Automation** | n8n + **Semaphore** (Ansible Web UI) + **Ansible** (update playbooks) || **Media** | Plex + Sonarr/Radarr/Prowlarr + qBittorrent |
|
||||
| **Disk health** | Hard Disk Sentinel |
|
||||
| **AI Agent** | OpenHands + Ollama |
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Reverse Proxy
|
||||
# Reverse Proxy (historical — Nginx Proxy Manager)
|
||||
|
||||
[Nginx Proxy Manager](https://nginxproxymanager.com/) sits in front of every service on the LAN, replacing `http://<ip>:<port>` with friendly `<service>.home` names and trusted local HTTPS. Runs as its own Compose stack under `nginx-proxy-manager/`.
|
||||
> **Note:** This homelab has migrated from Nginx Proxy Manager to **Traefik**. See [Traefik](Traefik.md) for the current reverse proxy setup and migration rationale. This page documents the original NPM setup as a reference.
|
||||
|
||||
[Nginx Proxy Manager](https://nginxproxymanager.com/) sat in front of every service on the LAN, replacing `http://<ip>:<port>` with friendly `<service>.home` names and trusted local HTTPS. Ran as its own Compose stack under `nginx-proxy-manager/`.
|
||||
|
||||

|
||||
|
||||
|
||||
129
docs/Traefik.md
Normal file
129
docs/Traefik.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Traefik — Reverse Proxy
|
||||
|
||||
Traefik replaced Nginx Proxy Manager as the homelab's reverse proxy, giving every service a `<service>.home.server` hostname with locally-trusted HTTPS — now configured entirely as code instead of a web UI.
|
||||
|
||||
See the [Traefik README](../docker-compose/traefik/README.md) for the full file-by-file breakdown and how to add new services.
|
||||
|
||||
## Why the migration
|
||||
|
||||
Two reasons, both rooted in the same observation: NPM works, but it doesn't scale with the rest of the infrastructure-as-code approach.
|
||||
|
||||
**Everything as code.** NPM's configuration lives entirely in its web UI — proxy hosts, SSL certs, custom locations, access rules. None of it is version-controlled or reproducible. If the data volume is lost, you're rebuilding every proxy host manually. Traefik's routes, middlewares, and TLS config are all YAML files in this repo — diffable, reviewable, reproducible with `docker compose up -d`.
|
||||
|
||||
**Docker daemon auto-discovery.** NPM requires manually adding each proxy host through its UI — domain, target IP/port, SSL cert, save. Every new service means another round-trip through a different tool. Traefik watches the Docker daemon directly and picks up containers with `traefik.enable=true` automatically. Adding a new service to the reverse proxy is a two-line label addition to that service's compose file.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Traefik v3.7 │
|
||||
│ │
|
||||
│ Entrypoints: :80 (HTTP) → redirect to HTTPS │
|
||||
│ :443 (HTTPS) → route to service │
|
||||
│ │
|
||||
│ Providers: │
|
||||
│ Docker daemon → auto-discovers labeled conns │
|
||||
│ File provider → watches dynamic/ for YAML │
|
||||
│ │
|
||||
│ TLS: mkcert local CA, single cert for all hosts │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
│ │ │
|
||||
┌────┴────┐ ┌─────┴─────┐ ┌────┴────┐
|
||||
│ Docker │ │ File │ │ certs/ │
|
||||
│ labels │ │ dynamic/ │ │ mkcert │
|
||||
│ per-svc │ │ routes │ │ CA │
|
||||
└─────────┘ └───────────┘ └─────────┘
|
||||
```
|
||||
|
||||
### Static config
|
||||
|
||||
Passed as command-line flags in `compose.yaml`:
|
||||
|
||||
- **Docker provider** — watches for labeled containers on `web_net`
|
||||
- **File provider** — hot-reloads `dynamic/` directory
|
||||
- **Entrypoints** — `:80` and `:443`
|
||||
- **Ping** — health check endpoint
|
||||
- **API dashboard** — enabled, secured via its own HTTPS route
|
||||
|
||||
### Dynamic config (`dynamic/`)
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `tls.yaml` | Default TLS cert store pointing at the mkcert certificate |
|
||||
| `middlewares.yaml` | `redirect-to-https` (301) + `security-headers` (HSTS, XSS, nosniff, SAMEORIGIN) |
|
||||
| `pihole.yaml` | Pi-hole route (host-network, can't use Docker labels) |
|
||||
| `plex.yml` | Plex route (external host `192.168.100.6:32400`) |
|
||||
| `sentinel.yml` | Sentinel route (external host `192.168.100.6:8088`) |
|
||||
| `dockscope-auth.yml` | BasicAuth middleware for DockScope (Docker socket access) |
|
||||
|
||||
### Services fronted by Traefik
|
||||
|
||||
| Service | Hostname | Method |
|
||||
|---|---|---|
|
||||
| Prometheus | `prometheus.home.server` | Docker labels |
|
||||
| Grafana | `grafana.home.server` | Docker labels |
|
||||
| cAdvisor | `cadvisor.home.server` | Docker labels |
|
||||
| Loki | `loki.home.server` | Docker labels |
|
||||
| Alloy | `alloy.home.server` | Docker labels |
|
||||
| Pi-hole | `pihole.home.server` | File provider |
|
||||
| Portainer | `portainer.home.server` | Docker labels |
|
||||
| n8n | `n8n.home.server` | Docker labels |
|
||||
| Filebrowser | `filebrowser.home.server` | Docker labels |
|
||||
| DockScope | `dockscope.home.server` | Docker labels + basicAuth |
|
||||
| Semaphore | `semaphore.home.server` | Docker labels |
|
||||
| MinIO | `minio.home.server` | Docker labels |
|
||||
| Plex | `plex.home.server` | File provider (external) |
|
||||
| Sentinel | `sentinel.home.server` | File provider (external) |
|
||||
| Vaultwarden | `vaultwarden.home.server` | Docker labels |
|
||||
| Traefik Dashboard | `traefik.home.server` | Docker labels |
|
||||
|
||||
## TLS certificates
|
||||
|
||||
Same approach as the NPM era — [mkcert](https://github.com/FiloSottile/mkcert) creates a local CA trusted on each device, then signs a single certificate covering all `*.home.server` hostnames. The cert and key live in `traefik/certs/` and are mounted read-only.
|
||||
|
||||
Traefik's file-based TLS store (`dynamic/tls.yaml`) applies this cert as the default for all routes — no per-host certificate assignment needed. Adding a new `.home.server` hostname automatically gets the right cert.
|
||||
|
||||
## Network
|
||||
|
||||
All Traefik-fronted services join the `web_net` external Docker network:
|
||||
|
||||
```bash
|
||||
docker network create web_net
|
||||
```
|
||||
|
||||
Services that can't join `web_net` (host-network containers like Pi-hole, or external machines like Plex) are routed via the file provider with explicit IP:port targets.
|
||||
|
||||
## Adding a new service
|
||||
|
||||
1. **Docker container on `web_net`** — add Traefik labels to its compose file
|
||||
2. **Host-network or external** — create a YAML file in `dynamic/`
|
||||
3. **Pi-hole** — add DNS record for `<service>.home.server`
|
||||
|
||||
No restart required — Traefik auto-discovers Docker label changes and hot-reloads file provider changes.
|
||||
|
||||
## Port 80/443 conflict with Pi-hole
|
||||
|
||||
Pi-hole runs in `network_mode: host`, which means it binds directly to ports 80 and 443 for its web UI — the same ports Traefik needs. Resolved by moving Pi-hole's web interface to port 8082 via the FTL environment variable:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
FTLCONF_webserver_port: '8082o,[::]:8082o'
|
||||
```
|
||||
|
||||
Pi-hole's DNS service (port 53) is untouched — only its web UI moved. See [Reverse Proxy](Reverse-Proxy.md) for the full history of this port conflict.
|
||||
|
||||
## Security notes
|
||||
|
||||
- Dashboard is LAN-only via Pi-hole DNS (not exposed publicly)
|
||||
- DockScope has basicAuth in addition to HTTPS (Docker socket access)
|
||||
- All HTTP → HTTPS redirects are permanent (301)
|
||||
- Security headers: HSTS (1 year, includeSubdomains, preload), XSS filter, content-type nosniff, SAMEORIGIN framing
|
||||
|
||||
## Lessons learned
|
||||
|
||||
- Traefik's Docker label approach means the reverse proxy configuration is co-located with each service — no separate tool or UI to maintain. This matches the "each service is a folder" philosophy the rest of the homelab already follows.
|
||||
- File-based routes in `dynamic/` are useful for services that can't use Docker labels (host-network containers, external machines). Keeping them as separate YAML files makes it obvious which services use which routing method.
|
||||
- The `web_net` external network is a hard requirement — Traefik can only route to containers on networks it's connected to. Forgetting to add a new service to `web_net` is the most common "why isn't my route working" issue.
|
||||
- Hot-reloading (file provider + Docker provider) means zero-downtime config changes — a major operational improvement over NPM, which required a container restart for some config changes.
|
||||
|
||||
[← Back to Home](Home.md)
|
||||
96
docs/Vaultwarden.md
Normal file
96
docs/Vaultwarden.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Vaultwarden
|
||||
|
||||
Self-hosted password manager — a lightweight, Rust-based implementation of the Bitwarden server API, giving full Bitwarden client compatibility (browser extensions, mobile apps, desktop apps) without Bitwarden's cloud dependency.
|
||||
|
||||
See [docker-compose/vaultwarden/](../docker-compose/vaultwarden/) for the deployment files.
|
||||
|
||||
## Why
|
||||
|
||||
After years of managing passwords across various tools, a self-hosted password manager was the missing piece. Vaultwarden provides:
|
||||
|
||||
- **Full Bitwarden compatibility** — works with the official Bitwarden browser extension, iOS/Android apps, and desktop apps. No proprietary client lock-in.
|
||||
- **Lightweight** — single container, SQLite database, minimal resource usage. Runs comfortably alongside the rest of the stack.
|
||||
- **Local data ownership** — passwords never leave the LAN unless you want them to (via Bitwarden's sync protocol for mobile access outside the home).
|
||||
- **Long passwords** — using Vaultwarden as the source of truth for 30+ character passwords across all services, replacing the short memorable passwords that were previously hardcoded in compose files.
|
||||
|
||||
## Setup
|
||||
|
||||
### Initial configuration
|
||||
|
||||
```bash
|
||||
cd docker-compose/vaultwarden
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Access the web UI at `vaultwarden.home.server` (via Traefik) or `http://localhost:8084` directly.
|
||||
|
||||
**First step:** create your admin account, then set `SIGNUPS_ALLOWED=false` in the compose file to prevent new registrations.
|
||||
|
||||
### Generating password hashes
|
||||
|
||||
Vaultwarden uses the same `admin_token` mechanism as Bitwarden for admin access. For the Traefik basicAuth middleware (DockScope), generate hashes with:
|
||||
|
||||
```bash
|
||||
htpasswd -nB <username>
|
||||
```
|
||||
|
||||
This produces bcrypt-hashed entries compatible with Traefik's basicAuth middleware.
|
||||
|
||||
## Integration with the homelab
|
||||
|
||||
### Traefik routing
|
||||
|
||||
Vaultwarden is fronted by Traefik with the standard label set:
|
||||
|
||||
- `vaultwarden.home.server` → HTTPS → container port 80
|
||||
- HTTP → HTTPS redirect
|
||||
- Security headers middleware
|
||||
|
||||
### Password management workflow
|
||||
|
||||
1. Store all service passwords in Vaultwarden (30+ character randomly generated)
|
||||
2. Reference passwords via `.env` files on the host (not in compose files)
|
||||
3. Compose files use `${VARIABLE}` references, never hardcoded values
|
||||
|
||||
This is the migration path from the previous approach of short, memorable passwords hardcoded in compose files — the Traefik migration was the catalyst for adopting proper secret management across the stack.
|
||||
|
||||
## Configuration
|
||||
|
||||
Key environment variables in `docker-compose.yml`:
|
||||
|
||||
| Variable | Value | Purpose |
|
||||
|---|---|---|
|
||||
| `WEBSOCKET_ENABLED` | `true` | Enable real-time sync with Bitwarden clients |
|
||||
| `SIGNUPS_ALLOWED` | `false` | Disable new account creation after initial setup |
|
||||
|
||||
Data is stored in `./vw-data/` (gitignored).
|
||||
|
||||
## Security notes
|
||||
|
||||
- Signups are disabled after initial account creation
|
||||
- The web UI is only accessible via HTTPS through Traefik (LAN-only via Pi-hole DNS)
|
||||
- Vaultwarden encrypts all vault data client-side before transmission — even if the server is compromised, passwords remain encrypted
|
||||
- The `vw-data/` directory contains the SQLite database and should be backed up regularly
|
||||
- Admin panel is accessible at `vaultwarden.home.server/admin` (requires admin token)
|
||||
|
||||
## Backups
|
||||
|
||||
The SQLite database in `vw-data/` should be backed up as part of the regular backup strategy. A simple approach:
|
||||
|
||||
```bash
|
||||
# stop the container, copy the data, restart
|
||||
docker compose stop vaultwarden
|
||||
cp -r vw-data/ /backups/vaultwarden-$(date +%Y%m%d)/
|
||||
docker compose start vaultwarden
|
||||
```
|
||||
|
||||
Or use the Bitwarden CLI to export a JSON/CSV backup from within the web UI.
|
||||
|
||||
## Lessons learned
|
||||
|
||||
- Migrating from hardcoded passwords to a password manager is painful but worth it — the Traefik migration forced the cleanup, and now every service uses proper secret references.
|
||||
- 30+ character passwords are free with a password manager — there's no reason to use short, memorable passwords for services that only need them in `.env` files.
|
||||
- Vaultwarden's Bitwarden compatibility means you don't have to convince family members to use a different app — the official Bitwarden clients just work.
|
||||
- The `SIGNUPS_ALLOWED` flag is easy to forget — leave it `true` too long and anyone on the LAN can create an account.
|
||||
|
||||
[← Back to Home](Home.md)
|
||||
Reference in New Issue
Block a user