Migrate from Bitbucket Cloud to GitLab: what the move involves and how much effort it takes
A team on Bitbucket Cloud can move repositories and pull requests into self-managed GitLab with the built-in Bitbucket Cloud importer, but the import is the smallest part of the effort. The GitLab import documentation covers Bitbucket Cloud as a source, so the repositories, and much of the pull request history, can be brought across without rewriting code. The harder work is everything around the code: rebuilding Bitbucket Pipelines as GitLab CI/CD, deciding how runners will execute jobs inside your own network, connecting identity so people sign in through your directory, and sequencing the cutover so developers are not pushing to two systems at once. Pipelines do not convert automatically, so every bitbucket-pipelines.yml file needs to be read, understood and rewritten as a .gitlab-ci.yml configuration, with its variables, deployment steps and caches checked one by one.
The effort depends on three counts you should establish before asking for a quote: the number of repositories, the number of open pull requests you need to keep, and the number of pipelines and deployment targets each repository depends on. A team with a handful of repositories and no active branches can often cut over in one pass. A team with hundreds of repositories, long-lived feature branches and pipelines that deploy to production will spend most of its time on pipeline logic, secrets and runner capacity. Ask the partner to inventory these counts with you, to identify which pipelines use Bitbucket-specific features, and to list which items the importer will not carry, such as pipeline history, pipeline variables and workspace-level settings. Confirm the list against the current importer documentation and your own export before you agree the scope.
Costs split into one-time and recurring parts. One-time costs include discovery, pipeline rewriting, runner setup, identity integration, testing and the cutover itself. Recurring costs include the GitLab subscription tier you choose, the compute your runners consume, and the staff time needed to patch and back up the instance. Ask any quote to state which items are fixed, which depend on the inventory, and who performs each one. Plan for a parallel period where Bitbucket remains read-only for reference, and agree in writing what happens to open pull requests on the cutover day.
Which Bitbucket Cloud features map to GitLab and which need rebuilding
This table compares Bitbucket Cloud capabilities with their GitLab equivalents and marks whether the import path is documented, partial, or manual. Use it to scope what the importer moves and what your team must recreate by hand. Confirm exact coverage for your Bitbucket Cloud data against the GitLab import documentation before you plan the cutover.
| Bitbucket Cloud capability | GitLab equivalent | Migration notes |
|---|---|---|
| Git repositories and branches | Projects with Git repositories and branches | Documented: the importer reads Bitbucket Cloud repositories by project. Confirm which refs and large histories come across for your repositories. |
| Pull requests | Merge requests | Partial: open and historical pull requests are mapped to merge requests, but confirm the state, source and target branch handling for your data. |
| Comments and reviewers | Merge request comments, discussions and reviewers | Partial: comment content is expected to carry over, but authorship and reviewer approvals may need checking after import. |
| Issues | GitLab issues | Documented: Bitbucket Cloud issues are importable. Confirm which fields, attachments and comments your issue tracker setup includes. |
| Wikis | Project wiki | Manual: plan a separate Git clone and push of the wiki repository, then verify links and page structure. |
| Bitbucket Pipelines and pipeline variables | GitLab CI/CD pipelines and CI/CD variables | Manual: the importer does not convert pipeline definitions. Rewrite bitbucket-pipelines.yml as .gitlab-ci.yml and recreate variables with their protected and masked settings. |
| Deployment environments | GitLab environments and deployment jobs | Manual: environment names, deployment permissions and environment-scoped variables must be recreated and tied to your runners. |
| User access and branch permissions | Project and group roles, protected branches and approval rules | Manual: Bitbucket Cloud user and branch restrictions are not carried over as-is. Map roles to GitLab groups and set protected branches and approvals again. |
Documented import paths and the tools that support them
Bitbucket Cloud content reaches self-managed GitLab through a small set of documented paths, and each covers a different slice of the migration. Use the importer for repositories and pull requests, the URL import for leftover Git content, and the API and Runner to script the rest and prove the result. The GitLab import documentation sets the boundary of each path.
GitLab Bitbucket Cloud importer
This is the built-in importer that brings Bitbucket Cloud repositories and pull requests into a GitLab project. It needs an authorized connection to your Bitbucket Cloud workspace, so the account used must be able to read every repository in scope. Confirm in the current import documentation for your target version which authorization method applies, which object types are supported, and what limits apply to large projects. Plan a test import of a representative repository before running the full set.
Generic import by repository URL
Where the Bitbucket importer does not reach a repository or its content, the generic Git import by URL can copy the Git history itself. This path carries commits, branches and tags but not Bitbucket-specific metadata such as pull request discussions. Use it for repositories the importer skipped and check that default branches and tags match the source after the copy. Credentials for private sources should be scoped to read-only access and removed after the import.
GitLab API for scripted imports
The GitLab REST API lets your team create projects in bulk, assign them to groups and record which source repository maps to which target. Scripts make the migration repeatable and give you a written inventory to check against the Bitbucket workspace. Ask your team to confirm the endpoints, token scopes and rate behavior for your GitLab version before writing the script. Use the same inventory to verify branch counts, default branches and access settings after each batch.
GitLab Runner for rebuilt pipelines
Bitbucket Pipelines does not run inside GitLab, so each pipeline needs a rewritten .gitlab-ci.yml and a Runner to execute its jobs. Choose the executor and host for the Runner based on the build images, secrets and network access your pipelines need. Keep Runner versions aligned with the GitLab instance, as the Runner documentation advises. Run rebuilt pipelines against a non-production project before the cutover.
Import documentation as the scope reference
The GitLab import documentation is the authoritative list of what each importer supports for each source. Treat it as the reference for the migration inventory, and record every item that falls outside it, such as items that need manual recreation or external tooling. Re-check the page against the GitLab version you deploy, because supported objects change between releases. Give the migration owner a written sign-off of the gap list before anyone begins the cutover.
How the Bitbucket Cloud importer runs and where it stops
Start by creating the destination group in your self-managed GitLab, then open the Bitbucket Cloud import option there and authorize GitLab against Bitbucket Cloud. The account you authorize determines what the importer can read, so confirm before the run that it has read access to every workspace and repository in scope. Next, select the workspaces and repositories to bring across. For each selected repository, GitLab creates a project that carries the Git history and the pull request data. Run a small trial batch first. The trial shows how access, naming and group layout behave in your instance before you commit the full list.
After each run, verify every project against its Bitbucket source instead of accepting the import status as proof. Compare the commit history on the default branch and on your long-lived release branches, and confirm that the latest commits match the same hashes on both sides. Check that branches and tags are present and point at the expected commits. For pull requests, confirm that open requests remain open, that merged and declined requests keep their final state, and that comments and review threads sit on the correct requests. Then check how contributions display. Commits and comments should show the original author name, but whether they link to a real GitLab account depends on a later step, so do not treat that link as finished at this stage.
Treat the first run as a test of attribution as well as data. Contributions are tied to people through a separate mapping process that depends on how Bitbucket account identities correspond to GitLab accounts, so the results will differ by team and by user. The topic on mapping users and contributions after a GitLab import covers that process. Before you set a cutover date, check the GitLab import documentation for the current list of Bitbucket Cloud objects the importer handles, and plan manual work for anything outside that list. Record what you checked, who signed off, and which items you moved by hand, because change reviewers will ask for that record.
The migration sequence from inventory to decommission
This sequence moves Bitbucket Cloud work into self-managed GitLab in deliberate stages, so each group of repositories is proven on the new platform before the old one stops taking writes. Each step closes with evidence the team can review before moving on.
Inventory workspaces, repositories and pipelines
The migration lead and each team lead list every Bitbucket Cloud workspace, repository, open and recent pull request, Pipelines configuration, pipeline variable, deploy key and user with write access. Record which repositories are active, which carry large binary history and which depend on Bitbucket-specific features such as apps or webhooks to other systems. Done means a signed-off inventory spreadsheet where each item has an owner, a target GitLab group and a decision: migrate, archive or leave behind. Confirm the importer's current coverage against the GitLab import documentation before you assume any item will transfer.
Run a pilot import of a representative group
The GitLab administrator imports one representative group of repositories with the Bitbucket Cloud importer into a test group on your self-managed instance. Choose a group that includes a busy repository, one with a long pull request history and one with a pipeline. Compare commit counts, branches, tags, open pull requests and comments against the source, and record every difference. Done means a written comparison report that lists what came across, what did not and what needs manual recreation, approved by the team lead who will own the group after cutover.
Rebuild a pilot pipeline in GitLab CI/CD
A DevOps or platform engineer rewrites one Bitbucket Pipelines configuration as a
.gitlab-ci.ymlfile in the pilot project. The pipeline runs on a GitLab Runner that your team installs and controls, with the executor chosen to match the build's needs. Variables move into GitLab CI/CD variables with protection and masking set deliberately, not copied by default. Done means a green pipeline on the pilot branch, a documented list of differences in images, caching and deployment steps, and a runner registration record your security team has reviewed.Validate in a parallel run
For a defined period, both Bitbucket Cloud and GitLab hold current code for the migrated groups, and developers push to GitLab while the old pipelines are kept available for comparison. The team opens merge requests in GitLab, runs reviews, approvals and pipelines, and checks that protected branch rules, code owners and required checks behave as the policy requires. Done means a list of validated merge request and pipeline scenarios, each marked as passed or with an agreed fix, and a sign-off from the engineering manager and security lead for the group.
Cut over with a write freeze and final check
The migration lead announces a write freeze on the affected Bitbucket Cloud repositories, so no new pushes or pull request changes land there. The team runs a final incremental import or push of the remaining commits, verifies refs and pull request state against the frozen source, then updates developer remotes, CI integrations and documentation to point at GitLab. Done means a cutover record listing each repository, the final commit hash checked, the time of the freeze and the person who confirmed the match.
Decommission Bitbucket Cloud access after retention
After the retention period your organization sets, the administrator removes developer and integration access to the migrated Bitbucket Cloud repositories, revokes app passwords and deploy keys, disables Pipelines for those repositories and archives an export that your records policy requires. Confirm with your legal or compliance owner what must be retained and for how long before the period starts. Done means an access revocation checklist, a stored archive with a checksum recorded, and a closed inventory item for each repository in the first step.
What drives the effort and cost of a Bitbucket Cloud to GitLab move
This table compares the factors that change the effort, cost and risk of moving from Bitbucket Cloud to self-managed GitLab, and what to ask for in a quote. Read each row as a question to answer about your own estimate before you ask a partner for a fixed-scope proposal. The main takeaway is that most cost comes from pipeline rewrites, identity setup and the open pull request and runner decisions, not from the import itself.
| Driver | Why it changes effort or cost | What to ask for in a quote |
|---|---|---|
| Repository count and history size | Each repository needs its own import, verification and sign-off. Large Git histories and binary files lengthen transfers and raise storage needs on the instance. | A per-repository inventory with history size, LFS usage and the import method planned for each group. |
| Bitbucket Pipelines files | Every bitbucket-pipelines.yml must be read and rewritten as .gitlab-ci.yml. Pipelines with many branches, deployments and custom steps take longer to validate. | A count of pipeline files, steps per file, and which pipelines will be rebuilt versus retired. |
| Pipes and custom Docker images | Bitbucket pipes have no direct one-to-one equivalent in GitLab CI/CD. Each pipe needs a replacement script, a CI component or an image, and each custom image needs a registry location. | A list of pipes in use, the replacement chosen for each, and who maintains any custom images. |
| Secrets and deployment variables | Repository, workspace and deployment variables must be recreated in GitLab with the right scopes and protection. Deployment targets often need new credentials. | Confirmation of how secrets will be recreated, rotated and access-restricted, and who approves the change. |
| Open pull requests at cutover | Open pull requests do not carry over as-is. The team must decide whether to merge, close or recreate them, which adds coordination work near the cutover date. | A cutover plan that states how open pull requests are handled and the freeze window for each repository. |
| User count and identity integration | Contribution mapping depends on how many users and placeholder accounts exist. Connecting self-managed GitLab to corporate identity (such as SAML or SCIM) adds configuration and testing. | The user count, the identity provider in use, and whether SAML, SCIM and two-factor enforcement are in scope. |
| Runner hosting and capacity | Bitbucket Pipelines runs on Atlassian-hosted infrastructure. In GitLab, you provide runners, their hosts, network access and autoscaling, which becomes a new set of components to build and operate. | Runner executor choice, host sizing assumptions, and who will own patching and capacity after handover. |
| GitLab tier needs | Advanced CI/CD features are listed for Premium on the GitLab pricing page, and Ultimate covers security and compliance features. The features your pipelines rely on decide which tier is needed. | A feature check against the GitLab pricing page, with the tier recommended and the reason for it. |
| One-time versus recurring costs | One-time work covers migration, pipeline rewrites and testing. Recurring costs include GitLab subscriptions (Premium is listed at 29 US dollars per user per month billed annually on the GitLab pricing page, and Ultimate is custom priced), server and storage hosting, runner compute and support. | Separate one-time and recurring line items, the subscription tier and billing basis, and who pays for hosting, runners and support each year. |
Rebuilding Bitbucket Pipelines as GitLab CI/CD
Bitbucket Pipelines are not carried over by the importer, so the pipeline rewrite is a design task rather than a conversion. Start by inventorying each bitbucket-pipelines.yml file and listing its pipelines, branch and tag triggers, steps, caches, services and deployment environments. Then map each Bitbucket step to a GitLab CI/CD job, group jobs into stages that reflect the order your team needs (build, test, package, deploy), and decide which jobs must wait for others with needs. Bitbucket pipes are the part that needs the most judgment. Some pipes wrap a single command that you can replace with a plain script block. Others wrap a vendor tool that you can run from a container image you control, and a few are best replaced by a shared template pulled in with include. Confirm for each pipe whether its image and version are approved for your environment before you copy it.
Variables need the same care. Repository variables and deployment variables in Bitbucket become CI/CD variables in GitLab, set at project or group level, with protected and masked flags chosen per variable. Protected variables are exposed only to pipelines on protected branches or tags, and masked variables are hidden in job logs, so deployment credentials should usually be both. Masking has conditions, such as minimum length and character set, so test that each secret value meets them. Choose runner executors based on what each job needs: shell executors suit tightly controlled hosts, Docker executors suit isolated builds, and autoscaling executors suit variable load on cloud or hypervisor capacity you run. Keep GitLab Runner at the same major and minor version as your GitLab instance, and confirm that your runner hosts are reachable from the instance.
Test the rebuilt pipeline on a pilot project before any production deployment job runs. Use a non-production environment, run the pipeline on feature branches and on a protected branch, and check that variables, artifacts, caches and approvals behave as they did in Bitbucket. GitLab Runner is distributed under the MIT license, so its use inside your infrastructure does not add a license fee, though you should confirm any other tool a job pulls in. Only after the pilot passes should you enable deployment jobs against production, and keep the Bitbucket pipeline available until the cutover is complete so you can compare results.
What usually goes wrong in a Bitbucket Cloud to GitLab migration
Most failed Bitbucket Cloud to GitLab moves fail on people and process, not on the importer. The risks below share one root cause: the old system keeps working in parallel while the new one is only partly configured. Each card names a check to run before the cutover date.
Open pull requests lose review context
Open pull requests, their comments and approvals do not all arrive intact in GitLab, so reviewers can lose the discussion that justified a change. Decide which open pull requests must finish in Bitbucket Cloud and which are recreated as merge requests. For recreated ones, record the approval state and the reasoning in the description. Check a sample of imported merge requests against the originals before cutover.
Secrets copied without protection
Pipeline variables and deployment credentials are often either copied in plain form or never moved at all, which breaks deployments silently. Mark sensitive values as masked and protected, scope them to environments, and rotate any credential that was visible in the old system. Build an inventory of every Bitbucket repository variable and deployment variable before the first pipeline runs in GitLab.
Developers push to both systems
During a parallel run, some developers keep pushing to Bitbucket Cloud while others move to GitLab, and the two histories diverge. Set a dated freeze on the Bitbucket repositories, make them read-only once the final sync is done, and announce which system is authoritative for each project. Confirm the freeze with repository permissions rather than relying on a message alone.
Shared runners with broad access
Runners registered once and shared across every project can run jobs from unrelated repositories and reach credentials they should not see. Before migrating pipelines, decide which projects need dedicated runners, what each runner can reach on the network, and whether the executor isolates jobs. Document the runner tags that map to each former Bitbucket pipeline environment.
Permissions that do not match old roles
Bitbucket workspace roles, project permissions and repository permissions do not map one-to-one onto GitLab groups, projects and access levels. Users who were admins in Bitbucket can end up with too little access, and contractors can end up with more. Write a role mapping table for each group, review it with project owners, and confirm that imported contributions are attributed to the right accounts.
No tested rollback or retained copy
Without a tested restore and a retained Bitbucket Cloud copy, a bad import or a missing project has no safe way back. Keep the Bitbucket Cloud workspace read-only for a defined period after cutover, and export repositories separately as Git mirrors. Run a restore of the GitLab backup into a staging instance before cutover, following the GitLab backup and restore documentation, and record how long it took.
Questions buyers ask before moving from Bitbucket Cloud to GitLab
What does a Bitbucket Cloud to GitLab migration cost, and what drives the price?
The cost comes from the work, not from the import itself: the importer moves repositories and pull requests, but someone has to inventory the workspace, decide how users and pull request history map across, rebuild Bitbucket Pipelines as GitLab CI/CD, and test the result. Most engagements are fixed-scope, so the quote follows from a written list of repositories, pipelines, integrations and deliverables. DeployOpen does not publish a price list. The main cost drivers are the number of repositories and pipelines, how many pipelines use Bitbucket-specific features, the number of integrations to reconnect, and how much history and access control must be preserved. Ask for the one-time migration effort separately from any recurring support or upgrade retainer, and ask what is excluded. GitLab's own list prices are a separate line item: the pricing page shows Premium at 29 US dollars per user per month billed annually, with Ultimate custom priced.
How long does the migration take?
The duration depends on scope, not on a single standard timeline. A workspace with a few small repositories and no pipelines moves very differently from one with hundreds of repositories, long pull request histories, and dozens of interdependent pipelines. The main variables are the number of repositories and their size, how many pipelines must be rebuilt, how many integrations and webhooks must be reconnected, whether users must be mapped before cutover, and how many teams must test and approve the new pipelines. Ask any partner for a timeline tied to a written scope with named phases and a cutover plan. Also confirm whether the timeline includes a parallel run period, the time your teams need to review pipeline results, and any change freeze windows in your organization.
What does our team have to provide during the migration?
The migration needs decisions and access from several people, and it stalls without them. Expect to provide a workspace admin who can grant the import access and approve repository transfers, owners on the Bitbucket side who can confirm which repositories are active and archived, and a named owner on the GitLab side who will create groups, projects and runner settings. Each pipeline needs an owner who knows what the job does, which secrets it uses, and how it deploys. You also need a decision on pull requests: whether open ones are recreated, closed before cutover, or left as reference in the old system. Finally, your security team should confirm how users, roles and access will be mapped. Agree these responsibilities in writing before work starts.
Can our own engineers do this with the GitLab documentation?
Yes, for a small and simple workspace. The GitLab import documentation covers importing from Bitbucket Cloud, and a team with GitLab administration experience can import repositories and rebuild simple pipelines without outside help. The harder parts are the ones the documentation cannot decide for you: how to sequence cutover across many teams, which pipelines can be retired, how to handle secrets and deployment credentials, how runners will be sized and isolated, and how to verify that a rebuilt pipeline behaves like the original. Teams that run this work themselves usually plan it as a project with an owner, a test environment and a rollback path. A partner is most useful when the pipeline count is high, when the deployment logic is complex, or when your team cannot take the time from delivery work.
Do Bitbucket Pipelines convert to GitLab CI/CD automatically?
No. The import brings over repositories and pull requests, but Bitbucket Pipelines configuration must be rewritten as GitLab CI/CD pipelines. The two systems share the same general idea of jobs triggered by repository events, but the syntax, the way steps and stages are organized, the way variables and secrets are stored, and the way execution hosts are provided all differ. A typical rebuild maps each pipeline to a .gitlab-ci.yml file, replaces Bitbucket images and caches with GitLab equivalents, moves deployment credentials into GitLab CI/CD variables with appropriate protection settings, and assigns jobs to runners. Confirm with the GitLab migration guidance which Bitbucket features in your pipelines have direct equivalents and which need redesign before you estimate effort.
Which GitLab tier do we need for the pipeline features we use?
Start with an inventory of what your pipelines and repositories actually use, then match it to the tier. The GitLab pricing page lists advanced CI/CD and priority support for Premium, and application security testing, software supply chain security, vulnerability management and compliance features for Ultimate. GitLab Free is listed at 0 US dollars and covers basic source control and CI/CD. All three tiers are available for self-managed installations, so the tier choice follows features, not hosting. Check each pipeline for features such as advanced pipeline configuration, deployment approvals, security scanning or compliance controls, and confirm current feature availability with GitLab before you commit, because tier contents change over time.
Can we migrate without running Bitbucket and GitLab in parallel?
Sometimes, but a parallel run is the safer default for any workspace with active pipelines. A direct cutover works when the repository count is small, the pipelines are simple, and the team can freeze changes for a short window. For larger workspaces, running both systems for a period lets teams verify rebuilt pipelines against real commits before they stop using Bitbucket. The trade-off is double maintenance, duplicated webhooks and the risk of work landing in the wrong system. Decide the cutover pattern per group of repositories, record which system is the source of truth for each, and set a date after which Bitbucket becomes read-only. Confirm how long your teams need to validate pipelines and whether your change process allows a read-only freeze.
How DeployOpen scopes, delivers and supports the migration
DeployOpen plans and delivers the move from Bitbucket Cloud to a self-managed GitLab instance inside your own infrastructure. Each engagement starts with a written scope that covers repositories, pull request history, pipelines and cutover, and the work is handed over with documentation your team can operate.
How we scope
- Inventory of Bitbucket Cloud workspaces, repositories, branches, pull requests and pipeline definitions to move
- Written decision on which pull request history, issues and attachments are imported and which are archived
- Agreed plan for rebuilding Bitbucket Pipelines as GitLab CI/CD and the runners that execute them
- Engagements are fixed-scope, with deliverables and timelines agreed in writing before work starts
How we build
- Target GitLab instance and install method set up in your cloud account, data center or air-gapped network
- Test import of sample repositories and pull requests, checked against the source for gaps and user mapping issues
- Pipeline rewrite to GitLab CI/CD with runner configuration, variables and protected secrets moved deliberately
- Verification of repository counts, commit history, branch protection and pipeline runs before the cutover date
How we hand over
- Runbook covering the import steps, cutover sequence and rollback path, written for your platform team
- Documented list of what did not transfer, with the manual steps your team or developers must complete
- Pipeline and runner documentation so engineers can change jobs and add projects after handover
- Knowledge transfer sessions with the engineers who will run the GitLab instance day to day
How we support
- Post-cutover review of imported projects, open merge requests and failing pipelines within the agreed scope
- Ongoing support, upgrades and continuous testing are available as a retainer, scoped separately from the migration
- Guidance on GitLab upgrade stops and backup routines so the new instance stays maintained after go-live
- Confirm with DeployOpen during scoping which support hours, response expectations and retainer terms apply

