Deployment guide

Migrate from GitLab to Gitea on a VPS in 2026

Deploy on a VPS Cloud →

Tutorial

Migrate from GitLab to Gitea on a VPS in 2026

Development10 min read13 steps

Since August 15, 2026, GitLab.com has enforced read-only mode on namespaces with more than five members on free plans. For many teams, this is a forced migration — and an opportunity to regain control. Gitea, in version 1.27.3 available as a VPS template on ServOrbit, offers a lightweight, self-hosted alternative that covers the vast majority of needs for a mid-sized development team. This guide covers the GitLab export, Gitea import, CI/CD setup, and permissions management, with real commands throughout.

Contents· Why GitLab.com became untenable for teams of 5+1/11
  1. 01Why GitLab.com became untenable for teams of 5+
  2. 02What Gitea provides natively
  3. 03Prerequisites before you start
  4. 04GitLab vs Gitea — key criteria
  5. 05Prepare the GitLab export
  6. 06Import into Gitea on your VPS
  7. 07Setting up CI/CD in Gitea
  8. 08Migrating team members and permissions
  9. 09Troubleshooting — the 4 most common errors
  10. 10Post-migration security tip
  11. 11Take back control of your Git forge

Why GitLab.com became untenable for teams of 5+

On August 15, 2026, GitLab enforced a new policy on its free tier: any namespace with more than five members is automatically set to read-only (source: support.gitlab.com/hc/en-us/articles/28301407860508). In practice, additional developers can no longer push code, open merge requests, or trigger CI/CD pipelines. The only path GitLab offers is a paid subscription — whose per-seat cost quickly becomes significant for a startup or agency.

This decision fits a broader pattern of accelerated monetisation of the SaaS platform. It primarily affects open-source teams on tight budgets, multi-project agencies, and independent developers sharing a namespace. It is not the first such restriction: GitLab already removed free CI/CD pipeline minutes for new users in 2021. The direction is structural.

In this context, self-hosting your Git forge on a VPS makes full sense again. You control access, data stays on your infrastructure, and the cost is fixed — the server cost, not the per-seat cost.

What Gitea provides natively

  • Full Git forge — repositories, branches, tags, pull requests, inline code review and conflict management through the web interface.
  • Organisation and team management — granular permissions per repository (read, write, admin) with no member limits.
  • Gitea Actions — CI/CD engine compatible with GitHub Actions YAML syntax, making existing workflow portability straightforward.
  • Built-in package registry — npm, PyPI, Maven, Docker, Helm: publish and consume artefacts with no external dependency.
  • Webhooks and REST API — broad API surface to integrate deployment tools, Slack notifications, or internal dashboards.
  • SSO authentication — LDAP, OAuth2, SAML, and OpenID Connect supported natively to centralise identity management.
  • Low memory footprint — 512 MB RAM is sufficient for a ten-person team; 1 GB recommended for concurrent Actions runners.
  • Simple upgrades — single binary or official Docker image, automatic database migrations on each version update.

Prerequisites before you start

On the server side, Gitea v1.27.3 runs comfortably on 512 MB RAM for minimal usage; plan for 1 GB if you enable Gitea Actions with concurrent runners. The ServOrbit VPS template starts from a Debian 12 image with Docker pre-installed — this is the recommended deployment mode as it isolates the Gitea process and simplifies upgrades.

On the network side, point a subdomain to your VPS IP before launching the installation: Gitea generates SSH and HTTPS clone URLs at first startup, and correcting them afterwards is tedious. A single A record git.your-domain.com is enough. You will also need a TLS certificate — Caddy or Traefik can generate one automatically via Let's Encrypt as a Gitea front-end proxy.

On GitLab.com, confirm you have Owner rights on the groups or projects to migrate: the export requires this access level. Generate a personal API token with the api and read_repository scopes — it will be used during the API-based export.

GitLab vs Gitea — key criteria

Scroll the table

