152 lines
5.4 KiB
Markdown
152 lines
5.4 KiB
Markdown
# central-proxy
|
|
|
|
The shared edge reverse proxy for the DigitalOcean droplet (`174.138.36.232`).
|
|
A single **Caddy** container terminates TLS for every site on the box and routes
|
|
each hostname to the right upstream container. It is the only thing published on
|
|
ports 80 and 443; all the application stacks (bigode, nazareonline, ...) sit
|
|
behind it on the external `proxy_net` Docker network and speak plain HTTP to it.
|
|
|
|
Caddy manages Let's Encrypt certificates automatically - it obtains and renews a
|
|
cert for each hostname listed in the `Caddyfile` on first request. Issued certs
|
|
and ACME account keys persist in the `caddy_data` Docker volume (mounted at
|
|
`/data`), not in this repo.
|
|
|
|
## Layout
|
|
|
|
```
|
|
compose.yaml the Caddy service: publishes 80/443, joins proxy_net,
|
|
bind-mounts the Caddyfile, persists certs in caddy_data
|
|
Caddyfile one site block per app; each reverse_proxies to a container
|
|
reachable by name on proxy_net
|
|
```
|
|
|
|
## Prerequisites
|
|
|
|
- The external network exists: `docker network create proxy_net`.
|
|
- Each upstream stack attaches its public-facing container to `proxy_net`, so
|
|
Caddy can reach it by container name (e.g. `nazareonline-web-1:80`).
|
|
- DNS A records for every hostname in the `Caddyfile` point at the droplet.
|
|
|
|
## Adding a site
|
|
|
|
Add a block to the `Caddyfile` naming the hostname(s) and the upstream:
|
|
|
|
```
|
|
www.example.com, example.com {
|
|
reverse_proxy example-web-1:80
|
|
}
|
|
```
|
|
|
|
The upstream container must be on `proxy_net`. Caddy forwards the original `Host`
|
|
and sets `X-Forwarded-Proto: https`, so upstreams behind it can rebuild HTTPS
|
|
URLs and do their own host canonicalisation.
|
|
|
|
Then reload without downtime (does not disturb the other sites):
|
|
|
|
```sh
|
|
docker exec central-proxy-caddy-1 caddy reload --config /etc/caddy/Caddyfile
|
|
```
|
|
|
|
Validate a change before reloading:
|
|
|
|
```sh
|
|
docker run --rm -v "$PWD/Caddyfile":/etc/caddy/Caddyfile:ro caddy:2-alpine \
|
|
caddy validate --config /etc/caddy/Caddyfile
|
|
```
|
|
|
|
## Deploy
|
|
|
|
The compose stack lives at `/home/waldson/central-proxy` on the droplet. To
|
|
apply a change, copy the edited `Caddyfile` up and reload:
|
|
|
|
```sh
|
|
scp -i ~/.ssh/deploy_key ./Caddyfile waldson@174.138.36.232:/home/waldson/central-proxy/Caddyfile
|
|
ssh -i ~/.ssh/deploy_key waldson@174.138.36.232 \
|
|
'docker exec central-proxy-caddy-1 caddy reload --config /etc/caddy/Caddyfile'
|
|
```
|
|
|
|
Bring the proxy up (or recreate it after a compose change) from that directory:
|
|
|
|
```sh
|
|
docker compose up -d
|
|
```
|
|
|
|
## Droplet groundwork
|
|
|
|
Baseline host configuration for the droplet (`wloud`, 174.138.36.232,
|
|
2 GB RAM, 2 vCPUs, 58 GB disk). Everything below was set up once and only
|
|
needs revisiting when the host changes.
|
|
|
|
### Swap
|
|
|
|
The droplet has 2 GB RAM and no swap by default; a 2 GB swapfile prevents
|
|
Out Of Memory (OOM) kills during on-droplet builds and daemon memory spikes.
|
|
It is created once with (requires sudo):
|
|
|
|
```sh
|
|
sudo fallocate -l 2G /swapfile
|
|
sudo chmod 600 /swapfile
|
|
sudo mkswap /swapfile
|
|
sudo swapon /swapfile
|
|
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
|
|
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
|
|
sudo sysctl -p /etc/sysctl.d/99-swappiness.conf
|
|
```
|
|
|
|
The fstab entry makes it survive reboots; swappiness 10 keeps the kernel from
|
|
swapping eagerly. Verify with `free -m` (~2048 total swap) and, after a
|
|
reboot, `swapon --show`.
|
|
|
|
### Firewall
|
|
|
|
`ufw` is installed and active on the host. Probing from outside the droplet
|
|
(2026-07-23) showed no filtering in the path for the service ports: 2222/tcp
|
|
and 22000/tcp answered "connection refused" while nothing listened, which
|
|
means packets reach the host, so neither ufw nor a DigitalOcean cloud
|
|
firewall blocks them. Ports in use:
|
|
|
|
| Port | Proto | Service |
|
|
|-----------|---------|--------------------------------|
|
|
| 22 | tcp | host sshd |
|
|
| 80, 443 | tcp | Caddy (all HTTP/HTTPS traffic) |
|
|
| 2222 | tcp | Forgejo git-over-SSH |
|
|
| 22000 | tcp+udp | Syncthing sync protocol |
|
|
|
|
If ufw rules are ever tightened, keep the four rows above allowed. To
|
|
re-verify reachability from any machine outside the droplet:
|
|
|
|
```sh
|
|
nc -vz 174.138.36.232 2222
|
|
nc -vz 174.138.36.232 22000
|
|
nc -vzu 174.138.36.232 22000
|
|
```
|
|
|
|
"Connection refused" means the port is reachable but nothing listens yet;
|
|
only a timeout indicates a filter.
|
|
|
|
### DNS
|
|
|
|
All records live on the DigitalOcean nameservers (ns1-3.digitalocean.com)
|
|
and point at the droplet:
|
|
|
|
| Record | Type | Value | Serves |
|
|
|--------------------|------|-----------------|----------------------|
|
|
| waldson.com.br | A | 174.138.36.232 | personal site |
|
|
| www.waldson.com.br | A | 174.138.36.232 | redirect to apex |
|
|
| git.waldson.com.br | A | 174.138.36.232 | Forgejo |
|
|
| sync.waldson.com.br| A | 174.138.36.232 | Syncthing GUI |
|
|
|
|
personaledson.com.br and nazareonline.com.br also resolve here for the
|
|
existing stacks. Check resolution with:
|
|
|
|
```sh
|
|
dig +short waldson.com.br www.waldson.com.br git.waldson.com.br sync.waldson.com.br
|
|
```
|
|
|
|
## Notes
|
|
|
|
- `letsencrypt/acme.json` is leftover state from an earlier Traefik-based proxy
|
|
and is not used by Caddy. It is git-ignored (it contains private keys); it can
|
|
be deleted from the droplet once you are sure nothing references it.
|
|
- Never `docker volume rm caddy_data` - that discards every issued certificate
|
|
and forces re-issuance, which can hit Let's Encrypt rate limits.
|