Run a single-user, self-hosted git server on the droplet. New forgejo/ compose stack on proxy_net: git-over-SSH is published on host 2222 (container sshd on 22) so the host's admin sshd keeps port 22, while the web UI (3000) is unpublished and reached only through the central Caddy, which gains a git.waldson.com.br reverse-proxy block. SQLite backs the instance and all state - repos, database, generated app.ini, SSH host keys - lives in the forgejo_data volume, so a force-recreate preserves every user and repository. The full configuration is injected via FORGEJO__ environment variables and written into app.ini on startup: registration disabled, Forgejo Actions disabled, and DOMAIN/SSH_DOMAIN/SSH_PORT set so clone URLs carry the right host and port. INSTALL_LOCK skips the web installer, so the single admin is created from the CLI; the security posture survives recreation instead of depending on installer clicks. README documents the deploy, admin-creation, SSH-config, and upgrade runbook.
302 lines
11 KiB
Markdown
302 lines
11 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
|
|
syncthing/ the Syncthing stack (own compose file); GUI behind Caddy,
|
|
sync protocol published on 22000
|
|
forgejo/ the Forgejo stack (own compose file); web UI behind Caddy,
|
|
git-over-SSH published on 2222
|
|
```
|
|
|
|
## 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
|
|
```
|
|
|
|
## 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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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`:
|
|
|
|
1. Set the GUI username and password (Actions -> Settings -> GUI).
|
|
2. Add the droplet as a device on each personal device (and vice versa), using
|
|
the device IDs shown under Actions -> Show ID.
|
|
3. 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/syncthing` so the data lands in the bind mount.
|
|
|
|
## Forgejo stack
|
|
|
|
A single-user Forgejo instance (the owner's self-hosted git server) at
|
|
`git.waldson.com.br`. It lives in `forgejo/` and deploys to
|
|
`/home/waldson/central-proxy/forgejo` on the droplet.
|
|
|
|
Only git-over-SSH is published on the host (`2222/tcp`), so the host's admin
|
|
sshd keeps port 22. The web UI (`3000`) is **not** published; it is reached only
|
|
through Caddy at `git.waldson.com.br`. The central `Caddyfile` carries the
|
|
`git.waldson.com.br` block. SQLite is the database, and the whole instance is
|
|
configured from environment variables in `compose.yaml` (registration disabled,
|
|
Forgejo Actions disabled, correct clone-URL host/port), so the security posture
|
|
is reproduced on every recreate rather than depending on installer clicks.
|
|
|
|
### Where data lives
|
|
|
|
Everything - git repositories, the SQLite database (`/data/gitea/gitea.db`), the
|
|
generated `app.ini`, and the container's SSH host keys - lives in the
|
|
`forgejo_data` named Docker volume mounted at `/data`. That one volume is the
|
|
entire instance, so `docker compose up -d --force-recreate` preserves all users
|
|
and repositories. Never `docker volume rm forgejo_data`: it discards every repo
|
|
and account. (Off-site backup of this volume is a separate slice: a nightly
|
|
`forgejo dump` into a Syncthing-shared folder.)
|
|
|
|
### Deploy
|
|
|
|
From `forgejo/` on the droplet:
|
|
|
|
```sh
|
|
docker compose up -d
|
|
```
|
|
|
|
The container comes up as `forgejo-forgejo-1` on `proxy_net`, which is the name
|
|
Caddy reverse-proxies to. Recreating it preserves users and repositories:
|
|
|
|
```sh
|
|
docker compose up -d --force-recreate
|
|
```
|
|
|
|
### First-run setup (interactive)
|
|
|
|
The web installer is locked (`INSTALL_LOCK=true`), so the single admin account
|
|
is created from the CLI once, after the container is first up:
|
|
|
|
```sh
|
|
docker exec -u git forgejo-forgejo-1 \
|
|
forgejo admin user create \
|
|
--admin --username <admin> --email <you@example.com> \
|
|
--random-password --must-change-password
|
|
```
|
|
|
|
Log in at `https://git.waldson.com.br`, change the password, and add your SSH
|
|
public key under Settings -> SSH / GPG Keys. Registration is already disabled, so
|
|
there is no sign-up path for anyone else.
|
|
|
|
### Cloning over SSH (port 2222)
|
|
|
|
Forgejo advertises `ssh://git@git.waldson.com.br:2222/<user>/<repo>.git`. To keep
|
|
remotes short (`git@git.waldson.com.br:<user>/<repo>.git`) on a personal machine,
|
|
add this to `~/.ssh/config`:
|
|
|
|
```
|
|
Host git.waldson.com.br
|
|
HostName git.waldson.com.br
|
|
Port 2222
|
|
User git
|
|
IdentityFile ~/.ssh/id_ed25519
|
|
```
|
|
|
|
With that entry, `git clone git@git.waldson.com.br:<user>/<repo>.git` works
|
|
without spelling out the port. HTTPS clone/pull works through Caddy with no SSH
|
|
setup at all: `git clone https://git.waldson.com.br/<user>/<repo>.git`.
|
|
|
|
### Upgrade
|
|
|
|
The image is pinned to a Forgejo major version (`codeberg.org/forgejo/forgejo:11`).
|
|
Patch and minor updates within that major are safe:
|
|
|
|
```sh
|
|
docker compose pull && docker compose up -d
|
|
```
|
|
|
|
A major bump (editing the tag to the next number) runs one-way database
|
|
migrations. Do it one major at a time, back up `forgejo_data` first, and read the
|
|
Forgejo release notes before changing the tag.
|
|
|
|
## 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.
|