CriterionGitLab.com FreeGitea VPS
Member limit (free SaaS)5 members (read-only beyond since Aug 2026)Unrestricted when self-hosted
Minimum memory footprint4 GB (GitLab CE self-hosted)512 MB
Built-in CI/CDGitLab CI (YAML)Gitea Actions (GitHub Actions syntax)
Container registryYes (GitLab Container Registry)Yes (Gitea Packages — Docker included)
Merge/pull request managementAdvanced Merge RequestsPull Requests with inline code review
SSO authenticationSAML, LDAP, OAuth2LDAP, OAuth2, SAML, OIDC
Import from GitLabN/ANative import via API or .tar.gz archive
Operating cost$29/user/month (Premium)VPS cost only (99 DH/month/month)

Prepare the GitLab export

  1. Log in to GitLab.com and navigate to the project t

    Log in to GitLab.com and navigate to the project to export: Settings > General > Advanced > Export project. Click 'Export project' and wait for the confirmation email (a few minutes for small repositories, up to an hour for large ones).

  2. To export via the API without using the interface, trigger t

    To export via the API without using the interface, trigger the export with: curl --request POST --header "PRIVATE-TOKEN: <your-token>" "https://gitlab.com/api/v4/projects/<project-id>/export"

  3. Check the export status with:

    Check the export status with: curl --header "PRIVATE-TOKEN: <your-token>" "https://gitlab.com/api/v4/projects/<project-id>/export" — wait for the finished status.

  4. Download the export archive:

    Download the export archive: curl --location --header "PRIVATE-TOKEN: <your-token>" "https://gitlab.com/api/v4/projects/<project-id>/export/download" --output gitlab-export.tar.gz

  5. To migrate the raw Git history as well, clone the repository

    To migrate the raw Git history as well, clone the repository with all references: git clone --mirror [email protected]:your-group/your-project.git your-project.git

  6. Verify the integrity of the mirror clone before transferring

    Verify the integrity of the mirror clone before transferring: git -C your-project.git fsck --no-progress — no errors should appear.

  7. Transfer the archive and mirror repository to your VPS:

    Transfer the archive and mirror repository to your VPS: rsync -avz gitlab-export.tar.gz your-project.git user@your-vps:/tmp/migration/

Import into Gitea on your VPS

  1. On your VPS, create the target organisation in Gitea via the

    On your VPS, create the target organisation in Gitea via the web interface or via the API: curl -X POST "https://git.your-domain.com/api/v1/orgs" -H "Authorization: token <gitea-token>" -H "Content-Type: application/json" -d '{"username":"my-organisation","visibility":"private"}'

  2. Create the empty repository in Gitea before the import:

    Create the empty repository in Gitea before the import: curl -X POST "https://git.your-domain.com/api/v1/orgs/my-organisation/repos" -H "Authorization: token <gitea-token>" -H "Content-Type: application/json" -d '{"name":"my-project","private":true}'

  3. Push the Git history from the mirror clone:

    Push the Git history from the mirror clone: git -C your-project.git remote set-url origin https://git.your-domain.com/my-organisation/my-project.git then git -C your-project.git push --mirror

  4. To import issues and merge requests from the GitLab archive,

    To import issues and merge requests from the GitLab archive, use the gitea-migrator tool or Gitea's native import: in the interface, + New Migration > GitLab and provide the source project URL with your GitLab token.

  5. Verify that all branches and tags are present:

    Verify that all branches and tags are present: git ls-remote https://git.your-domain.com/my-organisation/my-project.git should list the same references as the original.

  6. Update the clone URLs in each developer's local configuratio

    Update the clone URLs in each developer's local configuration: git remote set-url origin https://git.your-domain.com/my-organisation/my-project.git

Setting up CI/CD in Gitea

Gitea v1.27.3 includes Gitea Actions, a CI/CD engine whose YAML syntax is intentionally compatible with GitHub Actions. If your pipelines were on GitLab CI, some adaptation is required — GitLab's stages and script translate to Gitea Actions jobs and steps — but the logic remains the same.

To enable runners, install act_runner on your VPS or on a dedicated machine: act_runner register --no-interactive --instance https://git.your-domain.com --token <runner-token> --name my-runner --labels ubuntu-latest:docker://node:20-bullseye. The registration token is found in Site Administration > Runners.

