4.8 KiB
4.8 KiB
Caddy (infra LXC) Setup
Overview
The infra LXC (10.2.0.20) in VLAN 1020 runs Caddy as the internal reverse proxy. It handles TLS termination for all *.lerkolabs.com services using wildcard certs via Cloudflare DNS-01 challenge. Also hosts ntfy (push notifications) and Uptime Kuma (service monitoring) as co-located services.
| Service | Port | Domain |
|---|---|---|
| Caddy | 80/443 | reverse proxy — no direct domain |
| ntfy | 8090 | ntfy.lerkolabs.com |
| Uptime Kuma | 3001 | uptime.lerkolabs.com |
Prerequisites
- LXC created at 10.2.0.20 in VLAN 1020
- Cloudflare API token with Zone → DNS → Edit permissions for lerkolabs.com (stored in Vaultwarden)
- Pi-hole DNS record:
*.lerkolabs.com → 10.2.0.20
LXC Spec
| Property | Value |
|---|---|
| Hostname | infra |
| IP | 10.2.0.20/24 |
| Gateway | 10.2.0.1 |
| Cores | 2 |
| RAM | 1GB |
| Template | debian-12-standard |
| Nesting | ✓ (required for Docker) |
Installation
apt update && apt upgrade -y
apt install -y curl nano ufw
timedatectl set-timezone <your/timezone>
curl -fsSL https://get.docker.com | sh
systemctl enable docker
Directory Structure
/opt/docker/
├── caddy/
│ ├── Caddyfile
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── .env
│ ├── data/
│ ├── config/
│ └── logs/
└── infra/
├── uptimekuma/
│ ├── docker-compose.yml
│ └── data/
└── ntfy/
├── docker-compose.yml
├── server.yml
└── data/
Caddy Deployment
Dockerfile (custom build with Cloudflare DNS plugin)
FROM caddy:<see private/configs/versions.md> AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare
FROM caddy:<see private/configs/versions.md>
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
.env
CLOUDFLARE_API_TOKEN=<token from Vaultwarden: homelab/cloudflare-api>
chmod 600 /opt/docker/caddy/.env
docker-compose.yml
services:
caddy:
build: .
container_name: caddy
restart: unless-stopped
network_mode: host
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data
- ./config:/config
- ./logs:/logs
env_file:
- .env
Caddyfile structure
{
email <your-acme-email>
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
# Forward auth snippet — reuse in every protected service block
(authentik_forward_auth) {
forward_auth 10.2.0.25:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email \
X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks \
X-Authentik-Meta-Outpost X-Authentik-Meta-Provider \
X-Authentik-Meta-App X-Authentik-Meta-Version
trusted_proxies private_ranges
}
}
# Authentik
auth.lerkolabs.com {
reverse_proxy 10.2.0.25:9000
}
# Pi-hole (forward auth)
pihole.lerkolabs.com {
import authentik_forward_auth
reverse_proxy 10.2.0.11:80
}
# Add remaining services following the same pattern
# Services with native OIDC (Outline, Gitea, Vikunja): no forward auth needed
# Services without OIDC: import authentik_forward_auth
Build and start
cd /opt/docker/caddy
docker compose build
docker compose up -d
docker logs -f caddy
# Wait for: "certificate obtained successfully"
ntfy Deployment
server.yml
base-url: https://ntfy.lerkolabs.com
listen-http: ":8090"
cache-file: /var/cache/ntfy/cache.db
auth-file: /var/lib/ntfy/auth.db
auth-default-access: deny-all
behind-proxy: true
docker-compose.yml
services:
ntfy:
image: binwiederhier/ntfy:latest
container_name: ntfy
restart: unless-stopped
command: serve
ports:
- "8090:8090"
volumes:
- ./server.yml:/etc/ntfy/server.yml:ro
- ./data:/var/cache/ntfy
- ./data:/var/lib/ntfy
cd /opt/docker/infra/ntfy && docker compose up -d
docker exec -it ntfy ntfy user add --role=admin <username>
Uptime Kuma Deployment
services:
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- ./data:/app/data
cd /opt/docker/infra/uptimekuma && docker compose up -d
Caddy Reload
After editing the Caddyfile:
docker exec caddy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
Verification
# Cert issued
docker logs caddy | grep "certificate obtained"
# Service reachable
curl -I https://pihole.lerkolabs.com
# Expected: HTTP/2 200
# Ports bound
ss -tlnp | grep -E "443|8090|3001"