Tutorial

How to Self-Host a Git Server with Gitea

Why this matters

GitHub is great until it isn’t — until you need a private repo and don’t want to pay, until a corporation controls your code, or until you’re offline and can’t push. A self-hosted Git server fixes all three.

Gitea is the lightweight choice: it looks and feels like GitHub, runs on a Raspberry Pi, and doesn’t demand a cluster.

What I tested on

  • OS: Debian 13 (trixie)
  • Gitea: 1.22.x
  • Database: SQLite (built-in, no extra setup)
  • Hardware: 2 GB RAM VPS / Raspberry Pi 5
  • Duration: daily driver for 9 months

⚠️ Git over SSH needs port 22. If you run SSH on a non-standard port, configure SSH_PORT in Gitea’s config.

Step 1: Install with Docker

services:
  gitea:
    image: gitea/gitea:1.22
    container_name: gitea
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - DB_TYPE=sqlite3
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:22"

Port 2222 maps container SSH to a host port, so you don’t conflict with the host’s own SSH.

Step 2: First-run setup

Open http://your-server:3000, and Gitea walks you through:

  1. Database type (SQLite is fine for small setups)
  2. Site title and admin account
  3. SSH server port (set to 2222 to match above)
  4. Email settings — skip if you don’t need notifications

Create the admin account immediately; the first registered user becomes an admin by default, so register quickly before someone else does.

Step 3: Git over SSH

Test SSH access:

ssh -p 2222 git@your-server.com

If it says “Hi there, You’ve successfully authenticated,” it works. Add the port to your SSH config so you don’t type it every time:

# ~/.ssh/config
Host gitea
  HostName your-server.com
  User git
  Port 2222

Step 4: Back it up

SQLite is just a file inside the /data volume. Stop Gitea, copy the volume, restart:

docker compose stop gitea
tar czf /backup/gitea-$(date +%F).tar.gz gitea/
docker compose start gitea

Step 5: CI with Gitea Actions

Gitea has a built-in CI system compatible with GitHub Actions syntax. Enable it in app.ini:

[actions]
ENABLED = true

Then add a workflow file in .gitea/workflows/ci.yaml. It’s not as rich as GitHub’s runner ecosystem, but it covers builds and tests.

Backups that actually restore

A git server holds two very different kinds of data: the repositories, which are just files, and the forge database, which holds issues, users, keys and webhooks. Copying repos/ alone gets you the code and loses everything that makes it a forge.

The supported path is a dump, and it produces one file you can move anywhere:

docker exec -u git gitea gitea dump -c /data/gitea/conf/app.ini -f /data/dump.zip

What matters after that is the same thing that matters for every backup on this site: restore it somewhere else and check that a clone works. I do this quarterly into a throwaway container, clone one repo over SSH, open one issue. Ten minutes, and it is the only test that counts — the general version of this habit is in the backup guide.

Two details that catch people:

  • The dump is not incremental. It is a full archive, so a 20 GB instance produces a 20 GB file every run. Keep the last two and rotate, or accept the disk cost.
  • Uploaded release assets and LFS objects may live outside the default dump scope depending on your paths. If you use either, back up those directories explicitly.

SSH access and the two things it breaks

Git over SSH is the part that works for a decade and then fails during an upgrade, usually because of the container port.

If Gitea runs in Docker, the container’s SSH port 22 is published as something else on the host — 2222 in the compose file above. The clone URL the UI shows includes that port, and it is correct. It breaks when:

Someone clones with a URL from another host. The same repository has one URL for HTTPS and one for SSH, and pasting a GitHub-style git@host:user/repo.git silently ignores the port and hangs on 22.

known_hosts keeps a stale key. Rebuilding the container regenerates host keys unless you persist /data/ssh. Then every clone warns about a changed fingerprint, which trains people to type yes without reading — a habit worth not teaching.

To make the port stick, either publish 2222:22 and let the UI advertise it (SSH_PORT=2222 plus the external domain setting), or move the host’s own SSH to a non-standard port and give 22 to the container. Pick one deliberately; half-configuring both is how you lock yourself out of the box that hosts your code.

If you are weighing this against the fork that shares the same codebase, the Gitea vs Forgejo comparison covers what actually differs.

Pros and cons

Pros:

  • Familiar GitHub-like interface
  • Runs on very modest hardware
  • Full control over your code and issues
  • Built-in CI, wiki, and pull requests

Cons:

  • Smaller plugin ecosystem than GitHub
  • You maintain it — updates, backups, uptime
  • CI runners are less feature-complete than GitHub’s

Best for / Skip if

Best for: developers who want private repos without paying, or who care about data sovereignty.

Skip if: you only host open source. GitHub is free for public repos and has the better ecosystem.

Alternatives

  • Forgejo — a community fork of Gitea, governance-focused
  • GitLab CE — vastly more powerful, but heavy; needs 4+ GB RAM
  • Plain Git + SSH — a bare repo and a few scripts; sometimes enough

Verdict

Gitea hits a sweet spot: enough features to replace GitHub for a small team, light enough to run on a Pi, and simple enough that maintenance is measured in minutes per month. If you’ve been thinking about self-hosting code, this is the easiest on-ramp.

References

FAQ

Can I import from GitHub?

Yes, Gitea has a built-in GitHub importer for repos, issues, and pull requests.

Is Gitea secure?

Keep it updated, put it behind a reverse proxy with HTTPS, and don't expose the SSH port wider than needed.

How big is a Gitea install?

Around 100 MB idle, plus your repos. A Pi 4 handles it easily.

Can I use it for commercial projects?

Yes, the MIT license permits it.

Henry Xiang

I keep self-hosted services alive for a living — backups, docs, monitoring, the boring parts that break at 3am. Everything here runs on my own hardware first.