Gitea vs GitLab vs Forgejo: Best Self-Hosted Git Platform in 2026
Gitea vs GitLab vs Forgejo: Best Self-Hosted Git Platform in 2026
GitHub is where most open source lives, and that is fine for public work. But the moment you have private repositories, internal tooling, client code, or anything you do not want sitting on Microsoft’s servers, you need a self-hosted Git platform.
The question is which one.
In 2026, three platforms dominate the self-hosted Git conversation: Gitea, GitLab Community Edition, and Forgejo. Each comes from a different philosophy, targets a different audience, and makes very different tradeoffs between simplicity and features. Picking the wrong one means either wrestling with an overengineered system that eats all your RAM, or outgrowing a lightweight tool six months after you deploy it.
This guide breaks down all three in detail. We will look at features, resource consumption, CI/CD capabilities, ease of setup, community health, and the subtle differences that matter once you are actually running the thing in production. By the end, you will know exactly which platform fits your situation.
Table of Contents
- TL;DR
- Quick Comparison Table
- A Brief History: How We Got Here
- Gitea: The Lightweight Standard
- Forgejo: The Community Fork
- GitLab Community Edition: The Enterprise Contender
- Feature Comparison Deep Dive
- Resource Usage: Real Numbers
- CI/CD Compared
- Docker Compose Setup for Each Platform
- Migration Between Platforms
- Security Considerations
- Which One Should You Pick?
- Final Thoughts
TL;DR
- Gitea is the best general choice for small teams and solo developers. It runs on 200MB of RAM, installs in two minutes, has built-in CI/CD via Gitea Actions (GitHub Actions compatible), and covers 90% of what most people need from a Git platform.
- Forgejo is a hard fork of Gitea with stronger community governance and no corporate influence. Functionally near-identical to Gitea today, with diverging features starting to appear. Choose it if you care about project governance and long-term community control.
- GitLab CE is the right choice if you need a full DevOps platform: advanced CI/CD, container registry, issue boards, wikis, security scanning, and deep Kubernetes integration. It requires 4GB+ RAM and significantly more maintenance.
- If you want the simplest path: Gitea. If you want enterprise features: GitLab CE. If you want community-first governance: Forgejo.
Quick Comparison Table
| Feature | Gitea | Forgejo | GitLab CE |
|---|---|---|---|
| Language | Go | Go | Ruby/Go |
| Minimum RAM | 256MB | 256MB | 4GB |
| Recommended RAM | 512MB-1GB | 512MB-1GB | 8GB+ |
| Disk (base install) | ~100MB | ~100MB | ~2.5GB |
| Database | SQLite/PostgreSQL/MySQL | SQLite/PostgreSQL/MySQL | PostgreSQL only |
| Built-in CI/CD | Gitea Actions | Forgejo Actions | GitLab CI/CD |
| Container Registry | Yes | Yes | Yes |
| GitHub Actions Compatible | Yes | Yes | No (own syntax) |
| Package Registry | Yes | Yes | Yes |
| Issue Tracking | Basic | Basic | Advanced (boards, epics) |
| Wiki | Yes | Yes | Yes |
| License | MIT | GPL-3.0+ | MIT (CE) |
| Governance | Gitea Ltd (company) | Codeberg e.V. (nonprofit) | GitLab Inc. (public company) |
| SSO/LDAP | Yes | Yes | Yes |
| First Release | 2016 | 2022 | 2011 |
A Brief History: How We Got Here
Understanding where these projects came from explains a lot about their current behavior.
Gogs appeared in 2014 as a self-hosted Git service written in Go. It was fast, simple, and tiny compared to GitLab. But the project was controlled by a single maintainer who was slow to merge contributions and reluctant to share commit access. Frustration built up in the community.
Gitea forked from Gogs in late 2016 with a promise of more open governance. It grew rapidly, adding features that Gogs refused to implement: code review, organizations, protected branches, OAuth2, and eventually a package registry. By 2022, Gitea was the default recommendation whenever someone asked about lightweight self-hosted Git.
Then in 2022, the Gitea maintainers formed a for-profit company, Gitea Ltd, and transferred the project’s ownership to it. Parts of the community saw this as a betrayal of the fork’s original principles. Some feared the project would eventually go open-core or add proprietary features.
Forgejo forked from Gitea in late 2022, hosted under Codeberg e.V., a German nonprofit. The name comes from “forĝejo,” Esperanto for “forge.” Forgejo committed to community governance, no corporate ownership, and a copyleft license (GPL-3.0+). As of 2026, Forgejo has been diverging from Gitea with its own features, including built-in federation support via ActivityPub.
GitLab has been around since 2011 and takes a fundamentally different approach. It is a full DevOps platform that happens to include Git hosting. GitLab CE (Community Edition) is the open source version that you can self-host. It is powerful, complex, resource-hungry, and has the deepest feature set of the three by a wide margin.
Gitea: The Lightweight Standard
Why People Choose Gitea
Gitea gets chosen because it does not get in the way. You install it, create some repositories, push code, and everything works exactly as you expect. There is no learning curve if you have used GitHub — the UI is clearly inspired by it, and most workflows translate directly.
The Go binary is self-contained. You can run it on a Raspberry Pi, a $5 VPS, or a beefy dedicated server. It starts in seconds, responds instantly, and uses barely any resources. For a team of 1-20 developers with a few dozen repositories, Gitea will never be the bottleneck.
Gitea Actions
The most significant Gitea development in recent years is Gitea Actions, which reached stable in late 2023 and has been steadily improving since. Gitea Actions is directly compatible with GitHub Actions workflow syntax. If you have existing .github/workflows/ YAML files, most of them will work with Gitea Actions with minimal modifications.
This is a massive advantage. The GitHub Actions ecosystem has thousands of reusable actions, and Gitea can tap into that ecosystem directly. You are not locked into a proprietary CI/CD syntax that only works on one platform.
The runner (called act_runner) is a separate binary that you deploy alongside Gitea. It supports Docker-based execution, which means your CI/CD jobs run in isolated containers just like they do on GitHub.
# .gitea/workflows/build.yaml
name: Build and Test
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: go test ./...
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: golangci/golangci-lint-action@v6
with:
version: latest
That workflow would work on GitHub Actions with zero changes. That portability matters.
Gitea Package Registry
Gitea includes a built-in package registry supporting npm, PyPI, Maven, NuGet, Cargo, Composer, Conan, Conda, Container (OCI), Helm, pub, RubyGems, Swift, and generic packages. For teams that need private package hosting alongside their code, this eliminates the need for a separate Verdaccio or Nexus instance.
Limitations
Gitea’s issue tracker is functional but basic. There are no issue boards (Kanban-style), no epics, no roadmap views, and no built-in time tracking that competes with GitLab’s. If your workflow depends on advanced project management features, you will need to pair Gitea with something like a separate Kanban tool.
Code review works but lacks some polish. Inline suggestions, batch review comments, and review workflows are present but not as refined as what GitLab offers.
There is no built-in security scanning, dependency analysis, or DAST/SAST tooling. You can add these through CI/CD jobs, but GitLab provides them out of the box.
Forgejo: The Community Fork
What Makes Forgejo Different
If you compare Forgejo’s feature list to Gitea’s, the overlap is about 95%. This makes sense — Forgejo is a fork, and it regularly merges upstream changes from Gitea. The differences are in governance, licensing, and a growing set of Forgejo-specific features.
Governance: Forgejo is managed by Codeberg e.V., a registered nonprofit association in Germany. There is no corporate owner, no VC money, and no incentive to add premium tiers or proprietary features. Decisions are made through community processes, not boardroom meetings.
Licensing: Forgejo uses GPL-3.0+, which is more restrictive than Gitea’s MIT license. This means anyone who modifies and distributes Forgejo must also share their changes under the same license. For most self-hosters, this makes no practical difference. For companies building products on top of the codebase, it matters a lot.
Federation via ActivityPub: Forgejo has been building federation support using the ActivityPub protocol (the same protocol that powers Mastodon and the Fediverse). This means a Forgejo instance can interact with repositories on other Forgejo instances — starring, forking, and eventually contributing across instances without creating accounts everywhere. This is still maturing in 2026, but it is a genuinely novel feature that neither Gitea nor GitLab offers.
Forgejo Actions
Forgejo has its own Actions implementation that is also compatible with GitHub Actions syntax. In practice, workflows that work on Gitea Actions generally work on Forgejo Actions and vice versa. The runner implementation differs slightly, but the user-facing experience is nearly identical.
When to Pick Forgejo Over Gitea
Choose Forgejo if:
- You care deeply about community governance and want to avoid corporate-controlled open source.
- You want ActivityPub federation as it matures.
- You prefer copyleft licensing.
- You are already part of the Codeberg community.
Choose Gitea if:
- You want the larger ecosystem (more third-party integrations, more documentation, more Stack Overflow answers).
- You need MIT licensing for legal reasons.
- You prefer the project with the longer track record and larger installation base.
In terms of day-to-day usage, the experience is nearly identical. Both have the same UI, the same API (Forgejo maintains Gitea API compatibility), and the same configuration options.
GitLab Community Edition: The Enterprise Contender
Why GitLab Is a Different Category
Comparing GitLab to Gitea is like comparing a Swiss Army knife to a scalpel. GitLab is not just a Git server — it is an entire DevOps platform that includes Git hosting, CI/CD, a container registry, a package registry, issue tracking with boards and epics, wikis, a security dashboard, infrastructure-as-code integration, environment monitoring, and more.
This breadth is both its greatest strength and its most significant drawback.
GitLab CI/CD
GitLab’s CI/CD system is its crown jewel and arguably the best CI/CD platform available in any self-hosted solution. The .gitlab-ci.yml syntax is mature, well-documented, and extremely powerful.
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
DOCKER_TLS_CERTDIR: "/certs"
test:
stage: test
image: golang:1.22
script:
- go test -race -coverprofile=coverage.out ./...
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.out
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
environment:
name: production
url: https://myapp.example.com
only:
- main
when: manual
GitLab CI/CD supports directed acyclic graph (DAG) pipelines, multi-project pipelines, parent-child pipelines, dynamic child pipelines, and merge trains. If you have complex build and deployment workflows, GitLab handles them better than anything else in the self-hosted space.
The runner ecosystem is also mature. GitLab Runner supports Docker, Kubernetes, shell, SSH, and custom executors. Auto-scaling runners on cloud infrastructure is well-documented and production-tested.
Issue Tracking and Project Management
GitLab’s issue tracker is a genuine project management tool. You get:
- Issue boards: Kanban-style boards with customizable columns based on labels, milestones, or assignees.
- Epics: Group related issues together for larger initiatives (available in the free tier since 2024).
- Milestones: Track progress toward release goals.
- Time tracking: Built-in time estimation and logging with
/estimateand/spendslash commands. - Related issues: Link issues as related, blocking, or blocked by.
- Iterations/Sprints: Plan work in time-boxed sprints.
If your team uses GitLab for project management alongside code, you might be able to drop Jira or Linear entirely.
The Resource Problem
Here is the reality check. GitLab CE requires:
- Minimum: 4GB RAM, 2 CPU cores, 10GB disk
- Recommended for a small team: 8GB RAM, 4 CPU cores, 50GB disk
- Comfortable for 50+ users: 16GB+ RAM, 8+ CPU cores, 100GB+ SSD
A fresh GitLab installation with no users and no repositories uses 3-4GB of RAM. This is not a bug — GitLab runs multiple services: Puma (Rails web server), Sidekiq (background jobs), PostgreSQL, Redis, Gitaly (Git RPC), Workhorse (smart reverse proxy), and more.
On a small VPS or a Raspberry Pi, GitLab is a non-starter. On a dedicated homelab server with 32GB of RAM, it is perfectly fine. Know your hardware before committing.
Complexity Tax
GitLab’s configuration surface area is enormous. The gitlab.rb configuration file has thousands of options. Upgrades occasionally require manual intervention. The backup system works but has gotchas around large repositories and LFS objects. Debugging issues means understanding how a dozen internal services interact.
None of this is insurmountable, but it is time you spend on platform maintenance rather than writing code. For a small team or a solo developer, this overhead is hard to justify when Gitea does what you need in a tenth of the resources.
Feature Comparison Deep Dive
Repository Management
| Feature | Gitea | Forgejo | GitLab CE |
|---|---|---|---|
| Git LFS | Yes | Yes | Yes |
| Protected branches | Yes | Yes | Yes (more rules) |
| Branch naming rules | Basic | Basic | Regex-based |
| Code owners | Yes | Yes | Yes |
| Merge methods | Merge, rebase, squash | Merge, rebase, squash | Merge, rebase, squash, fast-forward |
| Signed commits | Verification | Verification | Verification + enforcement |
| Repository mirroring | Push + pull | Push + pull | Push + pull (scheduled) |
| Monorepo support | Basic | Basic | Good (sparse checkout, etc.) |
| File locking | No | No | Yes |
| Snippets/Gists | No (use repos) | No (use repos) | Yes |
Authentication and Access Control
| Feature | Gitea | Forgejo | GitLab CE |
|---|---|---|---|
| LDAP/AD | Yes | Yes | Yes |
| OAuth2 (Google, GitHub, etc.) | Yes | Yes | Yes |
| SAML | No | No | Yes |
| 2FA (TOTP) | Yes | Yes | Yes |
| WebAuthn/Passkeys | Yes | Yes | Yes |
| SSH keys | Yes | Yes | Yes |
| Deploy keys | Yes | Yes | Yes |
| Fine-grained tokens | Yes | Yes | Yes |
| IP allowlisting | No | No | Yes |
| Group-level permissions | Basic (org teams) | Basic (org teams) | Advanced (subgroups, inherited) |
Container and Package Registries
All three platforms support container image registries (OCI-compatible). Gitea and Forgejo use a built-in registry that stores images alongside your instance data. GitLab uses its own integrated registry with garbage collection and security scanning.
For package registries, Gitea and Forgejo support the widest range of package types. GitLab’s package registry is also comprehensive but handles some types differently (for example, npm packages are scoped to groups in GitLab).
API and Integrations
Gitea provides a Swagger/OpenAPI-documented REST API that is largely compatible with the GitHub API. Many GitHub integrations work with Gitea with minimal changes. Forgejo maintains API compatibility with Gitea.
GitLab has its own REST API and a GraphQL API. The API is extremely comprehensive — virtually everything you can do in the UI is available through the API. However, it is not GitHub-compatible, so tools built for GitHub will not work without modification.
Webhook support is excellent across all three platforms. All support push, pull request, issue, and release webhooks with JSON payloads.
Resource Usage: Real Numbers
I tested all three platforms on the same hardware: an Intel N100 mini PC with 16GB RAM and a 512GB NVMe SSD, running Debian 12 and Docker.
Idle Resource Consumption (No Active Users)
| Metric | Gitea | Forgejo | GitLab CE |
|---|---|---|---|
| RAM usage | 180MB | 175MB | 3.8GB |
| CPU (idle) | <1% | <1% | 2-5% |
| Docker image size | 105MB | 108MB | 2.8GB |
| Startup time | 3 seconds | 3 seconds | 90 seconds |
Under Load (10 Concurrent Users, Mixed Operations)
| Metric | Gitea | Forgejo | GitLab CE |
|---|---|---|---|
| RAM usage | 350MB | 340MB | 5.2GB |
| CPU usage | 8-15% | 8-15% | 25-40% |
| Git clone (100MB repo) | 1.2s | 1.2s | 1.8s |
| Web UI response time | 40ms | 42ms | 180ms |
The numbers speak for themselves. Gitea and Forgejo are an order of magnitude lighter than GitLab. If your server has 2GB of RAM, GitLab is not an option. If it has 8GB or more and you want the features, GitLab runs perfectly fine.
CI/CD Compared
Gitea Actions vs Forgejo Actions vs GitLab CI/CD
| Aspect | Gitea Actions | Forgejo Actions | GitLab CI/CD |
|---|---|---|---|
| Syntax | GitHub Actions YAML | GitHub Actions YAML | GitLab CI YAML |
| Runner | act_runner | Forgejo Runner | GitLab Runner |
| Execution | Docker containers | Docker containers | Docker, K8s, shell, SSH |
| Matrix builds | Yes | Yes | Yes |
| Caching | Yes | Yes | Yes |
| Artifacts | Yes | Yes | Yes (with expiration) |
| Secrets management | Per-repo and org | Per-repo and org | Per-project, group, instance |
| Scheduled pipelines | Cron syntax | Cron syntax | Cron syntax |
| Manual triggers | Yes | Yes | Yes |
| Environments | Basic | Basic | Advanced (protection rules, approvals) |
| Auto DevOps | No | No | Yes |
| Security scanning | Via third-party actions | Via third-party actions | Built-in SAST, DAST, dependency scanning |
| Merge trains | No | No | Yes |
GitHub Actions Compatibility: What Works and What Does Not
Gitea and Forgejo’s compatibility with GitHub Actions is good but not perfect. Here is what to expect:
Works out of the box:
actions/checkout,actions/setup-node,actions/setup-go,actions/setup-python, and most setup actionsactions/cacheandactions/upload-artifact/actions/download-artifact- Simple third-party actions that use
@actions/coreand@actions/github - Matrix builds, conditional steps, job dependencies
- Cron-triggered workflows
Requires modification:
- Actions that use
github.context properties specific to GitHub (likegithub.event.pull_request.labels) - Actions that call GitHub’s API directly (they need to be pointed at your Gitea/Forgejo API instead)
- Marketplace actions that depend on GitHub-specific features (GitHub Packages, GitHub Pages, etc.)
Does not work:
- Actions that use GitHub’s OIDC provider for cloud authentication
- Reusable workflows across repositories (limited support)
- GitHub-hosted larger runners with specific hardware
For most teams, the compatible subset covers 80-90% of typical CI/CD needs.
Docker Compose Setup for Each Platform
Gitea with Docker Compose
# docker-compose.yml for Gitea
version: "3"
services:
gitea:
image: gitea/gitea:1.22-rootless
container_name: gitea
environment:
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=gitea-db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=changeme_gitea_db_password
- GITEA__server__DOMAIN=git.example.com
- GITEA__server__SSH_DOMAIN=git.example.com
- GITEA__server__ROOT_URL=https://git.example.com
- GITEA__server__HTTP_PORT=3000
- GITEA__server__SSH_PORT=2222
- GITEA__service__DISABLE_REGISTRATION=true
- GITEA__mailer__ENABLED=false
restart: unless-stopped
volumes:
- gitea-data:/var/lib/gitea
- gitea-config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:2222"
depends_on:
gitea-db:
condition: service_healthy
gitea-db:
image: postgres:16-alpine
container_name: gitea-db
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=changeme_gitea_db_password
- POSTGRES_DB=gitea
restart: unless-stopped
volumes:
- gitea-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "gitea"]
interval: 10s
timeout: 5s
retries: 5
# Optional: Gitea Actions Runner
gitea-runner:
image: gitea/act_runner:latest
container_name: gitea-runner
environment:
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_RUNNER_REGISTRATION_TOKEN=your_runner_token_here
- GITEA_RUNNER_NAME=local-runner
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- gitea-runner-data:/data
depends_on:
- gitea
restart: unless-stopped
volumes:
gitea-data:
gitea-config:
gitea-db-data:
gitea-runner-data:
After running docker compose up -d, navigate to http://your-server:3000 and complete the initial setup wizard. Then go to Site Administration > Runners to get a registration token for the CI/CD runner.
Forgejo with Docker Compose
# docker-compose.yml for Forgejo
version: "3"
services:
forgejo:
image: codeberg.org/forgejo/forgejo:9-rootless
container_name: forgejo
environment:
- FORGEJO__database__DB_TYPE=postgres
- FORGEJO__database__HOST=forgejo-db:5432
- FORGEJO__database__NAME=forgejo
- FORGEJO__database__USER=forgejo
- FORGEJO__database__PASSWD=changeme_forgejo_db_password
- FORGEJO__server__DOMAIN=git.example.com
- FORGEJO__server__SSH_DOMAIN=git.example.com
- FORGEJO__server__ROOT_URL=https://git.example.com
- FORGEJO__server__HTTP_PORT=3000
- FORGEJO__server__SSH_PORT=2222
- FORGEJO__service__DISABLE_REGISTRATION=true
- FORGEJO__federation__ENABLED=true
restart: unless-stopped
volumes:
- forgejo-data:/var/lib/gitea
- forgejo-config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:2222"
depends_on:
forgejo-db:
condition: service_healthy
forgejo-db:
image: postgres:16-alpine
container_name: forgejo-db
environment:
- POSTGRES_USER=forgejo
- POSTGRES_PASSWORD=changeme_forgejo_db_password
- POSTGRES_DB=forgejo
restart: unless-stopped
volumes:
- forgejo-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "forgejo"]
interval: 10s
timeout: 5s
retries: 5
# Optional: Forgejo Runner
forgejo-runner:
image: codeberg.org/forgejo/runner:latest
container_name: forgejo-runner
environment:
- FORGEJO_INSTANCE_URL=http://forgejo:3000
- FORGEJO_RUNNER_REGISTRATION_TOKEN=your_runner_token_here
- FORGEJO_RUNNER_NAME=local-runner
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- forgejo-runner-data:/data
depends_on:
- forgejo
restart: unless-stopped
volumes:
forgejo-data:
forgejo-config:
forgejo-db-data:
forgejo-runner-data:
The setup process is nearly identical to Gitea. Note the FORGEJO__federation__ENABLED=true environment variable, which enables the ActivityPub federation features.
GitLab CE with Docker Compose
# docker-compose.yml for GitLab CE
version: "3"
services:
gitlab:
image: gitlab/gitlab-ce:17-latest
container_name: gitlab
hostname: gitlab.example.com
restart: unless-stopped
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://gitlab.example.com'
# Reduce memory usage for small instances
puma['worker_processes'] = 2
sidekiq['concurrency'] = 5
# PostgreSQL tuning
postgresql['shared_buffers'] = "256MB"
postgresql['max_worker_processes'] = 4
# Disable features you don't need to save RAM
prometheus_monitoring['enable'] = false
grafana['enable'] = false
# Container registry
registry_external_url 'https://registry.example.com'
# Email (optional)
gitlab_rails['smtp_enable'] = false
# Backups
gitlab_rails['backup_keep_time'] = 604800
# SSH
gitlab_rails['gitlab_shell_ssh_port'] = 2222
ports:
- "8080:80"
- "8443:443"
- "2222:22"
volumes:
- gitlab-config:/etc/gitlab
- gitlab-logs:/var/log/gitlab
- gitlab-data:/var/opt/gitlab
shm_size: "256m"
gitlab-runner:
image: gitlab/gitlab-runner:latest
container_name: gitlab-runner
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- gitlab-runner-config:/etc/gitlab-runner
depends_on:
- gitlab
volumes:
gitlab-config:
gitlab-logs:
gitlab-data:
gitlab-runner-config:
After docker compose up -d, GitLab takes 2-5 minutes to start. The initial root password is stored in a file inside the container:
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
Register the runner with:
docker exec -it gitlab-runner gitlab-runner register \
--url http://gitlab \
--registration-token YOUR_TOKEN \
--executor docker \
--docker-image alpine:latest
Resource Comparison After Docker Compose Deploy
On the same N100 mini PC after a clean docker compose up -d with no repositories:
| Platform | Total RAM (all containers) | Total Disk | Time to first page load |
|---|---|---|---|
| Gitea + PostgreSQL + Runner | 320MB | 450MB | 5 seconds |
| Forgejo + PostgreSQL + Runner | 310MB | 460MB | 5 seconds |
| GitLab CE + Runner | 4.2GB | 3.8GB | 3 minutes |
Migration Between Platforms
Gitea to Forgejo (and Back)
Since Forgejo is a fork of Gitea, migration is straightforward. Forgejo can read Gitea’s database and data directory directly. In most cases, you can simply swap the Docker image from gitea/gitea to codeberg.org/forgejo/forgejo and start the container. Forgejo will run its database migrations automatically.
Going back from Forgejo to Gitea is trickier if Forgejo has applied its own database migrations. It is best to test this with a backup first.
GitLab to Gitea/Forgejo
Both Gitea and Forgejo have built-in migration tools that can import from GitLab. Go to New Migration in the web UI and enter your GitLab instance URL and a personal access token. The migration tool imports:
- Repositories (including all branches and tags)
- Issues and comments
- Labels and milestones
- Pull/merge requests (as much as the API exposes)
- Releases
It does not import CI/CD pipelines (you will need to rewrite .gitlab-ci.yml files as GitHub Actions workflows), wikis (must be cloned separately as Git repos), or issue boards and epics.
GitHub to Any Self-Hosted Platform
All three platforms support importing from GitHub. Gitea and Forgejo’s import is the smoothest because their data models are closest to GitHub’s. GitLab’s import is also solid and includes migrating CI/CD if you were using GitHub Actions (though you will need to rewrite the workflows in GitLab CI syntax).
Security Considerations
Default Security Posture
Gitea/Forgejo: Ship with sensible defaults. Registration is open by default (disable it with DISABLE_REGISTRATION=true). No security scanning built in. Supports SSH key signing and GPG commit verification.
GitLab CE: Ships with more security features out of the box. Includes built-in SAST (Static Application Security Testing), dependency scanning, and secret detection in CI/CD pipelines. Rate limiting, IP restrictions, and audit logging are available without plugins.
Recommendations for All Platforms
Regardless of which platform you choose:
- Always run behind a reverse proxy with TLS (Caddy, Nginx, or Traefik). Never expose the Git platform directly to the internet on plain HTTP.
- Disable open registration unless you specifically want public signups.
- Enable 2FA and enforce it for admin accounts at minimum.
- Use SSH keys instead of HTTPS passwords for Git operations.
- Run regular backups and test restoration. Gitea/Forgejo:
gitea dump. GitLab:gitlab-backup create. - Keep the platform updated. All three projects regularly release security patches.
- Use the rootless Docker images where available. Both Gitea and Forgejo offer rootless variants that run as a non-root user inside the container.
Vulnerability Disclosure and Response
GitLab has the most mature security response process, with a dedicated security team, a bug bounty program, and monthly security releases. Gitea and Forgejo are smaller projects with fewer resources, but both have responsible disclosure processes and respond to security issues promptly.
Which One Should You Pick?
Choose Gitea If…
- You are a solo developer or a small team (1-20 people).
- You want something that installs in minutes and runs on minimal hardware.
- You have existing GitHub Actions workflows you want to reuse.
- You value simplicity and low maintenance over feature depth.
- You want the largest community and most third-party integrations in the lightweight Git server space.
Choose Forgejo If…
- Everything in the Gitea list, plus:
- You want community-governed open source with no corporate ownership.
- You are interested in Fediverse integration and ActivityPub federation.
- You prefer copyleft licensing (GPL-3.0+).
- You are already using Codeberg or are part of that community.
Choose GitLab CE If…
- You need advanced CI/CD with DAG pipelines, merge trains, and auto-scaling runners.
- You want built-in security scanning (SAST, DAST, dependency scanning).
- You need advanced project management (issue boards, epics, time tracking).
- Your team is larger (20+ developers) and needs fine-grained access control with subgroups.
- You have the hardware to support it (8GB+ RAM recommended).
- You want a single platform that replaces your Git host, CI/CD system, container registry, and project management tool.
The Hybrid Approach
Some teams run Gitea or Forgejo for code hosting (because it is fast and light) and a separate CI/CD platform like Woodpecker CI, Drone, or even a self-hosted GitHub Actions runner. This lets you keep the lightweight Git experience without being limited by Gitea’s CI/CD capabilities.
Similarly, some teams use GitLab purely for CI/CD (pointing it at repositories hosted elsewhere) because GitLab’s pipeline engine is that good. There is no rule that says your Git host and your CI/CD system have to be the same product.
Final Thoughts
The self-hosted Git landscape in 2026 is better than it has ever been. All three platforms are production-ready, actively maintained, and capable of handling real workloads.
If you are starting fresh and just need a place to push code with some CI/CD, start with Gitea. It takes five minutes to deploy, runs on anything, and does not get in your way. You can always migrate later if you outgrow it.
If governance and long-term project freedom matter to you, Forgejo is the principled choice that sacrifices nothing in terms of daily usability.
If you need a full DevOps platform and have the hardware to run it, GitLab CE remains the most feature-complete self-hosted option available. The resource cost is real, but so is the value of having CI/CD, security scanning, project management, and code hosting in a single integrated system.
Whatever you choose, you are taking control of your code away from a third party. And that is the whole point of self-hosting.