Why run your own
A self-hosted forge keeps source code on a machine whose location, access and retention you decide. For UK teams with clients who ask where their code lives, "a server in London that we administer" is a short, complete answer. You also escape per-seat pricing, set your own repository size limits and keep the forge up when a large hosted provider has a bad day.
The cost is that you are the operator: upgrades, backups and access control are yours. Forgejo and Gitea keep that burden small. Each is one Go binary, one config file and one database.
Install Forgejo on Debian
Create a system user and directories, then fetch the binary for your architecture from the project's release page and verify its signature.
sudo adduser --system --shell /bin/bash --group --disabled-password \
--home /home/git git
sudo mkdir -p /var/lib/forgejo /etc/forgejo
sudo chown git:git /var/lib/forgejo
sudo chown root:git /etc/forgejo && sudo chmod 770 /etc/forgejo
sudo install -m 755 forgejo-*-linux-amd64 /usr/local/bin/forgejo
Use PostgreSQL for anything beyond a personal instance. SQLite is fine for one or two users but serialises writes.
sudo -u postgres createuser -P forgejo
sudo -u postgres createdb -O forgejo forgejo
Add a systemd unit that runs /usr/local/bin/forgejo web --config /etc/forgejo/app.ini as the git user with WorkingDirectory=/var/lib/forgejo, enable it, and finish setup in the browser. Then lock the config directory back down to read-only for the service.
Proxy and TLS
Bind the forge to localhost and let Caddy or Nginx handle HTTPS:
git.example.co.uk {
reverse_proxy 127.0.0.1:3000
request_body {
max_size 512MB
}
}
Raise the body size limit, or large pushes over HTTPS will fail with an unhelpful 413. Set ROOT_URL in app.ini to the public HTTPS address so clone links are correct.
Clean SSH clone URLs
People prefer [email protected]:team/app.git to URLs with a port number. The easy route: one OpenSSH daemon on two ports. Port 22 accepts only the git user, whose keys the forge manages; port 2222 is for administrators.
# /etc/ssh/sshd_config.d/10-split.conf
Port 22
Port 2222
PasswordAuthentication no
Match LocalPort 22
AllowUsers git
AllowTcpForwarding no
X11Forwarding no
Restrict port 2222 to your own addresses in the portal firewall, keep 22 open for Git, and enable the forge's SSH_CREATE_AUTHORIZED_KEYS_FILE option so user keys land in the right place. Add your admin key through the portal's SSH key manager before changing anything, so the browser console is your only fallback rather than your plan.
CI next to the forge
Forgejo Actions reads .forgejo/workflows/*.yml with GitHub-compatible syntax. Enable it in app.ini under [actions], generate a registration token in the site administration, and install the runner on a second VPS:
forgejo-runner register --no-interactive \
--instance https://git.example.co.uk \
--token XXXXXXXX --name lon-build-01 \
--labels docker:docker://node:20-bookworm
forgejo-runner daemon
Keeping builds off the forge machine matters. A runaway build should never be able to fill the disk that holds every repository you own. See CI runners for sizing.
Mirrors and migration
Both forges can import repositories with issues and pull requests from the common hosted services, and can push-mirror to an external remote on every commit. A push mirror to a second location is a cheap extra copy of your code, independent of your backups.
Backups
sudo -u git forgejo dump --config /etc/forgejo/app.ini \
--file /var/backups/forgejo-$(date +%F).zip
restic -r sftp:backup@offsite:/forge backup /var/backups/
Run it nightly from a systemd timer. Add scheduled portal backups and a snapshot before each upgrade. Upgrades are a binary swap and a restart, but they also run database migrations, which are one-way.
Sizing
| Team | Plan |
|---|---|
| Individual or up to 5 people, SQLite | Spark |
| Up to 30 people, Postgres, packages registry | Boost |
| 50-plus people, large monorepos, LFS | Turbo or Nitro |
Disk is the figure to watch. Git LFS and container registries grow quietly; check usage monthly and move up a plan before you reach 80 percent full.