[003] Add Forgejo stack behind Caddy at git.waldson.com.br

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.
This commit is contained in:
Waldson Patrício 2026-07-23 13:48:44 -03:00
parent ac61d310fa
commit 08c1012e60
3 changed files with 157 additions and 0 deletions

View file

@ -9,3 +9,7 @@ www.nazareonline.com.br, nazareonline.com.br {
sync.waldson.com.br { sync.waldson.com.br {
reverse_proxy syncthing-syncthing-1:8384 reverse_proxy syncthing-syncthing-1:8384
} }
git.waldson.com.br {
reverse_proxy forgejo-forgejo-1:3000
}

View file

@ -20,6 +20,8 @@ Caddyfile one site block per app; each reverse_proxies to a container
reachable by name on proxy_net reachable by name on proxy_net
syncthing/ the Syncthing stack (own compose file); GUI behind Caddy, syncthing/ the Syncthing stack (own compose file); GUI behind Caddy,
sync protocol published on 22000 sync protocol published on 22000
forgejo/ the Forgejo stack (own compose file); web UI behind Caddy,
git-over-SSH published on 2222
``` ```
## Prerequisites ## Prerequisites
@ -133,6 +135,92 @@ These steps are done once by the owner at `https://sync.waldson.com.br`:
torque-data) with the droplet and accept them on the droplet, pointing each 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. 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 ## Droplet groundwork
Baseline host configuration for the droplet (`wloud`, 174.138.36.232, Baseline host configuration for the droplet (`wloud`, 174.138.36.232,

65
forgejo/compose.yaml Normal file
View file

@ -0,0 +1,65 @@
name: forgejo
# Single-user Forgejo instance behind the central Caddy proxy at
# git.waldson.com.br. The web UI is reached only through Caddy on proxy_net (the
# HTTP port is never published); git-over-SSH is published on host port 2222 so
# the host's own sshd keeps port 22. All state (repos, the SQLite database, and
# the generated app.ini) lives in the forgejo_data volume. See README.md for the
# deploy, admin-creation, and upgrade runbook.
services:
forgejo:
# Pinned to a major version: Forgejo major upgrades run one-way database
# migrations, so bump this tag deliberately (see the upgrade runbook) rather
# than tracking latest.
image: codeberg.org/forgejo/forgejo:11
restart: unless-stopped
environment:
# Own the repos and SQLite file as the droplet's deploy user, matching the
# Syncthing stack, so state is manageable over plain SSH.
USER_UID: "1000"
USER_GID: "1000"
# All configuration is injected here and written into app.ini on startup,
# so the security posture (registration off, Actions off) is reproduced on
# every recreate and does not depend on installer choices.
FORGEJO__database__DB_TYPE: sqlite3
FORGEJO__database__PATH: /data/gitea/gitea.db
# Public-facing identity. ROOT_URL and SSH_DOMAIN drive the clone URLs the
# UI shows; without them Forgejo would advertise its container hostname.
FORGEJO__server__DOMAIN: git.waldson.com.br
FORGEJO__server__SSH_DOMAIN: git.waldson.com.br
FORGEJO__server__ROOT_URL: https://git.waldson.com.br/
FORGEJO__server__HTTP_PORT: "3000"
# SSH_PORT is what the UI puts in ssh:// clone URLs (the published host
# port); SSH_LISTEN_PORT is the container-internal sshd that host 2222 maps
# to. Keeping them distinct lets the host sshd keep port 22.
FORGEJO__server__SSH_PORT: "2222"
FORGEJO__server__SSH_LISTEN_PORT: "22"
# No sign-up path for strangers; the single admin is created out of band
# via the CLI (see the runbook).
FORGEJO__service__DISABLE_REGISTRATION: "true"
# Skip the web installer entirely: the instance is fully configured above,
# and locking it stops anyone from re-running install to change these.
FORGEJO__security__INSTALL_LOCK: "true"
# No CI runners on the 2 GB droplet.
FORGEJO__actions__ENABLED: "false"
volumes:
# Repos, the SQLite database, generated config, and SSH host keys all live
# under /data, so this one volume is the entire instance. It is never
# removed casually (see the runbook).
- forgejo_data:/data
ports:
# git-over-SSH only. Host 2222 -> container sshd on 22, so the host's admin
# sshd on 22 is untouched. HTTP (3000) is deliberately unpublished; Caddy
# reaches it by container name on proxy_net.
- "2222:22"
mem_limit: 768m
networks:
- proxy_net
networks:
proxy_net:
external: true
volumes:
forgejo_data: