Skip to content

use case

Your own Git forge, in London

Forgejo and Gitea are single binaries that give you repositories, pull requests, issues, packages and CI. They run happily on a small VPS you control.

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

TeamPlan
Individual or up to 5 people, SQLiteSpark
Up to 30 people, Postgres, packages registryBoost
50-plus people, large monorepos, LFSTurbo 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.

Questions

Forgejo or Gitea?

Both share a common history and similar features. Forgejo is community-governed and adds features at its own pace; Gitea has a commercial steward. Configuration and migration between them are straightforward, so pick on governance preference.

Can users clone over SSH on port 22?

Yes. Move your admin SSH daemon to another port, or keep it on 22 and let the forge use the built-in SSH server on a different port. Moving admin SSH keeps clone URLs clean.

Can the forge run CI?

Yes. Forgejo Actions and Gitea Actions understand GitHub Actions workflow syntax. Run the act runner on a separate VPS so builds cannot slow down or compromise the forge.

What should I back up?

The repositories directory, the database and the app.ini configuration. The built-in dump command bundles all three into one archive, which you should copy off the server.