Migrate to fremforge from GitLab
This guide covers migration from GitLab to fremforge. It applies to GitLab.com, GitLab self-managed (CE or EE), and GitLab Dedicated. The differences between those tiers show up mostly in the authentication approach and in which GitLab-specific features you may have enabled.
Read the GitHub migration guide first if you are coming from GitHub. Some of the common-sense steps are identical and we do not repeat them here.
If you have not run the pre-migration checklist against your current GitLab tenant, do that first. For GitLab migrations specifically it flags the presence of GitLab CI include: cross-project references (which need manual unwinding) and GitLab Pages deployments (which need a fremforge Pages reconfiguration).
Before you start
What you need on your side:
- An owner-role account on the GitLab group(s) you are migrating.
- A personal access token on GitLab with
api,read_repository,read_user, andread_registryscopes. - Admin access to the fremforge organisation you are migrating to.
- For CI migration: an inventory of GitLab CI templates,
include:references, and any custom GitLab Runner tags your pipelines depend on.
What we recommend:
- Freeze merges to the default branches during migration.
- Verify one repository end-to-end first. GitLab CI pipelines are structurally more different from Forgejo Actions than GitHub Actions are, so the adaptation work is more substantive per-repository.
- If you have many cross-project
include:references in GitLab CI, map the dependency graph before you start migrating so you do not end up with half-migrated pipelines that reference templates no longer visible to them.
Time estimate:
- Single small repository with simple
.gitlab-ci.yml: 30-60 minutes including pipeline adaptation. - Medium repository with moderate CI complexity (DAG,
needs:, custom runners tags): 2-4 hours. - Bulk migration of 20-50 repositories where many share GitLab CI
include:templates: 3-5 engineering days, dominated by CI conversion.
Feature parity matrix
For the security/auth surface, the same matrix as in the GitHub migration parity matrix applies — fremforge ships the same security primitives against either source. fremforge defaults new orgs to HTTPS-only Git access (SSH disabled by default since 2026-05-22) with the pre-registered Git Credential Manager OAuth app; developers get MFA via your org’s IdP on every push refresh. GitLab-specific items:
| Capability | GitLab Premium/Ultimate | fremforge | Notes |
|---|---|---|---|
| HTTPS + GCM (OAuth interactive, MFA via IdP) | ⚠️ Custom OAuth app setup | ✅ Pre-registered | Two git config lines from the Secure sign-in guide. |
| SSH certificate authority | ✅ EE feature | ✅ | Native admin UI at <org>/_admin/auth-policy (sub-section under “Allow SSH protocol”) for orgs that have re-enabled SSH. |
OIDC tokens for CI workload identity (id_tokens) | ✅ | ✅ | Same Forgejo Actions OIDC issuer as for GitHub Actions migrations. |
| Push rules (regex on branch/commit/file/email) | ✅ | ⚠️ Via pre-receive hooks | fremforge ships gitleaks + workflow-security + onboarding pre-receive hooks today. Adding custom regex push rules = adding a hook script. |
| Group SAML SSO | ✅ Premium | ✅ | Same multi-source OIDC + SAML support as for GHEC migrations; per-tenant BYOIDC. |
| GitLab Project Access Tokens (per-repo bot identity) | ✅ | ⚠️ Scoped PATs | Forgejo PATs are user-scoped; per-repo bot identity is a UX difference, not a capability gap (configure a dedicated bot-<purpose> user and scope their PAT). |
| GitLab Pages | ✅ | ❌ Not shipped | fremforge has no Pages product. Markdown-style Pages sites can lift to the public-docs wiki; static HTML/JS sites need to move to external hosting (Bunny / Cloudflare Pages / Netlify / Vercel). See Step 6. |
| Apply review suggestions in PR | ✅ | ✅ | Click-through commit in a PR review’s ```suggestion code-fence — head-SHA + branch verified server-side. Customer docs at Develop → Issues and PRs → Apply review suggestions. Shipped 2026-05-24. |
| Public docs wiki on private repos | ⚠️ GitLab Pages on private projects (Premium tier) | ✅ Native opt-in per repo | Anonymous public-read of the repository wiki at frem.sh/<org>/<repo>/wiki[/...] independent of whether the underlying repo is public or private. Customer docs at Develop → Wiki and public docs. Shipped 2026-05-24. |
| Customer-tunable audit retention | ⚠️ Self-hosted only (fixed on GitLab.com) | ✅ Per-tenant {90 / 180 / 365 / 730}d hot-tier + 3y WORM | Tenant admin sets the queryable hot-tier window at Authentication policy → Audit log retention; 3-year WORM hash-chain archive unchanged. Defaults: 90d standard plan, 365d enterprise. Shipped 2026-05-24. |
| Per-group IP allowlist on Git | ✅ Real-time | ✅ HTTPS only | Same architecture as GHEC migration: fremforge gates IP on HTTPS Git (real-time), SSH is opt-in-per-org and gated via the SSH-disable kill-switch rather than IP. |
| EU-only data plane | ❌ GitLab.com is US (GCP). Self-managed GitLab works but pushes ops cost to you. | ✅ Native T Cloud EU-DE | The reason most EU-regulated customers consider fremforge over GitLab.com. |
| Per-user pricing | $99/user/mo (Ultimate) | $0 (no per-user license) | fremforge bills on org capacity + storage. |
Step 1: Repository and history migration
Forgejo’s native importer supports GitLab as a source. It copies the full Git history, issues, merge requests (imported as pull requests), comments, labels, milestones, wiki content, and releases.
Using the native importer (UI path)
From the fremforge admin UI for your organisation:
- Click + New → Migration.
- Select GitLab as the source.
- Paste the GitLab project URL (e.g.
https://gitlab.com/acme/backend). - Paste your GitLab personal access token.
- Tick the content types to import: Wiki, Issues, Merge requests, Releases, Labels, Milestones.
- Choose the target organisation and repository name on fremforge.
- Click Migrate repository.
Using the API (scriptable path)
# Build the JSON body via jq so the token is JSON-string-escaped safely
# (a token containing `"` or `\` would otherwise break the body).
jq -nc \
--arg clone_addr "https://gitlab.com/acme/backend.git" \
--arg auth_token "$GITLAB_TOKEN" \
--arg repo_owner "acme" \
--arg repo_name "backend" \
'{clone_addr: $clone_addr, auth_token: $auth_token,
service: "gitlab", repo_owner: $repo_owner, repo_name: $repo_name,
wiki: true, issues: true, pull_requests: true,
releases: true, labels: true, milestones: true,
lfs: true}' \
| curl -X POST "https://frem.sh/api/v1/repos/migrate" \
-H "Authorization: Bearer ${FREMFORGE_TOKEN}" \
-H "Content-Type: application/json" \
-d @-Mapping GitLab concepts to fremforge
GitLab and Forgejo do not share the same vocabulary. The importer handles the common cases:
| GitLab concept | fremforge (Forgejo) concept |
|---|---|
| Project | Repository |
| Group | Organisation (top-level) or nested team (subgroup) |
| Merge Request | Pull Request |
| Approver | Reviewer |
| Approval rule | Branch protection rule with required reviewers |
| Epic (GitLab Ultimate) | No direct equivalent, manual re-creation |
| Iteration | No direct equivalent, milestone is closest |
| Roadmap | Not a fremforge feature |
| GitLab Packages | fremforge package registry |
| GitLab Pages | No Forgejo equivalent — see Step 6 for migration paths (public-docs wiki for Markdown content, external hosting for static HTML/JS) |
| GitLab Registry (container) | fremforge Docker registry under the package registry |
| GitLab Runners | fremforge hosted CI runners or BYO runners |
| Value Stream Analytics | Not a fremforge feature |
| GitLab Security Dashboard | fremforge admin UI → Findings |
Subgroups
GitLab supports arbitrarily nested subgroups. fremforge (Forgejo) uses a flat organisation + repository model. If your GitLab structure is company / department / team / project, decide before migration whether:
- Collapse into a single organisation: all projects become
<company-slug>/<team>-<project>repositories under one organisation. Simpler permissions, loses some hierarchy. - One organisation per top-level group:
<company-department-slug>with flat repo naming. Loses department-level hierarchy. - One organisation per subgroup: more organisations, more seat management, each billable separately. Only viable if the subgroups have genuinely separate billing and access requirements.
The importer supports option 1 or 2 directly. Option 3 requires creating multiple fremforge organisations and running the import N times.
Step 2: Secrets and CI/CD variables
GitLab stores CI/CD variables at instance, group, project, and environment levels. fremforge Actions supports organisation, environment, and repository secrets/variables: a three-level hierarchy that maps cleanly to GitLab’s common cases.
Critical: GitLab CI/CD variables marked as “masked” do not export their values via the API. You must either know the value or retrieve it from your secrets-of-record. This is the same constraint as GitHub secret migration.
Re-entering variables
For each GitLab CI/CD variable, decide:
- Is it a long-lived cloud credential (AWS access key, Azure service principal secret, T Cloud AK/SK)? Strong recommendation: replace with OIDC federation to the target cloud (see Step 3 below) rather than migrating the static credential.
- Is it a protected variable (GitLab concept, only accessible to protected branches/tags)? fremforge has the equivalent via environment secrets. Configure the target environment with branch-protection-gated access and scope the secret there.
- Is it a file variable (GitLab stores multi-line file contents as a variable)? fremforge supports the same via standard secret storage. Paste the multi-line content; the runner makes it available as the named environment variable at job time.
Set variables and secrets in the fremforge admin UI at the matching scope: Settings → Secrets (org-level) or Repository → Settings → Secrets (repo-level).
Step 3: GitLab CI → Forgejo Actions conversion
This is the largest step in a GitLab migration. GitLab CI pipelines are structurally different from Forgejo Actions: stages, jobs, extends, include, rules, artifacts, and cache do not map one-to-one to GitHub Actions’ jobs, steps, needs, and uses.
There is no automated converter. Automated conversion is on the roadmap pending customer demand. At launch we ship documentation and manual conversion patterns covering the common cases.
Conversion patterns
Stages → Jobs with needs:
GitLab CI organises pipelines into sequential stages. Forgejo Actions organises them into parallel jobs with explicit DAG dependencies via needs:.
GitLab CI:
stages: [build, test, deploy]
build:
stage: build
script: make build
test:
stage: test
script: make test
deploy:
stage: deploy
script: make deployForgejo Actions equivalent (.forgejo/workflows/main.yaml):
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: fremforge
steps:
- uses: actions/checkout@v4
- run: make build
test:
needs: build
runs-on: fremforge
steps:
- uses: actions/checkout@v4
- run: make test
deploy:
needs: test
runs-on: fremforge
steps:
- uses: actions/checkout@v4
- run: make deployextends: → workflow reusable actions or composite actions
GitLab CI extends: inherits job configuration. Forgejo Actions has two equivalents:
- Reusable workflows (
workflow_call) for inheriting whole jobs across workflows. - Composite actions (
runs: using: composite) for inheriting step sequences.
Conversion is manual: identify the .extends: parent job in GitLab, copy its script: and setup to a composite action under .forgejo/actions/<name>/action.yml, then call with uses: ./.forgejo/actions/<name> from the consuming workflows.
include: cross-project templates
GitLab CI include: project: fetches CI configuration from another project. There is no direct equivalent in Forgejo Actions. Reusable workflows (uses: owner/repo/.forgejo/workflows/template.yaml@ref) are the closest thing and work within the same fremforge organisation.
For migrations that have shared template projects (common in organisations with CI platform teams), migrate the template repository first and update the uses: references in consumer repositories as part of their own migration.
rules: and only:/except: → if: expressions
GitLab CI has three overlapping mechanisms for conditional job execution. Forgejo Actions uses the same if: expression syntax as GitHub Actions.
GitLab:
deploy:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
- when: never
script: make deployForgejo Actions:
deploy:
if: github.ref == 'refs/heads/main'
runs-on: fremforge
steps:
- uses: actions/checkout@v4
- run: make deployartifacts: → upload-artifact / download-artifact
GitLab CI artifacts propagate automatically between stages. Forgejo Actions requires explicit actions/upload-artifact in the producing job and actions/download-artifact in the consuming job.
cache: → actions/cache
GitLab CI cache: maps to actions/cache@v4 with the same cache key / restore-key semantics.
services: → run side-cars in steps (NOT the workflow services: block)
GitLab CI services: (side-car Postgres/Redis/etc.) does NOT translate to a services: block on fremforge today. Forgejo Actions parses the syntax without error, but fremforge runners run on T Cloud CCI v2 where privileged containers are forbidden — there’s no Docker daemon for the runner to spawn side-car containers, so a services: declaration silently does nothing and your test will connect to localhost:5432 and fail. Workflows depending on the native services: block will not work.
The supported path is to spawn the side-car explicitly inside a step using the kaniko-built image plus a long-running step command, or — much more commonly — run the dependency inline using whatever the language ecosystem provides (Testcontainers for Java, pytest-docker for Python, testcontainers-go, etc.). These do not require a privileged runtime because they shell out to processes the language test harness manages.
Native services: block support is on the platform roadmap (tracking actions-services-cci-multi-pod) — it requires a runner-controller change to provision side-car CCI pods + DNS wiring and is non-trivial. Until that lands, the migration guidance is: rewrite GitLab services: blocks into in-step container/process management or testcontainers, not into Forgejo services: blocks.
Runner tags
GitLab CI tags: selects specific runners. Forgejo Actions runs-on: achieves the same. The fremforge-provided hosted runners use the label fremforge (maps to our Alpine 3.22-based runner-base image). Custom runners can register with custom labels and runs-on: targets them by label name.
If your GitLab CI relied on specific private runners (tags: [gpu], tags: [macos], tags: [on-prem]), those custom runners need setting up on fremforge via the BYO-runner registration flow. The fremforge hosted runners are Linux amd64 Ubuntu only; GPU and macOS are not available on the standard plan.
Step 4: OIDC federation for deploys
If your GitLab CI pipelines deploy to cloud infrastructure, the best-practice migration is to replace long-lived credentials with OIDC federation. fremforge supports OIDC token federation to T Cloud natively; for AWS, Azure, and GCP, the standard OIDC-federation patterns work.
Set up:
- In the fremforge admin UI, Settings → OIDC Federation → Add target.
- For T Cloud: specify the target agency URN. The runner workflow receives a short-lived scoped JWT that the T Cloud agency trusts and exchanges for scoped credentials.
- For AWS/Azure/GCP: configure the OIDC trust in the target cloud’s IAM with
https://frem.sh/_app/runner/oidcas the issuer (single global issuer for all tenants — per-tenant isolation is enforced via therepository_ownerclaim condition, the GitHub-Actions-compatible pattern). See build/runners/oidc/ for the JWKS endpoint, full claim set, and per-cloud trust-policy examples.
After setup, workflows use the federation instead of static keys. Each runner job receives a short-lived OIDC ID token at $ACTIONS_ID_TOKEN_REQUEST_URL, exchange it for scoped T Cloud credentials at job start:
- name: Federate to T Cloud
run: |
OIDC_TOKEN="$(curl -sSf -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=tcloud-${ORG_SLUG}")"
CREDS_JSON="$(curl -sSf -X POST \
"https://iam.eu-de.otc.t-systems.com/v3.0/OS-FEDERATION/token-exchange" \
-H 'Content-Type: application/json' \
-d "{\"identity\":{\"id_token\":{\"id\":\"$OIDC_TOKEN\"}},\"scope\":{\"agency\":{\"name\":\"fremforge-deploy\",\"domain\":{\"name\":\"$TCLOUD_DOMAIN\"}}}}")"
{
echo "TCLOUD_AK=$(jq -r '.credential.access' <<<"$CREDS_JSON")"
echo "TCLOUD_SK=$(jq -r '.credential.secret' <<<"$CREDS_JSON")"
echo "TCLOUD_TOKEN=$(jq -r '.credential.securitytoken' <<<"$CREDS_JSON")"
} >> "$GITHUB_ENV"A wrapper action that hides the curl shape will land at frem.sh/fremforge/configure-tcloud-credentials once the public Forgejo namespace is live; the inline form above keeps the raw OIDC contract visible and works today.
Delete the corresponding static credential secrets from both GitLab and fremforge.
Step 5: SSH key re-registration
Same process as GitHub migration Step 4. Each developer adds their SSH public key to their fremforge account.
SSH certificate authorities (GitLab EE feature) import via <your-org>/_admin/auth-policy — the SSH CA section sits under the “Allow SSH protocol” toggle (folded into Authentication policy on 2026-05-26). Same admin UI as the GitHub migration path — see the SSH certificate authority section for setup details.
Step 6: GitLab Pages → external hosting (no Forgejo Pages equivalent)
fremforge does not ship a Pages product. Upstream Forgejo has no Pages module and we have not built one. GitLab Pages migrations need to land somewhere else — pick the option that fits your shape:
Markdown documentation that lived in a Pages site backed by a repo wiki: enable fremforge’s public-docs wiki opt-in on the migrated repo. The wiki at
frem.sh/<org>/<repo>/wiki[/...]serves rendered Markdown with anonymous-read enabled per-repo. Note: this serves rendered Markdown, not arbitrary static HTML/JS — so it covers documentation-style Pages sites, not single-page-app Pages deployments.Static HTML/JS sites (single-page apps, Hugo / Jekyll / Astro output): host externally and CNAME from your own domain. Reasonable EU-friendly choices include Bunny Edge Storage + Bunny CDN (the same stack fremforge’s own marketing site uses), Cloudflare Pages, Netlify, or Vercel. Reconnect the build via fremforge’s runner workflows that deploy on push.
Custom domain only: if you previously used GitLab Pages for the custom-domain TLS certificate (rather than its build step), point your DNS at your external host and reuse fremforge’s runner-OIDC federation to deploy directly from the workflow — no proxy through fremforge needed.
If you have a non-trivial GitLab Pages pipeline (custom CI, redirects, per-branch deployments), email support@frem.sh — we can talk through the right external-host shape for your case before you commit to a migration plan.
Step 7: Third-party integration reconnection
GitLab’s integrations (webhook targets, Jira sync, Slack notifications, etc.) reconnect the same way as GitHub migration Step 5. Most integrations ship Forgejo-compatible support out of the box.
GitLab-specific integrations with no direct equivalent:
- GitLab Security Dashboard aggregation, fremforge’s admin UI Findings view serves the same purpose for the bundled supply chain stack.
- Value Stream Analytics / DORA metrics, fremforge does not ship DORA metric calculation. If you depend on it, integrate with an external tool (e.g. Faros, LinearB, Jellyfish) via webhook events and API polling.
Step 8: Verify, decommission
Same as GitHub migration Step 7:
- Clone, build, verify tests pass.
- Run a sample PR through the pre-receive scanner, dependency scanner, signed-commit verification.
- Verify webhooks deliver.
- Verify SSO sign-in for every user.
- Update documentation to new URLs.
- After 30 days successful operation, archive GitLab projects (do not delete).
Common pitfalls specific to GitLab migrations
Pipeline conversion takes longer than you estimate.
A complex .gitlab-ci.yml with many stages, DAGs, extends:, include:, and services: can take half a day per repository to port accurately. Budget 2-4 hours per medium-complexity pipeline. The compatibility matrix at docs.frem.sh/marketplace-compat is GitHub-Actions-focused; for GitLab-CI-specific patterns, use Step 3 of this guide and the Forgejo Actions documentation.
Merge Request approval rules
GitLab’s approval rules (require N approvers, specific approver groups, code-owner-based approval) map to Forgejo’s branch protection rules but with less granularity. If your GitLab project depends on sophisticated approval rules (e.g. “different approvers for security-sensitive paths”), evaluate whether Forgejo’s CODEOWNERS-based required-reviewer model is sufficient before migration.
GitLab Premium/Ultimate features
If you use GitLab Ultimate features (DAST, fuzz testing, license compliance, SAST with Ultimate severity rules, Dependency Firewall, group-level Security Dashboards), fremforge does not ship direct equivalents. See the AI posture post at www.frem.sh and the fremforge product page for what we cover and what we explicitly do not.
GitLab runners tagged for compliance contexts
GitLab Runner tags like compliance, restricted-network, no-internet are commonly used for runners that cannot reach the public internet. fremforge’s hosted runners satisfy this constraint by design, uses: directives resolve through the EU-only action mirror at https://frem.sh/mirrors/ (the canonical reference is in the GitHub migration page §Step 3: Actions workflow adaptation), and outbound-proxy egress is restricted to that mirror, mirrored package registries, and the customer-tenant’s outbound webhooks. If your compliance context mandates no-internet runners at all, use BYO-runners on your own network and point them at fremforge’s runner-registration endpoint.
Get help
support@frem.sh with subject line MIGRATION, GITLAB, <your-specific-issue>. Early-adopter customers have direct access to the build team.
See also: GitHub migration guide · Azure DevOps migration guide · Top-20 Marketplace actions compatibility matrix · Pre-migration checklist.