Gitea vs GitLab vs Forgejo: Best Self-Hosted Git Platform in 2026

self-hosted gitgitea vs gitlabforgejo reviewbest git server 2026devops

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

  • 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

FeatureGiteaForgejoGitLab CE
LanguageGoGoRuby/Go
Minimum RAM256MB256MB4GB
Recommended RAM512MB-1GB512MB-1GB8GB+
Disk (base install)~100MB~100MB~2.5GB
DatabaseSQLite/PostgreSQL/MySQLSQLite/PostgreSQL/MySQLPostgreSQL only
Built-in CI/CDGitea ActionsForgejo ActionsGitLab CI/CD
Container RegistryYesYesYes
GitHub Actions CompatibleYesYesNo (own syntax)
Package RegistryYesYesYes
Issue TrackingBasicBasicAdvanced (boards, epics)
WikiYesYesYes
LicenseMITGPL-3.0+MIT (CE)
GovernanceGitea Ltd (company)Codeberg e.V. (nonprofit)GitLab Inc. (public company)
SSO/LDAPYesYesYes
First Release201620222011

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 /estimate and /spend slash 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

FeatureGiteaForgejoGitLab CE
Git LFSYesYesYes
Protected branchesYesYesYes (more rules)
Branch naming rulesBasicBasicRegex-based
Code ownersYesYesYes
Merge methodsMerge, rebase, squashMerge, rebase, squashMerge, rebase, squash, fast-forward
Signed commitsVerificationVerificationVerification + enforcement
Repository mirroringPush + pullPush + pullPush + pull (scheduled)
Monorepo supportBasicBasicGood (sparse checkout, etc.)
File lockingNoNoYes
Snippets/GistsNo (use repos)No (use repos)Yes

Authentication and Access Control

FeatureGiteaForgejoGitLab CE
LDAP/ADYesYesYes
OAuth2 (Google, GitHub, etc.)YesYesYes
SAMLNoNoYes
2FA (TOTP)YesYesYes
WebAuthn/PasskeysYesYesYes
SSH keysYesYesYes
Deploy keysYesYesYes
Fine-grained tokensYesYesYes
IP allowlistingNoNoYes
Group-level permissionsBasic (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)

MetricGiteaForgejoGitLab CE
RAM usage180MB175MB3.8GB
CPU (idle)<1%<1%2-5%
Docker image size105MB108MB2.8GB
Startup time3 seconds3 seconds90 seconds

Under Load (10 Concurrent Users, Mixed Operations)

MetricGiteaForgejoGitLab CE
RAM usage350MB340MB5.2GB
CPU usage8-15%8-15%25-40%
Git clone (100MB repo)1.2s1.2s1.8s
Web UI response time40ms42ms180ms

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

AspectGitea ActionsForgejo ActionsGitLab CI/CD
SyntaxGitHub Actions YAMLGitHub Actions YAMLGitLab CI YAML
Runneract_runnerForgejo RunnerGitLab Runner
ExecutionDocker containersDocker containersDocker, K8s, shell, SSH
Matrix buildsYesYesYes
CachingYesYesYes
ArtifactsYesYesYes (with expiration)
Secrets managementPer-repo and orgPer-repo and orgPer-project, group, instance
Scheduled pipelinesCron syntaxCron syntaxCron syntax
Manual triggersYesYesYes
EnvironmentsBasicBasicAdvanced (protection rules, approvals)
Auto DevOpsNoNoYes
Security scanningVia third-party actionsVia third-party actionsBuilt-in SAST, DAST, dependency scanning
Merge trainsNoNoYes

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 actions
  • actions/cache and actions/upload-artifact / actions/download-artifact
  • Simple third-party actions that use @actions/core and @actions/github
  • Matrix builds, conditional steps, job dependencies
  • Cron-triggered workflows

Requires modification:

  • Actions that use github. context properties specific to GitHub (like github.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:

PlatformTotal RAM (all containers)Total DiskTime to first page load
Gitea + PostgreSQL + Runner320MB450MB5 seconds
Forgejo + PostgreSQL + Runner310MB460MB5 seconds
GitLab CE + Runner4.2GB3.8GB3 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:

  1. Always run behind a reverse proxy with TLS (Caddy, Nginx, or Traefik). Never expose the Git platform directly to the internet on plain HTTP.
  2. Disable open registration unless you specifically want public signups.
  3. Enable 2FA and enforce it for admin accounts at minimum.
  4. Use SSH keys instead of HTTPS passwords for Git operations.
  5. Run regular backups and test restoration. Gitea/Forgejo: gitea dump. GitLab: gitlab-backup create.
  6. Keep the platform updated. All three projects regularly release security patches.
  7. 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.