Migrating CI/CD pipelines is a common task for DevOps engineers, especially as teams move toward more unified and modern delivery platforms. In this real-world case study, we walk through the process of migrating multiple Bitbucket repositories and pipelines to GitHub, while modernizing CI workflows using GitHub Actions.
The repositories in scope were responsible for firmware builds based on OpenWRT and Yocto, two widely used Linux-based frameworks for embedded systems. The goal was not only to migrate source code, but also to consolidate fragmented pipeline implementations into a single, consistent CI/CD architecture.
This article is Part 1 of a migration series, focusing on repository migration and preserving Git history, structure, and dependencies.
Migration Context and Challenges
The repositories involved in this migration had different CI implementations:
- Some relied on Bitbucket Pipelines, using YAML-defined workflows
- Others executed Bash scripts through Jenkins jobs
One of the key challenges was unifying these approaches under a single CI/CD platform. Migrating everything to GitHub and standardizing on GitHub Actions provided a clean and maintainable foundation for future development.
Migration Strategy Overview
The migration process consisted of three main goals:
- Move repositories from Bitbucket Cloud to GitHub Cloud
- Preserve full Git history, including branches, tags, and references
- Ensure submodules and dependencies continue to function correctly
Because this was a Bitbucket Cloud environment, not all migration tools were applicable. A direct Git-based approach was selected to ensure full control and transparency during the process.
Step-by-Step: Migrating Repositories from Bitbucket Cloud to GitHub Cloud
Step 1: Create a New Repository on GitHub
For each project, an empty repository was created in GitHub using the same repository name as the original Bitbucket repository. This repository served as the migration destination.

Step 2: Clone the Bitbucket Repository Using
To preserve the complete repository state, including all branches and tags, a mirrored clone was created:
git clone --mirror git@bitbucket.org:workspace/project-repo.git
This creates a bare repository containing all Git objects and references, without checking out a working directory. A typical structure looks like:
branches config description HEAD hooks info objects packed-refs refs
This confirms that the full Git history is available locally.
Step 3: Push the Mirrored Repository to GitHub
Next, the mirrored repository was pushed to GitHub:
cd project-repo.git
git push --mirror git@github.com:workspace/project-repo.git
This ensures that all branches, tags, and references are transferred exactly as they existed in Bitbucket Cloud.
Step 4: Perform Sanity Checks After Migration
Once the repository was pushed, several checks were performed:
- Verify that all branches were migrated correctly
- Confirm tags and commit history are intact
- Ensure the correct default branch is set
If necessary, the default branch can be updated in GitHub via:
Repository → Settings → General → Default branch

These checks ensure the repository is ready for CI/CD pipeline migration.
Step 5: Updating Git Submodules After Migration
Many of the migrated repositories relied on Git submodules. After migration, all submodule URLs needed to be updated to point to GitHub instead of Bitbucket.
Before migration:
[submodule "submodule_name"]
path = submodule_path
url = git@bitbucket.org:workspace/dependant-repo.git
After migration:
[submodule "submodule_name"]
path = submodule_path
url = git@github.com:workspace/dependant-repo.git
Once updated, initialize and update submodules:
git submodule update --init --recursive
Synchronizing Nested Submodules
In some cases, submodules themselves contained nested submodules. These nested dependencies might still reference old Bitbucket URLs.
To ensure Git’s internal configuration is fully updated, run:
git submodule sync --recursive
This step is especially important when working with previously initialized repositories.
Preserving Submodules at Specific Commit SHAs
Some submodules were pinned to specific commit SHAs to ensure build stability. Preserving these references is critical for firmware builds and reproducible pipelines.
Steps to preserve a specific commit:
cd submodule_path
git checkout <commit-sha>
Then return to the main repository:
git add submodule_path
git commit -m "Preserve submodule at specific commit SHA"
git push origin <branch-name>
This ensures the repository continues using the exact submodule version required by the build process.
What’s Next: Migrating CI Pipelines to GitHub Actions
This concludes Part 1 of our migration journey series.
In the next post, we’ll focus on: updating project dependencies securely, ensuring that builds and pipelines we plan to migrate continue to function without disruption. Stay tuned for Part 2!
Check out more of our blog posts here.
