Migrate from GitHub to GitLab: what the move involves and what it takes
The GitHub importer in GitLab can bring repositories, issues, pull requests and wikis into a self-managed instance, but GitHub Actions workflows are not carried over and must be rewritten as GitLab CI/CD pipelines as a separate piece of work. For a team that needs source control, code review and CI/CD inside infrastructure it controls, the import is the smaller task. The larger effort is usually the pipelines, the access model and the user mapping that follows the import. Before any import, the self-managed instance must be installed, configured and run with backups in place, so the first decision is the install method and the sizing that follows from your user count.
Effort scales with three counts: the number of repositories, the number of users who will contribute history and reviews, and the number of pipelines that must be rebuilt. Repository count and total history size drive import duration and storage, user count drives the placeholder mapping work, and pipeline count drives the rewrite. Ask any partner to size each of these from an inventory of your GitHub organization rather than from a headline number, and confirm which GitHub objects your team actually relies on, such as Actions secrets, environments, branch protection rules and CODEOWNERS files, since these need manual handling. The sizing of the instance itself should follow the published reference architectures for your expected user and API load, and the reference architecture pages are the right next read for that.
Plan the cutover before the first import runs. Decide whether GitHub stays read-only for history, how open pull requests will be handled, and when developers switch their remotes. Confirm the import behavior for your GitHub plan and token scopes in your own environment, because the importer depends on the access granted to it. Also confirm that the instance can reach GitHub from its network, or that you have an approved offline path if your network restricts outbound access. A short pilot on a few representative repositories will show which problems appear, such as large files, mismatched branch names or missing contributor identities, before you commit the full schedule.
Which GitHub capabilities map to GitLab and which need rework
This table pairs each GitHub capability in scope with its closest GitLab equivalent and a migration note on what the importer may not carry over. Read the notes as a checklist for what to verify in your own test import, not as a guarantee of parity. Confirm each importer behavior against the current GitLab import documentation before you plan the cutover.
| Capability | GitLab equivalent | Migration note |
|---|---|---|
| Repositories and Git history | Projects and repositories | Git history moves with the importer. Confirm how large repositories, LFS objects and forks are handled in your GitLab version. |
| Issues | Issues | Issue text, comments and labels are usually imported. Confirm how assignees and closed states map, and whether reactions and linked references survive. |
| Pull requests | Merge requests | Review content and comments are the main risk. Confirm which discussion threads, review states and commit links the importer keeps. |
| Wikis | Project or group wiki | Wiki content is a separate Git repository. Check that page links and attachments resolve after the move. |
| Labels and milestones | Labels and milestones | Names and colors usually come across, but check whether group-level labels need to be created first. |
| Releases | Releases | Release notes and tags may need a separate step. Verify attached binaries and asset links after import. |
| Branch protection | Protected branches and push rules | Protection settings are typically not carried over. Recreate them and test that direct pushes are blocked. |
| CODEOWNERS-style review rules | CODEOWNERS file and approval rules | GitLab reads a CODEOWNERS file, but approval rule behavior differs. Rebuild required approvals and test them on a sample merge request. |
| GitHub Actions workflows | GitLab CI/CD pipelines and runners | Workflows must be rewritten. Treat them as a separate migration with their own runner and secrets plan. |
| Secrets | CI/CD variables and external secrets integrations | Secret values cannot be exported from GitHub. Re-enter them in GitLab and rotate credentials where possible. |
| Environments and deployment approvals | Environments and protected environments | Approval gates and deployment history are rebuilt, not copied. Confirm the protection model in your tier. |
| Pages-style static hosting | GitLab Pages | Sites are rebuilt by a CI job, and custom domains and certificates are set up again. Confirm Pages settings for self-managed instances. |
Documented import paths from GitHub into self-managed GitLab
GitLab's import documentation lists GitHub as a supported source, but the GitHub importer is only one of several routes. Each path carries a different slice of the work, so the choice follows from what you need to move and how much you want to script.
GitHub importer
The GitHub importer is the documented route for bringing repositories, issues, pull requests and wikis from GitHub into a GitLab group or project. It is the starting point for most teams because it creates the project and maps much of the collaboration history in one pass. Check the import documentation for the exact object types it covers in your GitLab version, and confirm how it handles private repositories and the access token it needs. Plan the user mapping step as a separate task, since contributors are matched or left as placeholders.
Direct repository import by URL
For Git content, GitLab can create a project by pointing it at a repository URL. This moves the Git history and branches but not the GitHub-specific objects such as issues, pull requests and review comments. It suits teams that only need code in GitLab first, or repositories that GitHub's importer cannot reach. Confirm the authentication method for private sources and whether tags and LFS objects are included for your setup before you rely on it for a cutover.
Manifest-based bulk import
A manifest lets you list many Git repositories and import them in one operation rather than project by project. It is useful when a GitHub organization holds dozens of repositories that must land in a planned group structure. The manifest carries repository locations and target paths, not contributor history or collaboration objects. Validate the manifest against a test group first, and confirm the naming and namespace rules that your instance enforces.
File export and re-import
Export-based approaches move a project as a file, which can carry data the live importer does not reach. They depend on the exporting and importing GitLab versions being compatible, so confirm that before you schedule the move. Treat them as a fallback for specific project content rather than a full GitHub migration path, and test the restored result against a sample of issues and merge requests.
API-driven scripting
The GitLab REST API lets you create projects, set memberships, post issues and attach comments when the importer does not cover a object type or your data needs transformation. Scripts give you control over link rewriting, label mapping and retries across many repositories. They also take ongoing engineering effort to write, test and run, so estimate that work before choosing this route. Keep an audit log of every created object so the cutover can be verified and reversed if needed.
Actions workflows are separate
GitHub Actions workflow files are not converted by any of these import paths, so they must be rewritten as GitLab CI/CD pipelines and matched to runners. Repository imports bring the YAML files as ordinary content, which means they will not run until you adapt them. Plan secrets, environments and runner capacity on their own track, and check the sibling migration topic for the rewrite steps.
How the GitHub importer moves repositories, issues and pull requests
The GitHub importer creates one GitLab project for each repository you select, so the first decision is the target namespace and which repositories belong in the first wave. Start by authorizing GitLab to read from GitHub through the import page. Confirm the access token scope and the GitHub organization approval steps for your version before you begin, because organization policies on GitHub can block third-party access even when your account is an owner. Select repositories in batches so that a failed import is easy to trace and repeat.
Issues and pull requests do not arrive as identical objects. Pull requests are expected to appear as GitLab merge requests and issues as GitLab issues, with authors, comments and status carried across in some form. Field-level behavior varies by GitLab version, and the exact mapping for assignees, labels, milestones, reviews and closed or merged states should be checked in the GitLab import documentation for the version you run. Record which fields your team relies on, such as review approvals or linked issues, and test those on one project before importing the rest.
After each import, compare the source and target directly. Run git ls-remote against both the GitHub and GitLab remotes and check that branch and tag names and commit hashes match, including the default branch. Confirm that history is complete by comparing commit counts on key branches, and confirm that Git LFS objects and the wiki came across if your repositories use them. Then sample issue and merge request attachments, because some may remain as links to GitHub-hosted files. Confirm with GitLab whether attachments are copied or referenced on your version, and plan for access to the source if links remain.
Sequencing the move: inventory, pilot, parallel run, cutover and decommission
The sequence moves GitHub work into self-managed GitLab in stages, so each group of repositories and users is proven before the write freeze and GitHub is retired.
Inventory repositories, users, open work and workflows
Your team lists every repository, wiki, issue, open pull request, GitHub Actions workflow, webhook, deploy key and GitHub App integration, and records which ones are active. Assign an owner to each repository and note which contributors are members of the organization versus external. The output is a signed-off inventory that sets the scope of the import and flags what will not transfer through the GitHub importer, so the team knows which items need manual rebuilding. Confirm the current import behavior against the GitLab import documentation before the inventory is final.
Prepare the GitLab instance and upgrade plan
Before any import, the platform team installs or confirms the self-managed GitLab instance using an installation method that fits your infrastructure, sets the target edition, and records the version you will run. Plan the upgrade path now, because GitLab requires upgrades to pass through required upgrade stops, so the target version must be reached through the documented stops. Document the backup schedule and restore test for the instance. The output is a running, backed-up GitLab at a known version with a written upgrade path to the version you expect to maintain.
Set up Runners before pipelines move
The CI/CD team registers GitLab Runners with the executor types your workloads need, such as shell, Docker or autoscaling executors, and keeps Runner major and minor versions aligned with the GitLab version. Runners are isolated from production networks where your policy requires it, and tagged so jobs reach the right fleet. Pipelines are not moved until a test job runs successfully on each Runner pool. The output is a verified Runner fleet with a documented registration and version record.
Import a pilot group and map users
The team imports a small, representative set of repositories through the GitHub importer, including one with pull requests, issues and a wiki. Review how contributions are attributed, reassign placeholder users where needed, and confirm that access levels match your GitLab groups. Engineers in the pilot group work in the imported projects for a short, agreed period and report gaps. The output is a pilot report listing what transferred, what did not, and the user mapping rules the wider migration will follow.
Run a parallel period with both hosts readable
Repositories are imported in batches while GitHub stays readable and writable, so teams can compare histories and check that new activity is captured. The team keeps a log of which repositories have been synchronized, and reruns imports or applies manual corrections for any gap found. Pipelines are rebuilt as GitLab CI/CD in this period, but they do not run in production until cutover. The output is a batch-by-batch record showing that each repository has been imported, verified and assigned to a cutover wave.
Freeze writes on GitHub and cut over
For each cutover wave, the team announces a window, sets the GitHub repositories to read-only, performs a final import of any changes made since the last batch, and confirms the commit counts and branch heads match. Team members update remotes, CI/CD variables and external integrations to point to GitLab, and the first production pipelines run on the GitLab Runner fleet. The output is a verified wave where GitLab is the system of record and GitHub is read-only, with the freeze and remote-change steps recorded.
Decommission GitHub access and retain archives
After each wave has run stably, the team removes GitHub deploy keys, webhooks, GitHub Apps and personal access tokens that no longer serve a purpose, and archives the GitHub organization according to your records policy. Retain an export or backup that your legal and compliance owners have approved, and confirm the retention period with them. The output is a closed-out GitHub estate with a documented archive location and a list of revoked credentials.
Mapping GitHub contributors to GitLab users without losing attribution
Imported contributions can keep their original authors even when those authors have no GitLab account yet. During a GitHub import, GitLab attaches issues, comments, merge requests and commits to placeholder users, which are inactive accounts created for each GitHub author it cannot match. Once a contributor signs in to GitLab and accepts the reassignment, the placeholder's contributions move to their real account. Until then, the history reads as the placeholder, so the migration plan must say who will claim which contributions and by when. For large migrations, confirm the placeholder limits that apply to your instance and edition with GitLab before the import runs, because a count above the limit leaves some authors unclaimed. The detailed mechanics of placeholder users, reassignment and acceptance are covered in the sibling topic on mapping users and contributions after a GitLab import.
Identity planning decides how many contributors can claim their history cleanly. Start by exporting the list of GitHub authors and comparing their commit emails with the addresses your identity provider holds. GitLab matches accounts by verified email, so contributors who committed with personal or retired addresses will need manual claims. If you use SAML single sign-on, confirm whether your identity provider provisions users through SCIM before the import, since a provisioned account exists when the contributor first signs in and the claim is straightforward. Check which of the authentication methods in the GitLab authentication documentation your organization will enable, and whether self-registration is allowed. Finally, name the owner who approves reassignments, who answers attribution questions after cutover, and who decides how contractors and former employees are handled. Without these owners, placeholder history tends to remain unclaimed for months.
Moving GitHub Actions workflows: what to rebuild and where it runs
Each workflow file under .github/workflows must be rewritten as a .gitlab-ci.yml pipeline or an included file. Triggers, jobs, dependencies, artifacts and conditions map to GitLab keywords, but the syntax and execution model differ, so treat each workflow as a design task rather than a find-and-replace. Start with the workflows that build and test the most important repositories, and record what each one produces before you change it.
- Triggers and rules: Map
on:events torules:orworkflow:rulesso merge request and branch pipelines run under the same conditions as before. - Stages and dependencies: Group jobs into
stages:that reflect real dependencies. Jobs in the same stage run in parallel, and GitLab waits for each stage to finish before starting the next. - Artifacts and caches: Rewrite upload and download steps as
artifacts:andcache:with explicit paths and expiry times. - Matrix builds: Convert strategy matrices to
parallel:matrix:and confirm the generated job count matches what the team expects. - Container images and tools: Pin the
image:for each job and check that tool versions match the images the workflows used.
Before retiring a workflow, run the rewritten pipeline on a branch and compare the artifacts, test reports and deployment results with the original run.
Reusable workflows and composite actions carry shared build and deploy logic across many repositories. In GitLab, that logic usually moves into includes, project templates or CI/CD components. GitLab has no direct equivalent for every composite action, so each shared piece needs a decision about where it lives, who owns it and how projects pull it in.
- Includes: The
include:keyword pulls shared YAML from another project or file, which suits internal templates that change slowly. - CI/CD components: Versioned components with declared inputs suit shared build and deploy logic used by many projects.
- Composite actions: These often become a set of hidden jobs or a component, depending on whether other projects need to call them with parameters.
- Ownership: Decide which group owns the template project and who can approve changes that affect every pipeline that includes it.
- Version pinning: Pin component and include references to a tag or commit so one template change does not alter every pipeline at the same moment.
Check that every shared template resolves from the self-managed instance and that each reference points to a project the runners can reach. Confirm the current component and include syntax in the GitLab CI/CD documentation for your release, because these features change between versions.
GitHub-hosted runners do not come with a self-managed GitLab instance, so the team must provide the runners that execute jobs in its own infrastructure. GitLab Runner is distributed under the MIT license and supports shell, Docker and autoscaling executors across clouds and hypervisors. The right choice depends on the workflows, the isolation they need and how much build load arrives in bursts.
- Docker executor: Runs each job in a container, which fits workflows built around container images and gives each job a clean environment.
- Shell executor: Runs jobs directly on the host. It is simpler to set up, but jobs share host state, so the host needs tighter isolation.
- Autoscaling executors: Create machines on demand in clouds or hypervisors, which suits bursty CI load. Confirm which driver your platform supports before you design around it.
- Version alignment: Keep Runner major and minor versions aligned with the GitLab instance, as the GitLab Runner documentation advises.
- Isolation by sensitivity: Place jobs that deploy to production or touch regulated data on runners with their own tags and restricted access.
Confirm runner sizing, network reach to package registries, container registries and deployment targets, and the runner tags each job requests. For offline or air-gapped instances, confirm where images and packages will come from before the first pipeline runs.
Secrets and variables stored in GitHub do not move with the repository, so each value must be re-entered on the GitLab side. This is also the point to review who needs each value, because the scope that worked on GitHub may be wider than the workflow requires. Treat the re-entry as an access review, not only a copy task.
- Re-entry: Recreate each repository, organization and environment secret as a GitLab CI/CD variable with a named owner.
- Scope: Mark variables as protected and masked where they fit, and limit them to the branches and environments that need them.
- Environments: Map GitHub environments and their required reviewers to GitLab environments and protected environment rules.
- Credentials: Replace long-lived cloud keys with short-lived credentials where the target platform supports them, and confirm each integration in the GitLab documentation for your release.
- Inventory: Record each variable, who set it, and which pipelines use it, so the list can be reviewed before cutover.
Before cutover, confirm that no secret value appears in job logs and that each variable is readable only by the groups that need it. Run a test deployment to each environment using the new variables before the old workflow is disabled.
What drives the effort and cost of a GitHub to GitLab move
This table lists the factors that change the effort and cost of moving from GitHub to self-managed GitLab, and whether each one is paid once or recurs. Use it to see which parts of a written quote depend on your own estimates, such as repository count, user mapping and runner design. The main takeaway is that repository history and identity work set most of the one-time effort, while the target's high availability and runner fleet add recurring operating cost.
| Driver | Why it changes effort or cost | One-time or recurring | What to ask for in a quote |
|---|---|---|---|
| Repository count and history size | Each repository is imported and verified, and large histories take longer to transfer and check | One-time | Per-repository inventory, total size, and the import batches and verification steps included |
| Users and placeholder volume | GitHub contributors who never linked an account become placeholder users that must be reassigned or accepted | One-time | Count of contributors, expected mapping approach, and who performs reassignment |
| Open pull requests and issues | Open items, and their comments and attachments, need a decision on whether to import, close or recreate them | One-time | Counts of open pull requests and issues, and how attachments and review comments are handled |
| Wikis and project settings | Wikis, labels, milestones and settings may need separate handling because the importer scope is limited | One-time | List of what the importer covers and what is rebuilt by hand |
| Actions workflows, secrets and environments | Each workflow is rewritten as GitLab CI/CD, and secrets and environments must be recreated in GitLab | One-time, with recurring upkeep as pipelines change | Workflow count and complexity, secrets inventory, and whether the rewrite is in scope or separate |
| Branch protection and approval rules | GitHub rules are mapped to GitLab protected branches and approval settings, and each mapping must be tested | One-time, with recurring policy review | Inventory of rules, the GitLab tier needed for each approval feature, and the test plan |
| SSO and identity setup | Connecting GitLab to your identity provider, group sync and provisioning takes configuration and testing | One-time, with recurring admin effort for user lifecycle | Identity provider, protocols in scope, and whether SCIM provisioning is included |
| Runner fleet design | Runners execute CI/CD jobs, so executor type, scaling and isolation must be chosen and run after cutover | One-time build, recurring infrastructure and maintenance | Executor choice, expected job volume, hosting location, and who patches and upgrades runners |
| High availability or Geo requirements | Reference architectures and a secondary site add servers, databases and replication to build and operate | One-time build, recurring infrastructure and operations | Target user count, reference architecture, and whether a Geo secondary site is in scope |
What usually goes wrong in a GitHub to GitLab move and how to prevent it
Most problems in a GitHub to GitLab move come from the gap between what the importer moves and what teams assume it moves. The risks below cover identity, parallel running, secrets, runners and upgrades, and each one has a check you can run before cutover.
Lost attribution from unmapped users
Imported issues, comments and pull requests can arrive under placeholder users if contributors never match a GitLab account. Map every GitHub contributor before the import starts and confirm which accounts are reassigned and which remain placeholders. Review the list of unmatched authors with the people who own the repositories, and decide how to handle former employees and bot accounts. Check the placeholder limits for large migrations before you commit to a schedule.
Pull requests left open on both hosts
When teams keep reviewing on GitHub while the GitLab copy receives merges, the two histories diverge and reviewers lose track of which system is current. Freeze new pull requests on GitHub for each repository at its cutover point and close or redirect them with a link to the GitLab project. Agree on a single merge system per repository and record the date it changes, so the change record shows where code was reviewed.
Pipelines that run twice during parallel run
If both GitHub Actions and GitLab CI/CD build the same commits during a parallel period, deployments and notifications can fire twice and tests can compete for the same environments. Disable deployment jobs in one system before enabling them in the other, and confirm which pipeline owns each branch and environment. Check that scheduled workflows and webhooks are turned off on the retiring side as well as the active side.
Secrets not re-entered
The importer does not carry over GitHub Actions secrets, so pipelines that rely on them fail or, worse, fall back to values stored in code. Inventory every secret and variable each workflow reads, then re-enter them as masked and protected GitLab CI/CD variables at the right group or project level. Rotate credentials that were shared across repositories, and confirm that deploy keys and tokens for external systems are issued again by their owners.
Runners not aligned to GitLab versions
Jobs can fail or behave unexpectedly when GitLab Runner versions drift from the GitLab server version. The Runner documentation advises keeping major and minor versions aligned with GitLab, so pin Runner releases to the same major and minor version as your instance and test a sample pipeline on each executor type. Record which runner fleet serves which projects so that an upgrade of one side does not silently break the other.
Skipped upgrade stops after install
A fresh install on a current release can look finished, yet a later upgrade fails if the instance jumps past a required stop. Teams that install the latest version and plan to update later often discover the gap only when a patch is due. Record the upgrade path from the install version to the target before go-live, and schedule each required stop in your change calendar. Confirm which monthly and patch releases your team will track, since only the current stable line receives bug fixes.
Questions teams ask before moving from GitHub to GitLab
How much does it cost to move from GitHub to GitLab?
The cost has two separate parts: the GitLab subscription and the migration work. The subscription is licensed per user, and the GitLab pricing page lists Free at 0 US dollars, Premium at 29 US dollars per user per month billed annually, and Ultimate as custom priced, with all three available for self-managed installations. The migration work is quoted separately from a written scope. It is driven by the number of repositories and their size, how many pipelines must be rebuilt, the number of users whose contributions need mapping, the hosting design you choose, and how much testing and cutover support you need. Ask any partner for a quote that lists each component, what is one-time and what recurs, such as subscription renewals, runner capacity and ongoing support.
How long does a GitHub to GitLab migration take?
Duration depends mostly on scope and on how many repositories move in each wave. The main drivers are the count and size of repositories, the volume of issues, pull requests and wikis, how cleanly users can be matched between the two systems, and whether the target instance, runners and identity integration already exist. Pipelines usually take longer than repository transfer because Actions workflows are rewritten, not copied. Plan the cutover in waves, validate each wave against a checklist, and agree the timeline in writing before work starts. Ask for a schedule that separates repository import, user mapping, workflow rebuild and cutover, so you can see which step sets the critical path.
What does our engineering team need to provide for the move?
The work needs decisions and access more than heavy effort from your developers. Expect to provide a list of GitHub organizations and repositories in scope, a named owner for each group, and an approved target hostname and hosting location on infrastructure you control. Your team must confirm the GitLab tier, the identity provider used for sign-in, and who approves user mapping. Developers should be available to review imported projects, confirm branch protections and validate a sample of pipelines. Your security team should agree on access rules for administrators and runners before anything is connected. Write these responsibilities into the scope so that delays on either side are visible.
Can our own team run the GitHub to GitLab migration without a partner?
Yes, a capable internal team can run the import, provided it has someone who administers GitLab, someone who understands your GitHub setup, and time to test. The GitHub importer is documented in GitLab's import guidance, so the steps are known. The harder parts are rewriting Actions workflows, mapping users and contributions, rebuilding secrets and runner access, and planning a cutover that avoids double processing. Consider a partner if you have many repositories, tight regulatory evidence requirements, or no one who can own the instance after go-live. Whichever route you take, DeployOpen works on a fixed scope agreed in writing, and ongoing support is available as a retainer.
What does the GitHub importer not bring over to GitLab?
The importer covers repositories, issues, pull requests and wikis from GitHub, but several things must be checked and rebuilt separately. Review the imported result for each project rather than assuming completeness. Items to verify include GitHub Actions workflows, which are not converted automatically, repository secrets and environment variables, branch protection and review rules, webhooks and third-party app connections, and GitHub Packages or release assets. Confirm each item against the current GitLab import documentation for your version, because importer coverage changes between releases. Build an inventory of these items during discovery so nothing is lost at cutover.
How are GitHub Actions workflows handled after the import?
GitHub Actions workflows are moved as a separate project, not as part of the repository import. Each workflow is rewritten as GitLab CI/CD pipeline configuration, with jobs mapped to stages, self-hosted or GitHub-hosted runners replaced by GitLab Runners you operate, and secrets recreated in GitLab. The GitLab Runner documentation describes the executors and autoscaling options you can choose from. Keep pipeline changes in their own branch, run them in parallel with GitHub for a defined period, and compare job results before retiring the old workflows. Reusable workflows, marketplace actions and matrix builds need explicit design decisions. Treat pipeline rebuilds as an engineering task with its own acceptance tests.
How do we map GitHub users and contributions to GitLab accounts?
Imported issues, comments and pull requests initially belong to placeholder users until each person claims or is matched to a GitLab account. Map users before cutover by comparing email addresses and usernames, and decide how unmatched contributors will appear. Confirm that the people who will claim placeholders can sign in through your chosen identity provider, because sign-in determines how accounts are created. Plan for large contributor lists, since the number of placeholders and the approval steps affect how long reassignment takes. Keep a record of mappings as audit evidence for who authored which change.
Should we run GitLab in the cloud, in our data center or air-gapped?
Choose the hosting model before scoping the migration, because it changes the install method, backup design and runner placement. GitLab documents installation through the Linux package, the Helm chart, the GitLab Operator, Docker or from source, and it documents running self-managed instances in offline, air-gapped environments. The number of users and expected CI load determine whether a single node is enough or whether a high availability design is needed. Confirm with GitLab which features your offline or restricted environment supports, and plan upgrades around required upgrade stops from the start.
How DeployOpen scopes and delivers a GitHub to GitLab migration
DeployOpen is a Chainscore Labs company that plans, implements, integrates, hardens, backs up, upgrades, documents and supports self-hosted open-source platforms inside the client's own infrastructure, whether that is their cloud accounts, data centers or air-gapped networks. For a GitHub to GitLab move, that means the work covers the self-managed GitLab instance itself, the import of repositories, issues, pull requests and wikis, the user mapping plan, and the separate rebuild of GitHub Actions workflows. Most engagements are fixed-scope, and the scope is written down before any work starts: which GitHub organizations and repositories move, which GitLab edition and install method is used, how identity is connected, and what evidence is handed over at the end.
The written scope is the basis for the quote. Ask for it to name each deliverable, the environment it lands in, the acceptance checks (for example, a trial import of a representative repository set compared against the source, and a review of the placeholder user list before reassignment), and the dates the client must provide access, approvals and named contacts. Cost is driven mainly by the number of repositories and the amount of history and pull request data, how many users need contribution mapping, how many Actions workflows must be redesigned as GitLab CI/CD, and how much of the surrounding platform (identity, runners, backups) is built during the same engagement. Confirm with DeployOpen which of these items are in the fixed scope and which would be change requests.
Ongoing support, upgrades and continuous testing are available as retainers once the migration is complete. A retainer is the usual route for the recurring work that follows a cutover: applying GitLab minor and patch releases, planning required upgrade stops, reviewing runner fleet health and keeping backups tested. Ask for the retainer terms in writing, including what is covered, how requests are prioritized and how upgrade work is scheduled around the release cadence. DeployOpen does not publish a price list, so the retainer and the one-time migration are both priced from the written scope.

