homelab-blueprint/docker-compose-files/forgejo
Brian Pooe 1c2f4a1c81 fix(forgejo): set SSH_DOMAIN so the UI's clone URL is actually usable
Forgejo's displayed SSH clone URL comes from SSH_DOMAIN, which defaults
to "localhost" (not DOMAIN) when unset — completely unusable, and
DOMAIN itself would've been wrong anyway since it resolves through
Caddy's wildcard CNAME, which SSH can't traverse. Point it at the
dedicated forgejo.home.brianpooe.com A record instead.
2026-07-19 15:17:25 +02:00
..
.env.sample fix(forgejo): set SSH_DOMAIN so the UI's clone URL is actually usable 2026-07-19 15:17:25 +02:00
README.md docs(forgejo): document the forgejo.home.brianpooe.com DNS alias 2026-07-19 15:13:56 +02:00
template.yaml fix(forgejo): set SSH_DOMAIN so the UI's clone URL is actually usable 2026-07-19 15:17:25 +02:00

Forgejo

Self-hosted Git service (soft fork of Gitea) with Postgres.

Persistent Data

Split between local disk and the UNAS forgejo_data share (see nfs-mounts — you'll need to add this share following that guide, it isn't in the table yet):

  • forgejo_data named volume (local disk) — app config, SSH host keys, session store, Bleve search index.

  • forgejo_db_data named volume (local disk) — Postgres data directory. Databases don't belong on NFS regardless of engine (locking/fsync semantics), so this stays local even though Forgejo isn't using SQLite.

  • Share dirs, NFS-mounted on the Proxmox host and bind-mounted into the LXC at FORGEJO_DATA_ROOT:

    • repositories/ — bare git repo objects. This is what grows over time.
    • lfs/ — Git LFS objects.
    • attachments/ — issue/PR attachments.
    • avatars/ — user and repo avatars.

    Git's own protocol tolerates NFS latency far better than a database does, so this bulk data is a safe, capacity-friendly NAS candidate.

The share binds use create_host_path: false, so the stack refuses to start if the share is not mounted, rather than silently writing into an empty directory that later needs merging back in.

Installation

  1. Create .env if it does not exist:

    cp -n docker-compose-files/forgejo/.env.sample docker-compose-files/forgejo/.env
    
  2. Set the required values in docker-compose-files/forgejo/.env — at minimum FORGEJO_ROOT_URL, FORGEJO_DOMAIN, FORGEJO_DB_PASSWORD, and FORGEJO_DATA_ROOT.

  3. Add the UNAS share and bind-mount it into the LXC, following nfs-mounts.md (add forgejo_data to the share list there, same No Root Squash treatment as paperless_data):

    # On the Proxmox host, once the share is mounted at /mnt/unas/forgejo_data
    pct set <vmid> -mp0 /mnt/unas/forgejo_data,mp=/mnt/unas/forgejo_data
    mkdir -p /mnt/unas/forgejo_data/{repositories,lfs,attachments,avatars}
    chown -R 101000:101000 /mnt/unas/forgejo_data
    
  4. Deploy with make:

    make deploy-forgejo
    

    Manual equivalent:

    ./substitute_env.sh docker-compose-files/forgejo/template.yaml docker-compose.forgejo.yml .env docker-compose-files/forgejo/.env
    docker compose -f docker-compose.forgejo.yml config --quiet
    docker compose -f docker-compose.forgejo.yml up -d
    
  5. Open http://<host-ip>:3000, create the first (admin) account, and confirm repo creation/push/clone works.

Reverse Proxy

Add a block for git.<domain> (port FORGEJO_HTTP_PORT) to docker-compose-files/caddy/Caddyfile_template, then make reload-caddy. Git-over-SSH (FORGEJO_SSH_PORT) is not proxyable through Caddy — expose that port directly if you want SSH clone/push from outside the LAN.

FORGEJO_SSH_PORT defaults to 2222 because the LXC's own admin sshd already owns port 22. This instance was later moved to the standard port — the LXC's admin sshd got relocated to 2200 first (systemd ssh.socket override), freeing 22 for Forgejo's git-SSH. See .env.sample for the one-time steps if you want to do the same elsewhere.

Since SSH bypasses Caddy, this instance instead gets a plain Technitium A record — forgejo.home.brianpooe.com10.0.10.18 — so clone URLs read ssh://git@forgejo.home.brianpooe.com/<owner>/<repo>.git instead of the raw LXC IP. Add this in the Technitium admin UI (or via its API) as a normal zone record; it isn't behind the *.home.brianpooe.com → Caddy wildcard.

Notes

  • Pin FORGEJO_VERSION to a specific major (as shipped, 10) rather than floating — Forgejo releases can carry DB migrations, so an unpinned tag risks pulling a breaking bump on a routine docker compose pull.
  • FORGEJO__security__INSTALL_LOCK=true is baked into the template since all DB config is supplied via environment — this skips the web installer so a stray first visitor can't reconfigure the instance.

Useful Commands

# Tail logs
docker compose -f docker-compose.forgejo.yml logs -f forgejo

# Create an additional user without open registration
docker exec -it forgejo forgejo admin user create --username <name> --email <email> --random-password

# Recreate after an image update
docker compose -f docker-compose.forgejo.yml pull
docker compose -f docker-compose.forgejo.yml up -d