Take a Breath. You Probably Don’t Need GitLab CE
Forgejo is the right default for solo developers and small teams, and GitLab CE only earns its keep once you have 50 or more developers who need CI/CD, a registry, and audit logging in one box.
Take a breath. Close those tabs. You’ve spent two hours reading four-year-old Reddit threads that are somehow still being argued about, and you still haven’t picked a self-hosted git platform.
Here’s the verdict up front. Forgejo is the right default for solo developers, home labs, and small teams: light footprint, community-governed, and it’ll outlive whatever drama hits its upstream next. Gitea is fine if you’re already on it and don’t care about fork politics. GitLab CE only earns its keep once you’ve got 50 or more developers who need unified CI/CD, a container registry, and audit logging in one box, plus the RAM budget to feed it.
Now let’s get into why any of this matters and how to actually run these things.
Why Self-Host Git At All?
GitHub is free, reliable, and everyone already has an account. So why bother running your own?
- Data ownership. Your code, your server, your rules. No scanning for “safety,” no terms-of-service surprises.
- CI/CD ownership. No surprise billing because a workflow ran 3,001 minutes this month.
- Compliance and isolation. Defense contractors, air-gapped networks, and anyone with data residency requirements need this by law, not by preference.
- Vendor lock-in avoidance. GitHub Actions syntax is GitHub-specific. Your self-hosted runner config isn’t going anywhere.
- The home lab itch. Some of us just want more things running on our servers. No shame in it.
If you’re storing dotfiles and a couple of side projects, GitHub will serve you fine forever. If you’ve got a home lab, a small team, or a philosophical objection to renting your infrastructure from someone else, read on. You’ve got three serious contenders: GitLab CE is the kitchen sink, Gitea is the nimble workhorse, and Forgejo is Gitea’s community-governed fork that’s quietly becoming the safer long-term bet.
Gitea: The Lightweight Workhorse
Gitea forked from Gogs (a simpler git service) in 2016. It’s a single Go binary that runs comfortably in 512 MB of RAM by official recommendation, though a small instance idles closer to 50 to 100 MB in practice. Your Raspberry Pi handles it fine. Your $5 VPS handles it fine.
What you get:
- Repository hosting, PR/MR workflows, webhooks
- Built-in issue tracking, wiki, and Git LFS
- Gitea Actions for CI (built in, but you still need to register a runner)
- A built-in container/package registry
- Simple web UI, zero bloat
What you don’t:
- SAST scanning, dependency checks, or other security scanning features
- Enterprise SSO out of the box (LDAP works fine, OAuth is basic)
- A company-neutral governance story
In 2022, that last point turned into a real problem. Gitea’s original maintainer set up a company to commercially back the project, and a chunk of long-time contributors worried the roadmap would drift toward features that needed beefier infrastructure and less community input. That friction is what produced Forgejo.
Forgejo: The Community Fork That Actually Matters
Forgejo hard-forked from Gitea in late 2022 and is governed by the Codeberg e.V. nonprofit instead of a company. No single vendor steers the roadmap.
At the code level, Forgejo and Gitea are nearly identical today: same ~50 to 100 MB footprint, same UI, same Docker image swap. What’s different is governance and a few features Forgejo has pushed further than upstream, including early work on ActivityPub federation, letting your instance interact with other Forgejo/Codeberg instances the way Mastodon servers talk to each other. It’s early, but it’s a genuinely interesting direction for decentralized code hosting that neither Gitea nor GitLab is chasing.
The practical reality: if you’re starting fresh, Forgejo is the safer pick. If you’re already on Gitea and happy, migrating later is painless, we’ll cover it below.
GitLab CE: The Full-Stack Freight Train
GitLab Community Edition is a full DevOps platform that happens to include git hosting: integrated CI/CD, container registry, security scanning (the good stuff is gated behind paid tiers, but the CE foundation is there), advanced LDAP/SAML SSO, Kubernetes integration, and audit logging.
What it costs:
- Memory: 4 GB minimum, 8+ GB recommended for a comfortable single-server install with active CI/CD. Heavier workloads climb well past that.
- Disk: 200+ GB depending on repos and artifacts.
- CPU: 4+ cores recommended.
- Dependencies: PostgreSQL, Redis, and Gitaly (its file server) all running alongside the Rails app.
GitLab CE is not a single binary. It’s a distributed system pretending to be an all-in-one tool, and if you don’t tune it, it will happily consume every byte of RAM on your server and stare at you blankly when you ask why.
Feature and Resource Comparison
| Feature | Gitea | Forgejo | GitLab CE |
|---|---|---|---|
| Git hosting | ✅ | ✅ | ✅ |
| PR/MR workflows | ✅ | ✅ | ✅ |
| CI/CD | Gitea Actions (built in) | Forgejo Actions (built in) | Full GitLab CI |
| Container/package registry | ✅ built in | ✅ built in | ✅ built in |
| Security scanning | ❌ | ❌ | 🟡 baseline in CE, full SAST is paid tiers |
| LDAP/SAML | 🟡 basic | 🟡 basic | ✅ mature |
| Federation (ActivityPub) | ❌ | 🟡 in progress | ❌ |
| Idle memory | ~50-100 MB | ~50-100 MB | 4 GB+ |
| Recommended RAM | 512 MB | 512 MB | 8 GB+ |
| Docker image size | ~100 MB | ~100 MB | 2 GB+ |
| Governance | Company-backed | Nonprofit (Codeberg e.V.) | Company-backed |
| Setup complexity | Low | Low | High |
Production Forgejo Setup (Docker Compose)
Here’s a production-ready Forgejo stack with PostgreSQL and a health check:
services: db: image: postgres:16-alpine container_name: forgejo-db environment: POSTGRES_DB: forgejo POSTGRES_USER: forgejo POSTGRES_PASSWORD: ${DB_PASSWORD:-change_me_please} volumes: - db_data:/var/lib/postgresql/data restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U forgejo"] interval: 10s timeout: 5s retries: 5
forgejo: image: codeberg.org/forgejo/forgejo:latest container_name: forgejo environment: USER_UID: 1000 USER_GID: 1000 FORGEJO__database__DB_TYPE: postgres FORGEJO__database__HOST: db:5432 FORGEJO__database__NAME: forgejo FORGEJO__database__USER: forgejo FORGEJO__database__PASSWD: ${DB_PASSWORD:-change_me_please} FORGEJO__server__ROOT_URL: https://git.example.com/ FORGEJO__server__DOMAIN: git.example.com FORGEJO__service__DISABLE_REGISTRATION: "false" volumes: - forgejo_data:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ports: - "3000:3000" - "2222:22" depends_on: db: condition: service_healthy restart: unless-stopped
volumes: db_data: forgejo_data:export DB_PASSWORD=$(openssl rand -base64 16)docker compose up -ddocker compose logs -f forgejoThe first user who registers becomes admin. Then set DISABLE_REGISTRATION: "true" to close signups, put a reverse proxy in front on 443, and open port 2222 for SSH cloning. Gitea’s Compose file is nearly identical, just swap the image to gitea/gitea:latest and the env prefix to GITEA__.
If you’re going the GitLab CE route instead, tune it or it will eat your server:
services: gitlab: image: gitlab/gitlab-ce:latest container_name: gitlab hostname: gitlab.example.com restart: unless-stopped environment: GITLAB_OMNIBUS_CONFIG: | external_url 'https://gitlab.example.com' puma['worker_processes'] = 2 sidekiq['max_concurrency'] = 5 prometheus_monitoring['enable'] = false gitlab_rails['env'] = { 'MALLOC_CONF' => 'dirty_decay_ms:1000,muzzy_decay_ms:1000' } ports: - "80:80" - "443:443" - "2222:22" volumes: - ./gitlab-config:/etc/gitlab - ./gitlab-logs:/var/log/gitlab - ./gitlab-data:/var/opt/gitlab shm_size: "256m" deploy: resources: limits: memory: 6gDisabling Prometheus monitoring and capping Puma workers and Sidekiq concurrency is the difference between GitLab behaving on a modest server and GitLab quietly becoming the only thing your server does.
Pointing Existing Repos at Your New Server
Once it’s running, moving existing repos over is just a remote swap:
git remote add selfhosted http://your-server:3000/username/repo.git# or replace origin entirelygit remote set-url origin http://your-server:3000/username/repo.git
git push selfhosted --allgit push selfhosted --tagsMigration Paths
Gitea to Forgejo: the easiest migration you’ll do all year. Stop the Gitea container, point the same data volume at a Forgejo container, start it. They share a database schema and read each other’s data without conversion.
GitHub or GitLab into Gitea or Forgejo: both ship a built-in migration tool. Go to “New Migration,” pick your source, and it imports repos, issues, PRs, and wikis. Not perfect for large orgs with heavy CI history, but genuinely smooth for personal projects and small teams.
Gitea or Forgejo into GitLab: manual work. There’s no equivalent import wizard, so plan on scripting issue recreation against GitLab’s API if you go this direction.
Which One Should You Actually Pick?
Pick Forgejo if: you’re running 1 to 50 developers, your server has under 4 GB of RAM, you want a single-binary ops-light solution, and you care about who controls the roadmap.
Pick Gitea if: you’re already on it and happy, or you want the identical lightweight product without following the fork’s governance drama.
Pick GitLab CE if: you have 50+ developers, the RAM budget to match, and you actually need SAST scanning, advanced audit logs, or enterprise SSO baked into the same box as your git hosting.
The Real Talk
Most self-hosters don’t need GitLab CE. They’ll spin up a cluster, allocate 16 GB of RAM, manage PostgreSQL backups, and use maybe 10% of what they installed. Forgejo or Gitea covers 90% of use cases at a fraction of the ops burden, and it boots in seconds on hardware that would make GitLab CE weep.
GitLab CE wins when you have a team large enough that unifying git, CI/CD, and registry into one system actually saves you from wiring disparate tools together. Otherwise you’re paying a complexity tax for features gathering dust.
Start with Forgejo if you’re not sure. It’s the lowest-risk entry point, and migrating up to GitLab later is always on the table. Your 2 AM self will thank you when your git platform boots in seconds and uses less RAM than your browser.