16 KiB
Forgejo — Self-Hosted Git Server
Device Type: Docker Container (hosted on NAS16) Hostname: forgejo IP Address: 192.168.150.40 (shares NAS16's host IP) VLAN: 50 — Lab / Servers
Last Updated: 2026-07-26
🧩 Role & Purpose
Forgejo is the self-hosted Git server and file version-control system for the KingDezigns network. It provides a web UI for browsing, editing, and tracking revision history of scripts, configs, and project files — replacing ad-hoc file copies and manual backups with real version control.
Forgejo was chosen over Gitea specifically for its non-profit, community-run governance model (Codeberg e.V.) — consistent with the KingDezigns philosophy of avoiding dependency on third-party business decisions (same reasoning behind self-hosted DNS, self-hosted reverse proxy, and self-hosted stock price API). Functionally, Forgejo and Gitea are near-identical since Forgejo is a fork of Gitea; the choice was governance, not features.
This server hosts version-controlled copies of the scripts and project files that live under the network's 07 - Projects share, organized per host.
🌐 Network Placement
- VLAN: 50 — Lab / Servers
- Host: NAS16 (192.168.150.40)
- Access Type: LAN / Cable (web UI also reachable externally via NPM)
Chosen over NAS08 or PLEX32 because NAS16 has the most headroom of any Pi in the fleet (16GB RAM, consistently underutilized) — the same reasoning already used to place the CrowdSec notifier scripts on NAS16 instead of HAS. NAS08 is the busiest box (DNS, Nextcloud, ONLYOFFICE, Collabora, Vaultwarden, STOCKPROXY) and PLEX32's CPU headroom exists specifically for Quick Sync transcoding — neither is a good fit for an always-on secondary service.
This placement is subject to VLAN 50's normal containment rules — see firewall_policies.md. No firewall changes were required to stand this up; existing VLAN 1/20 → VLAN 50 inbound rules and NAS16's local traffic already covered everything needed.
📦 Primary Functions
Core Service
- Forgejo (Docker —
codeberg.org/forgejo/forgejo:10) - SQLite database (appropriate at this scale — no separate DB container)
- Web UI for repository browsing, in-browser file editing, and commit history
- SSH-based git access (clone/push/pull) on a dedicated port
- Git-over-HTTPS also available via the public web URL
Public Access
- Domain:
https://forgejo.kingdezigns.com/ - Proxy: NPM on HAS (192.168.150.30) →
192.168.150.40:3000 - SSL: Let's Encrypt via NPM, Force SSL enabled — same pattern as all other KingDezigns public services
- SSH clone/push: LAN-only for now —
ssh://git@192.168.150.40:2222/.... Not exposed externally (no UCG Max port-forward configured). External SSH access is a possible future addition; HTTPS clone via the public domain works from anywhere in the meantime.
Organizations & Repository Convention
Each host in the network gets its own Forgejo Organization, matching the 07 - Projects folder structure on NAS16's share. Each organization currently holds a single repository named <hostname>-scripts.
| Organization | Repository | Source Folder (on NAS16) |
|---|---|---|
NAS08 |
nas08-scripts |
/export/kingdezignsnas-16/Public/07 - Projects/NAS08 |
NAS16 |
nas16-scripts |
/export/kingdezignsnas-16/Public/07 - Projects/NAS16 |
PLEX32 |
plex32-scripts |
/export/kingdezignsnas-16/Public/07 - Projects/PLEX32 |
This convention (<HOSTNAME> org → <hostname>-scripts repo) is expected to extend to future hosts (e.g. HAS) as they're added to version control.
Note: All three Projects folders live on the same NFS share on NAS16 — there is no need to install
gitor SSH keys on NAS08 or PLEX32 themselves. All git operations for all three repos are run from NAS16, simplycd-ing into the relevant subfolder.
🐳 Docker Configuration
Compose File Location
Managed via OMV → Services → Compose, project name forgejo.
Compose Contents
services:
forgejo:
image: codeberg.org/forgejo/forgejo:10
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1001
- FORGEJO__database__DB_TYPE=sqlite3
- FORGEJO__server__ROOT_URL=https://forgejo.kingdezigns.com/
- FORGEJO__server__DOMAIN=forgejo.kingdezigns.com
- FORGEJO__server__SSH_PORT=2222
- FORGEJO__server__SSH_LISTEN_PORT=22
volumes:
- /opt/forgejo/data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
restart: unless-stopped
UID/GID 1000/1001: Matches the existing
ncsharegroup convention used across NAS08, NAS16, and PLEX32 for permission consistency.
Key Docker Commands
# Start
cd /path/to/omv/compose/project/forgejo && sudo docker compose up -d
# Stop
cd /path/to/omv/compose/project/forgejo && sudo docker compose down
# Update Forgejo (recreates container to pick up new image + env vars)
cd /path/to/omv/compose/project/forgejo && sudo docker compose pull && sudo docker compose up -d
# View logs
sudo docker logs forgejo --tail 50
# Check container status
sudo docker ps
Note:
docker restart forgejorestarts the existing container with its already-loaded environment — it does not re-read Compose file changes. Any edit to environment variables in the Compose file requiresdocker compose up -d, notdocker restart, to actually take effect. This was the root cause of an initialROOT_URLmismatch — see Troubleshooting History below.
Docker Group Membership
rufusking was not initially in the docker group on NAS16, requiring sudo for all docker/docker compose commands. Add to group for passwordless docker access going forward:
sudo usermod -aG docker rufusking
(requires logout/login or newgrp docker to take effect)
🔐 SSH Access
- SSH clone command:
ssh://git@192.168.150.40:2222/<ORG>/<repo>.git - Port: 2222 (mapped to container's internal port 22) — chosen to avoid colliding with each host's own SSH daemon on port 22
- Auth: SSH public key only, added per-user under Settings → SSH/GPG Keys in the Forgejo web UI
- Scope: LAN-only — no UCG Max port-forward configured for 2222
Adding a new SSH key (per host/user)
ssh-keygen -t ed25519 -C "user@hostname-forgejo"
cat ~/.ssh/id_ed25519.pub
Paste the output into Forgejo: Settings → SSH/GPG Keys → Add Key.
Test:
ssh -T -p 2222 git@192.168.150.40
Expected response (this is success, not an error):
Hi there, <username>! You've successfully authenticated with the key named <key-name>, but Forgejo does not provide shell access.
Note: Because the Compose file grants
dockergroup access only after re-login, and because the Forgejo container's internalauthorized_keysfile is separate from the host's own SSH config, a key must be explicitly added via the Forgejo web UI — it is not sufficient to have a key that works for host-level SSH into NAS16 itself.
📝 Day-to-Day Workflow — Editing Files
Files under 07 - Projects/<HOST> can be edited two ways, both fully supported:
1. Web UI (Forgejo)
Browse to the repo, open a file, click the edit (pencil) icon, make changes, and commit directly from the browser. Best for quick one-off edits or browsing history/diffs.
2. Directly on the NFS share (local edit)
Since each Projects subfolder is a real git working directory, files can be edited by any method (Nano, a text editor, another script) directly on the share. Changes are not automatically tracked — git does not watch the folder — so they must be explicitly staged, committed, and pushed:
cd "/export/kingdezignsnas-16/Public/07 - Projects/<HOST>"
git add .
git commit -m "describe what changed"
git push
git statusbeforehand is optional — useful only to preview what changed.- Uncommitted local changes are not lost if you forget to push; the commit still exists locally and can be pushed later with a plain
git push. - Pulling before editing locally (
git pull) is good practice if the same file might also be edited via the web UI, to avoid merge conflicts.
🔒 Required Firewall Behavior
Inbound to Forgejo (via NAS16, 192.168.150.40)
- Allowed from VLAN 1 and VLAN 20 (global VLAN 50 inbound rules)
- Allowed from local VLAN 50 traffic
- External web traffic (
https://forgejo.kingdezigns.com/) arrives via NPM on HAS (192.168.150.30), same as all other public KingDezigns services - Port 2222 (SSH) is not forwarded externally — LAN-only
Outbound from Forgejo
- Subject to VLAN 50's global final drop, same as all NAS16 services — no outbound access to other VLANs
- No outbound dependencies beyond local DNS (Pi-hole)
No new firewall rules were required for this deployment — it operates entirely within NAS16's existing inbound/outbound rule set.
💾 Backup
Forgejo's data (SQLite database, repositories, config, SSH host keys) lives entirely under /opt/forgejo/data on the NAS16 host filesystem (bind-mounted, not a Docker volume) — making it straightforward to fold into NAS16's existing backup routine.
- Data path:
/opt/forgejo/data - Backup method: Folded into the existing
/usr/scripts/omv/nas16-backup.shOMV Scheduled Job (every 3 days at 2:00 AM) — no separate/dedicated backup job created - Restore consideration: Restoring
/opt/forgejo/dataand recreating the container (docker compose up -d) is expected to fully restore Forgejo, since all state (repos, DB, config, SSH host keys) lives under that single path — not yet tested end-to-end as of this writing
Note: Because
07 - Projectsfiles are also independently backed up as part of NAS16's normal share backups, project files are effectively protected twice — once as plain files on the share, and once as full git history inside Forgejo's data volume.
🛠️ Troubleshooting History (2026-07-26)
SSH "Permission denied (publickey)" on initial setup
Symptom: ssh -T -p 2222 git@192.168.150.40 returned Permission denied (publickey) even after generating an SSH keypair on NAS16.
Root cause: The generated public key was never actually added to the user's account in the Forgejo web UI (Settings → SSH/GPG Keys was empty) — the keypair existing locally does nothing until the public half is registered server-side.
Fix: Pasted the full id_ed25519.pub contents into Forgejo's Add Key form. Verified success via the "successfully authenticated... does not provide shell access" response (this message is expected and indicates success, not failure).
"Push to create is not enabled for organizations"
Symptom: git push failed with this error even after a successful local commit.
Root cause: The target repository (e.g. PLEX32/plex32-scripts) did not yet exist on the server — Forgejo does not auto-create repositories on push by default (a deliberate security default, left enabled).
Fix: Create the repository via the web UI first (Organization → New Repository, left uninitialized — no README/gitignore) before pushing.
ROOT_URL mismatch warning after adding NPM/domain
Symptom: After proxying Forgejo through NPM at https://forgejo.kingdezigns.com/, login displayed: "Your ROOT_URL in app.ini is 'http://192.168.150.40:3000/', it's unlikely matching the site you are visiting."
Root cause: ROOT_URL (and DOMAIN) were still set to the internal IP:port from initial setup. A mismatched ROOT_URL produces broken links in generated clone commands, notification emails, webhook payloads, and OAuth2 callbacks — not just cosmetic.
Dead end encountered: Editing /data/gitea/conf/app.ini directly (via vi inside the container, or sed) appeared to succeed and verified correctly immediately after the edit — but reverted to the old value on every docker restart forgejo. This is because the Compose file's FORGEJO__server__ROOT_URL environment variable overrides app.ini on every container start; a restart re-applies the container's existing (stale) environment rather than re-reading Compose.
Fix: Updated ROOT_URL (and added an explicit DOMAIN) directly in the Compose file's environment: block, then applied with docker compose up -d (which recreates the container with the new environment) rather than docker restart. Confirmed persistence across a subsequent restart.
Lesson for future config changes: Any Forgejo setting exposed as a FORGEJO__* environment variable in the Compose file will always win over a manually-edited app.ini value on every container start/recreate. Edit the Compose file, not the file inside the container, for any setting that has a corresponding env var.
git: command not found on NAS16 host
Symptom: Running git init/git add/etc. from the NAS16 shell failed — git was not installed on the host itself (only inside the Forgejo container, which is unrelated).
Fix: sudo apt-get update && sudo apt-get install -y git
permission denied while trying to connect to the docker API
Symptom: Plain docker exec ... commands failed for rufusking on NAS16.
Root cause: rufusking was not a member of the docker group.
Fix (immediate): Prefix commands with sudo.
Fix (permanent, recommended): sudo usermod -aG docker rufusking, then log out/in.
🧠 Summary for AI Systems
- Forgejo = self-hosted Git server + web UI for file version control, running as a Docker container on NAS16 (192.168.150.40), VLAN 50.
- Chosen over Gitea specifically for non-profit/community governance (Codeberg e.V.) — functionally near-identical to Gitea since it's a fork.
- Placed on NAS16 rather than NAS08 or PLEX32 because NAS16 has the most spare RAM/CPU headroom of the three (same reasoning as the existing CrowdSec notifier placement).
- Image:
codeberg.org/forgejo/forgejo:10, SQLite database, Compose-managed via OMV. - Public web UI:
https://forgejo.kingdezigns.com/, proxied via NPM on HAS, Force SSL + Let's Encrypt. - SSH git access:
ssh://git@192.168.150.40:2222/...— LAN-only, no external port-forward configured (may be added later). - Registration disabled (
DISABLE_REGISTRATION = true) — accounts are admin-created only. - Organization convention: one org per host (
NAS08,NAS16,PLEX32), one repo per org (<hostname>-scripts), matching the07 - Projects/<HOST>folder structure on NAS16's share. Expected to extend to future hosts (e.g. HAS). - All git operations for all three hosts are run from NAS16 — no git installation or SSH keys needed on NAS08 or PLEX32 themselves, since their Projects folders are on the same NFS share.
- Files can be edited either via the Forgejo web UI (commits directly) or locally on the NFS share (requires manual
git add/git commit/git push— changes are not auto-tracked). - Backup:
/opt/forgejo/data(bind-mounted, contains DB + repos + config + SSH host keys) folded into the existingnas16-backup.shOMV Scheduled Job (every 3 days, 2:00 AM) — no separate backup job. - Critical operational lesson: any
FORGEJO__*environment variable set in the Compose file overridesapp.inion every container start — config changes to settings with an env-var equivalent must be made in Compose + applied viadocker compose up -d, not by hand-editingapp.inior usingdocker restart. - SSH keys must be registered per-user in the Forgejo web UI (Settings → SSH/GPG Keys) — a working host-level SSH key does not automatically grant Forgejo git access.
- Repos must be created via the web UI before first push — "push to create" is disabled for organizations by default (left as-is, not overridden).
rufuskinginitially lackeddockergroup membership on NAS16 — recommended fix isusermod -aG docker rufuskingrather than routinesudouse.- Known minor cleanup items from initial imports (not yet addressed): duplicate files in
NAS08/nas08-scripts(e.g.nextcloud_update_check (1).sh, a redundant copy in bothnextcloud/andscripts/, a stray.html~backup file) and a possible duplicateplex32_backup2.shinPLEX32/plex32-scripts— safe to clean up in a follow-up commit since git history preserves them regardless. - Restore procedure for
/opt/forgejo/datahas not yet been tested end-to-end.