If you prefer a more decoupled CI/CD approach, Woodpecker CI is a mature alternative that integrates natively with Gitea via OAuth2 and provides a build supervision interface. Its .woodpecker.yml pipeline file is also close to Drone CI syntax, simplifying migrations from older stacks. In both cases, the first pipeline should validate the build, run tests, and if everything passes, push an image to the Gitea Docker registry.

Migrating team members and permissions

Gitea organises access at three levels: organisations (equivalent to GitLab groups), teams within an organisation, and per-repository permissions. Start by creating all user accounts — either manually, by temporarily enabling auto-registration, or via LDAP/SSO if already available.

Then create teams in your organisation: Owners, Developers, Reporters map to GitLab's Owner, Developer, and Reporter roles. Gitea allows you to refine permissions per repository within the same team — a subset of members can have write access to specific repos only.

To migrate memberships in bulk, the Gitea API accepts calls like: curl -X PUT "https://git.your-domain.com/api/v1/orgs/my-organisation/teams/<team-id>/members/<username>" -H "Authorization: token <gitea-token>". A shell script looping over your exported GitLab user list is enough to automate this step for teams of fewer than fifty people. Beyond that, evaluate an LDAP integration to synchronise groups automatically.

Troubleshooting — the 4 most common errors

remote: repository not found during mirror push. The target repository does not yet exist in Gitea, or the token used does not have write rights. Verify that the repository was created (import step 2) and that your token has the write:repository scope.

error: src refspec refs/merge-requests/... does not match any during push --mirror. GitLab's internal references (refs/merge-requests/) are not accepted by Gitea. Filter them explicitly before pushing: delete the parasitic refs locally with git -C your-project.git for-each-ref --format='%(refname)' refs/merge-requests | xargs -I{} git update-ref -d {}.

Error 413: Request Entity Too Large when importing a large archive. The maximum upload size is configured in Gitea's app.ini under the MAX_UPLOAD_SIZE key. Increase it (MAX_UPLOAD_SIZE = 2048 for 2 GB) and reload the service: docker compose restart gitea.

Imported issues do not show the correct authors. Gitea associates issues with local accounts by username. If the GitLab username differs from the Gitea username, issues are attributed to the user who ran the import. Create all accounts with the same usernames as on GitLab before running the issues migration.

Post-migration security tip

Once the migration is complete, disable auto-registration (DISABLE_REGISTRATION = true in app.ini) if you are not using SSO, and enforce two-factor authentication for all Owner members. Also configure automatic backups of the Docker volume containing /data/gitea: a daily dump to remote storage (S3, Backblaze B2) protects you from disk loss. Finally, verify that Gitea's SSH port (default 2222 in Docker) is not directly exposed on the public IP if you filter by SSH key — a fail2ban rule or a UFW rule limiting SSH connections per minute significantly reduces the attack surface.

Take back control of your Git forge

The GitLab.com restriction of August 15, 2026 accelerated a trend already underway: teams that had accepted dependency on a free SaaS platform now face an unanticipated pricing decision. Gitea v1.27.3 meets the essential needs of a team Git forge — repositories, pull requests, CI/CD, package registry, SSO — in a footprint that fits on an entry-level VPS.

The migration is not instantaneous, but it is sequential and reversible at each step: the Git history is complete after the push --mirror, issues and pull requests follow via the native import, and CI/CD reconfigures in a few hours if your workflows were already in YAML. The ServOrbit Gitea VPS template spares you the initial installation and configuration phase — Gitea is ready, Docker is in place, and you only need to attach your domain and follow the steps described here.

See the companion guides on the self-hosted AI stack with Gitea and Ollama, replacing GitHub Actions with Woodpecker CI, and installing GitLab CE on a VPS if you are still comparing both options.

Deploy your Git forge on ServOrbit VPS

Ready-to-use Gitea v1.27.3 template, root access, daily snapshots. Your private forge in less than ten minutes.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab