Host your own Git with Forgejo
Forgejo is a self-hosted Git service: repositories, issues, pull requests and a web UI that will feel familiar if you've used GitHub. It's a community-run fork of Gitea, it ships as a single container, and with SQLite as the database it's comfortable on a small VPS. I keep private projects and config repos on mine.
This builds on the Docker and Caddy setup. Start there if you haven't got Caddy running yet.
Add it to your compose file
forgejo:
image: codeberg.org/forgejo/forgejo:CHANGE_ME_MAJOR_VERSION
restart: unless-stopped
mem_limit: 768m
environment:
USER_UID: "1000"
USER_GID: "1000"
FORGEJO__server__DOMAIN: "git.example.com"
FORGEJO__server__ROOT_URL: "https://git.example.com/"
FORGEJO__server__DISABLE_SSH: "true"
FORGEJO__service__DISABLE_REGISTRATION: "true"
FORGEJO__security__INSTALL_LOCK: "true"
volumes:
- forgejo:/data
Add forgejo: under volumes: at the bottom of the file. Replace the image tag with the current major version from the Forgejo releases page. A major-version tag follows that release line's updates without jumping to the next major on its own, which is what you want (more on that in the updating containers post). Avoid latest for something that holds your code.
A few things worth knowing about that block:
- The
FORGEJO__section__KEYvariables map straight ontoapp.inisettings, so you don't edit a config file by hand. - With no
DB_TYPEset, Forgejo uses SQLite. That's fine for a personal server or a small team. INSTALL_LOCKskips the web installer. Without it, the first visitor to the site gets to configure the instance, which is not a race you want to run.- There's no
ports:section. Only Caddy publishes ports.
Add it to Caddy
git.example.com {
reverse_proxy forgejo:3000
}
docker compose up -d docker compose exec -w /etc/caddy caddy caddy reload
Point a DNS record for git.example.com at your server first, or Caddy can't get a certificate. Watch docker compose logs forgejo for errors on the first start. If you see permission errors on /data, the USER_UID and USER_GID values are the first thing to check.
Create the admin account
Because registration is closed and the installer is locked, create the first user from the command line:
docker compose exec -u git forgejo forgejo admin user create \ --admin --username YOUR_NAME --email [email protected] \ --password CHANGE_ME --must-change-password
That password ends up in your shell history, so treat it as temporary: --must-change-password forces a new one at first login. Log in at https://git.example.com, set a real password, and turn on two-factor authentication in your account settings.
Registration lockdown
We already set DISABLE_REGISTRATION to true, so nobody can sign up. To add someone, create them as an admin from the Site Administration page or with the same CLI command (without --admin). If you'll host repositories only for yourself, consider also making the whole instance sign-in-only. Setting FORGEJO__service__REQUIRE_SIGNIN_VIEW to "true" hides even public repositories from anonymous visitors, which keeps crawlers out.
Pushing over HTTPS
With SSH disabled, you use HTTPS for everything. In Forgejo, go to Settings, Applications and generate an access token, then use it as the password when git asks:
git remote add origin https://git.example.com/YOUR_NAME/project.git git push -u origin main
A credential helper (git config --global credential.helper store, or your OS keychain) saves you retyping it. Store tokens with the narrowest scope that works.
The SSH caveat
Git over SSH is nicer day to day, but it's the awkward part of this setup, for two reasons:
- Caddy proxies HTTP, not SSH. The Git SSH traffic can't go through the reverse proxy. If you want it, the container's SSH port has to be published to the host, which breaks the "only Caddy publishes ports" rule. Do it deliberately.
- Port 22 is already taken by the server's own SSH daemon. So the container's SSH has to listen on a different host port, such as 2222.
If you accept that, remove DISABLE_SSH and change the Forgejo service like this:
ports:
- "2222:22"
environment:
FORGEJO__server__SSH_PORT: "2222"
FORGEJO__server__SSH_DOMAIN: "git.example.com"
sudo ufw allow 2222/tcp
Remember that Docker bypasses ufw for published ports, so the ufw rule above documents your intent but it's the ports: line that actually opens it. Clone URLs then look like ssh://[email protected]:2222/YOUR_NAME/project.git. Add your public key under Settings, SSH / GPG Keys. This SSH access is separate from your own login to the server, but the same advice applies: keys only, and see the SSH hardening post.
If you'd rather not open another port, stick with HTTPS and tokens. It's slightly less convenient and one less thing exposed.
Keeping RAM low
Forgejo is light when idle and spikes on large pushes and repository indexing. A mem_limit of 768m is a reasonable starting point for a few users, but watch docker stats for a week and tune it, as in the RAM budget post. If you do outgrow SQLite, Forgejo supports PostgreSQL. Run a single Postgres for all your apps rather than one per app, and set FORGEJO__database__DB_TYPE, HOST, NAME, USER and PASSWD (use a CHANGE_ME password you generate yourself). Most personal setups never need to.
Back it up
Your repositories, database, attachments and config all live in the forgejo volume. Forgejo has a built-in dump command that produces one consistent archive:
docker compose exec -u git forgejo forgejo dump -c /data/gitea/conf/app.ini -f /data/forgejo-dump.zip
Run it before your nightly restic backup and delete the old zip afterwards, or the dumps pile up inside the volume. Then, as with any backup, try restoring it on a scratch machine once. A repository you can't restore is not a backup, even though every clone on your laptop is a copy of the code itself (issues and pull requests are not).
When to use it
Forgejo is a good fit when you want private repositories and CI-free simple hosting under your control. If you need heavy CI runners, you'll want a separate machine for them. If you just want a backup mirror of GitHub projects, Forgejo can mirror repositories on a schedule. Unsure whether your plan has the headroom? Check the VPS size calculator.
Want one login for Forgejo and your other apps? Forgejo can use an external OIDC provider; Pocket ID with Docker is a light way to do it.