Run an always-on Syncthing peer on the droplet as an off-site copy for the owner's folders. New syncthing/ compose stack on proxy_net: the sync protocol is published on host 22000 (tcp+udp) for direct device connections, while the GUI (8384) is unpublished and reached only through the central Caddy, which gains a sync.waldson.com.br reverse-proxy block. Config and folder data live on a host bind mount for SSH inspection and are git-ignored. README documents the deploy, upgrade, and interactive pairing runbook. Binding the GUI to a non-loopback address (STGUIADDRESS) also disables Syncthing's DNS-rebinding host check, so the proxy needs no Host rewrite. |
||
|---|---|---|
| syncthing | ||
| .gitignore | ||
| Caddyfile | ||
| compose.yaml | ||
| README.md | ||
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
syncthing/ the Syncthing stack (own compose file); GUI behind Caddy,
sync protocol published on 22000
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
Caddyfilepoint 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):
docker exec central-proxy-caddy-1 caddy reload --config /etc/caddy/Caddyfile
Validate a change before reloading:
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:
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:
docker compose up -d
Syncthing stack
An always-on Syncthing peer for the owner's folders, giving devices somewhere
to sync when no two of them are online together and keeping an off-site copy on
the droplet. It lives in syncthing/ and deploys to
/home/waldson/central-proxy/syncthing on the droplet.
Only the sync protocol is published on the host (22000/tcp and 22000/udp),
so devices connect directly instead of through community relays. The web GUI
(8384) is not published; it is reached only through Caddy at
sync.waldson.com.br, guarded by Syncthing's own username/password. The
central Caddyfile already carries the sync.waldson.com.br block. Binding the
GUI to a non-loopback address (STGUIADDRESS=0.0.0.0:8384, required so Caddy in
its own container can reach it) also turns off Syncthing's DNS-rebinding host
check, so a plain reverse_proxy needs no Host-header rewrite.
Where data lives
Config (device keys, the folder list) and every synced folder live under the
host bind mount syncthing/data (mounted at /var/syncthing in the container),
so state survives container recreation and is inspectable over plain SSH. The
directory is git-ignored. PUID/PGID default to 1000; if the droplet's
deploy user has a different id, adjust them in compose.yaml so the synced
files stay owned by that user.
Deploy
From syncthing/ on the droplet:
docker compose up -d
The container comes up as syncthing-syncthing-1 on proxy_net, which is the
name Caddy reverse-proxies to. A restart preserves config and pairings:
docker compose restart
Upgrade
The image is syncthing/syncthing:latest (Syncthing publishes no rolling major
tag; pin a specific version here if you want reproducible upgrades). To update:
docker compose pull && docker compose up -d
First-run setup (interactive, in the GUI)
These steps are done once by the owner at https://sync.waldson.com.br:
- Set the GUI username and password (Actions -> Settings -> GUI).
- Add the droplet as a device on each personal device (and vice versa), using the device IDs shown under Actions -> Show ID.
- Share all five folders (Sync, phone-photos, comprovantes, notes,
torque-data) with the droplet and accept them on the droplet, pointing each
folder path under
/var/syncthingso the data lands in the bind mount.
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):
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:
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:
dig +short waldson.com.br www.waldson.com.br git.waldson.com.br sync.waldson.com.br
Notes
letsencrypt/acme.jsonis 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.