GitLab’s Premium and Ultimate tiers are priced per user, gate the best features behind the top tier, and commit you to annual seats with CI-minute overages on top. Gitea (and its community fork Forgejo) is a lightweight, self-hosted Git platform that removes seat licensing entirely, with Gitea Actions providing GitHub-Actions-compatible CI. Repos and issues migrate easily; the real lift is translating pipelines.
What you gain, and what you give up
You gain a fast, low-footprint Git host with code review, issues, packages, and Actions-based CI, at no per-seat cost. You give up GitLab’s all-in-one breadth: advanced security scanning (SAST/DAST/dependency), built-in value-stream and compliance dashboards, sophisticated merge-request approval rules, and the integrated container/agent fleet. For most teams that mainly need Git + code review + CI, Gitea/Forgejo is ample; if you lean on Ultimate’s security and compliance suite, plan replacements (e.g. Trivy, Semgrep, Renovate) rather than assume parity.
Migrate repos, issues, and artifacts
Gitea/Forgejo can pull-mirror repositories directly from GitLab, including the option to import issues, pull/merge requests, labels, and milestones via the GitLab API during migration. Stand up the new instance (with an external PostgreSQL for production), then run migrations per project. This part is genuinely smooth.
A few things do not ride along with the mirror, so plan them explicitly:
- User mapping. Issue and MR authorship maps to Gitea accounts only when the accounts match. Create or link users first, or accept that history attributes to a fallback account.
- Packages and container images. GitLab’s package and container registries do not mirror through the Git import. Re-publish artifacts to Gitea’s package registry, or point builds at whatever external registry you already run.
- Wikis and snippets. Wikis migrate as their own Git repositories; snippets have no direct equivalent, so decide whether to fold them into a repo or drop them.
Treat the repo and issue import as a rehearsal for the real work. If a pilot project imports cleanly and its history looks right, you have proven the mechanism and can move on to the part that actually takes time.
The pipeline rewrite is where the time goes
.gitlab-ci.yml does not run on Gitea. Gitea Actions uses the GitHub Actions workflow format (.gitea/workflows/*.yml), so each GitLab job, stages, rules, needs, artifacts, services, caching, must be rewritten as Actions steps (or Woodpecker pipelines if you prefer that engine). Budget the bulk of the project here:
- Map GitLab stages/jobs → Actions jobs/steps, and GitLab runners → Act runners registered to the instance.
- Re-create CI/CD variables and secrets in the new platform’s secret store.
- Reconnect container registry, package registry, and deploy targets; re-point webhooks and service connections.
- Recreate protected-branch and approval rules as closely as the platform allows.
Where the effort concentrates
Not every job costs the same to convert. Simple build-and-test jobs map almost line for line: a script: block becomes a run: step, and a job’s image: becomes the container the step runs in. The expensive cases are the ones that leaned on GitLab-specific machinery: rules: with complex if/changes conditions, dynamic child pipelines, extends and YAML anchors used as a homegrown template system, and needs: graphs that fan out widely. Convert the straightforward jobs first to build momentum, then sit down with the complex ones once the team is fluent in the Actions model.
Re-entering secrets and registering runners
Secrets do not travel with the repo mirror. Re-enter CI/CD variables in the new secret store, scoped to org or repo, and never inline them in workflow YAML. For runners, register Act runners against the Gitea instance and re-point the same underlying compute you already run. Confirm the runners can reach your registries and deploy targets before you rely on them, since network egress rules often differ from the old GitLab environment.
Cut over team by team
- Inventory repos, pipelines, runners, secrets, artifacts, integrations, and permission/environment models.
- Stand up Gitea/Forgejo (HA + PostgreSQL), register Act runners.
- Mirror repos and import issues/MRs for a pilot project; translate and dry-run its pipeline (build/test/deploy).
- Validate the full pipeline shape on the pilot, then migrate project by project, keeping GitLab live until each team’s pipelines are green.
- Re-point webhooks/runners back on failure, rollback is keeping GitLab authoritative until sign-off.
Validation before you trust it
The acceptance bar is “a representative project builds, tests, and deploys end to end on the new platform.” Verify secret/artifact resolution, webhook and integration behavior, and a deploy-plus-rollback. Don’t migrate the whole org until the pilot’s pipeline runs clean.
What to weigh before you start
GitLab → Gitea/Forgejo removes per-seat cost; repos and issues migrate cleanly, but pipeline translation to Actions/Woodpecker is the substance of the work, and Ultimate’s security/compliance suite needs deliberate replacement. Pilot one project end to end, then cut over team by team with GitLab as fallback. Model your per-seat savings in the calculator above, and treat them as provisional until you scope the pipeline-conversion effort.