Forgejo
Lifecycle: Current. Last verified: 2026-07-17.
Service record
| Field | Value |
|---|---|
| Purpose | Git hosting and Forgejo Actions control plane |
| Owner | IT Operations |
| Criticality / recovery priority | High / P3 |
| Host/runtime | CT 101, rootless Podman as podsvc |
| Image | codeberg.org/forgejo/forgejo:11 |
| Web | Caddy route git.kh3group.com; backend 192.168.2.20:30080 |
| SSH | Container 22, CT host 2222 |
| Network | kh3-backend |
| Data | /opt/podman/volumes/forgejo/data |
| Env | /opt/podman/env/forgejo.env, mode 0600; never print |
| Quadlet | /home/podsvc/.config/containers/systemd/forgejo.container |
| Database | PostgreSQL database/role forgejo |
The runner is a separate rootless Quadlet with image
code.forgejo.org/forgejo/runner:6.3.1, control plane
http://forgejo:3000, data under
/opt/podman/volumes/forgejo/runner-data, and allowed bind roots for
runner-data and runner-ssh.
Dependencies and access
Forgejo depends on CT 101, rootless Podman, PostgreSQL, and kh3-backend.
Web access depends on Technitium and CT 103. SSH clients inside the DMZ use
[email protected] -p 2222; CT host port 22 is not Forgejo.

Forgejo's repository runner list. The upstream example uses a docker label;
the KH3 documentation runner must be online with the mkdocs label.
Reconstruct and configure
Use Rootless Podman Restore for the historical database/archive import, then reconcile the live-only runner and publishing Quadlets. Required secret names include database password and OAuth/OIDC client secret; values belong in restricted env/config storage.
Forgejo's advertised SSH port must match the externally reachable port.
Container SSH remains 22, while clone URLs normally need 2222 unless an
intermediate proxy supplies another port.
The runner daemon must start with
forgejo-runner daemon --config /config.yml. Its redacted configuration shape
is:
container:
network: kh3-backend
options: >-
-v /opt/podman/volumes/forgejo/runner-ssh:/runner-ssh:ro
--dns 192.168.2.2
--add-host git.kh3group.com:192.168.2.3
valid_volumes:
- /opt/podman/volumes/forgejo/runner-data
- /opt/podman/volumes/forgejo/runner-ssh
The corresponding host mounts are
/opt/podman/volumes/forgejo/runner-data:/data for runner state,
/opt/podman/volumes/forgejo/runner-data/config.yml:/config.yml:ro for
configuration, and the two allow-listed roots for job containers. Never
publish an unrestricted host path through valid_volumes.
Operate
ssh pvessh 'pct exec 101 -- runuser -l podsvc -c \
"podman ps --filter name=forgejo --filter name=runner"'
ssh pvessh 'pct exec 101 -- runuser -l podsvc -c \
"podman logs --tail=200 forgejo"'
ssh pvessh 'pct exec 101 -- runuser -l podsvc -c \
"podman logs --tail=200 runner"'
ssh pvessh 'curl -sS -o /dev/null -w "%{http_code}\n" \
http://192.168.2.20:30080/'
Expected: containers running and web returns 200, 30x, or an intentional
authentication response.
Run Forgejo CLI commands in the container as user git, never root:
ssh pvessh 'pct exec 101 -- runuser -l podsvc -c \
"podman exec -u git forgejo forgejo --config /data/gitea/conf/app.ini --version"'
Upgrade
- Read Forgejo release notes and database compatibility.
- Back up data, database, env/config, runner registration/config, and SSH host keys.
- Pin the tested image.
- Pull and restart only Forgejo in an approved window.
- Validate database migration, web, HTTPS Git, SSH on
2222, OAuth, and one Actions workflow. - Roll back image and data/database together if migration is not reversible.
Backup and restore
Back up /data, PostgreSQL database/roles, runner config/registration, and
deployment units. Restore database and data from the same recovery point.
Forgejo's /data/git/.ssh/authorized_keys is generated; after restore, compare
database key records and regenerate as git if stale.
Security and monitoring
- Keep OAuth secrets and job tokens out of logs.
- Restrict runner bind roots.
- Review runner jobs before exposing new host mounts.
- Monitor web health, database health, runner online state, disk usage, and a scheduled test workflow.
Troubleshooting
| Symptom | Diagnosis | Remedy |
|---|---|---|
Web 502 |
Test 30080, Forgejo unit, PostgreSQL |
Repair failed dependency |
| SSH permission denied | Compare client fingerprint, DB ownership, generated authorized_keys |
Regenerate keys as container user git; do not duplicate a globally unique key |
Clone URL lacks :2222 |
Check selected server SSH settings | Correct advertised port in an approved change |
| Runner offline | Unit/log/config path/control-plane URL | Use http://forgejo:3000 and explicit --config /config.yml |
| Job bind ignored | Check container.valid_volumes |
Add only reviewed bind root and restart runner with approval |
Job stuck running without task container |
Check runner logs and task containers | Cancel/re-run in UI; do not edit Actions DB |