How to Migrate from CircleCI to GitHub Actions Across Every Repo
Mandy Singh
June 29, 2026 · // 9 min read
Your organization picked GitHub Actions. The decision is made. Now someone has to rewrite the CI configuration in every repository that still runs on CircleCI.
For any single repo, this is a contained task. A developer who knows both platforms can convert one config.yml in about 30 minutes. The trouble starts when you multiply that by 200 repositories, add a review cycle with each owning team, and try to track which repos are done and which have not started. The work that took 30 minutes per repo becomes weeks of calendar time once coordination is part of it.
So most teams run both CI platforms in parallel for far longer than planned. They keep paying for two systems while the migration stalls somewhere around 60% complete and the remaining repos sit on the backlog.
This guide covers both halves of the problem. First, how to convert a CircleCI config to a GitHub Actions workflow by hand. Then how to run that same conversion across every repository at once.
What migrating from CircleCI to GitHub Actions actually involves
Migrating from CircleCI to GitHub Actions means replacing each repository’s .circleci/config.yml with an equivalent workflow file under .github/workflows/, then removing the CircleCI directory. The build logic stays the same. The CI platform that runs it changes.
The reason this is more than a copy-paste job is that the two formats do not map one-to-one. CircleCI organizes pipelines with orbs, executors, jobs, and workflows. GitHub Actions uses event triggers, runners, jobs, and steps, with reusable logic pulled from the Actions Marketplace. Most of the concepts have a clear equivalent. A few need a decision from whoever owns the repo.
How to convert a CircleCI config to a GitHub Actions workflow
The conversion follows the same sequence for every repository.
- Read the existing
.circleci/config.ymland identify the orbs, executors, jobs, and workflow dependencies it uses. - Create a new
.github/workflows/ci.ymlfile with the matching event triggers, runner, and steps. - Map each CircleCI concept to its GitHub Actions equivalent.
- Preserve build steps, environment variables, caching, and artifact handling exactly as they were.
- Remove the
.circleci/directory. - Open a pull request that lists any manual follow-up, such as secrets that need to be recreated.
Step 3 is where most of the thinking happens. This mapping covers the cases you hit in almost every repo.
| CircleCI | GitHub Actions |
|---|---|
version: 2.1 plus a workflows block | name: plus on: event triggers (push, pull_request) |
Orb commands (for example node/install-packages) | A Marketplace action (actions/setup-node) or an inline run: step |
| Executors and Docker images | runs-on: for the runner, container: when a job needs a specific image |
jobs and steps | jobs and steps, with similar structure |
checkout | actions/checkout@v4 |
save_cache / restore_cache | actions/cache, or the built-in cache: option on setup actions |
store_artifacts | actions/upload-artifact@v4 |
Job dependencies via requires | needs: on the dependent job |
| Project environment variables | Repository secrets, referenced through env: |
Here is a typical Node build before and after the conversion.
# .circleci/config.yml (removed)
version: 2.1
orbs:
node: circleci/node@5.0
executors:
default:
docker:
- image: cimg/node:18.17
jobs:
build:
executor: default
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run: npm run build
- run: npm test
- store_artifacts:
path: coverage/
workflows:
main:
jobs:
- build
# .github/workflows/ci.yml (new)
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
- run: npm test
- uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage/
A handful of cases need a per-repo decision rather than a direct mapping.
Parallel jobs that CircleCI expresses through a workflows block become needs: declarations in GitHub Actions, which preserves the dependency graph. If a build needs internal resources, swap ubuntu-latest for your self-hosted runner label. In a monorepo, add paths: filters under on.push so a build only triggers when the relevant directory changes. When you test across multiple runtimes, add a strategy.matrix block. And every environment variable stored in CircleCI project settings has to be recreated as a GitHub Actions secret, so note the required secrets in the pull request rather than assuming the next person will know.
Why the migration stalls once you have hundreds of repos
The conversion above is well understood. Run it once and you have a working template. The reason a platform team can lose a quarter to this is not the YAML.
Each repository is owned by a different team. Each conversion is a pull request that someone on that team has to review, run their pipeline against, and merge. Multiply that by the number of repos and the real work appears: explaining the change to every team, getting each one to prioritize it over their own roadmap, tracking who has merged and who is blocked, and following up on the long tail that never quite gets done. The change is small in every repo. Coordinating it across the whole organization is the job.
This is the layer that breaks when you go from 5 repos to 500. The script that converts one config does not chase down the forty teams that still have not merged.
How to run the migration across every repository at once
The conversion logic is mechanical and the validation already exists in the form of each repo’s own pipeline. The missing piece is execution across the whole codebase plus the coordination to drive every pull request to merge. That is the gap Tidra closes.
Tidra is an AI coding agent for both implementation and coordination of code changes across your organization. For a CircleCI to GitHub Actions migration, it runs the same sequence you would run by hand, in every targeted repository:
- It reads each repository’s
.circleci/config.ymlto understand the pipeline, including orbs, executors, and workflow dependencies. - It maps the CircleCI concepts to their GitHub Actions equivalents, turning orb commands into action steps or inline scripts, executors into runner specifications, and workflow triggers into
on:event configurations. - It generates the new
.github/workflows/ci.yml, preserving build steps, environment variables, caching, and artifact handling. - It removes the
.circleci/directory. - It opens a pull request with a description of what changed and any manual follow-up, such as the secrets a repo needs configured.
You define the change once and review the plan before any code is generated. You see the diffs per repo, regenerate anything that came out wrong, and bulk-create the pull requests when you are satisfied. From there the same dashboard tracks which PRs are open, merged, or blocked, and handles the follow-up with each team. One platform engineer described a recent migration this way: “This would have taken us 6 weeks to coordinate manually. We did it in 3 days.”
A note on what this does and does not do. The generation is not deterministic, which is why the workflow is review-first rather than fire-and-forget. You iterate on the plan and the diffs, and nothing reaches a pull request until you approve it. Secrets are the one piece that stays manual on purpose, since the values live in your settings, not your config files. Self-hosted runners and matrix builds are worth a closer look on the repos that use them. The point of reviewing before merge is to catch those cases while they are still a diff.
If you want to run the conversion yourself first on one repo, this is the prompt that drives it:
Migrate our CI/CD pipeline from CircleCI to GitHub Actions. For each repository:
1. Read the existing .circleci/config.yml
2. Create an equivalent .github/workflows/ci.yml
3. Map CircleCI jobs to GitHub Actions jobs, preserving:
- Build steps and commands
- Environment variables (use GitHub Actions env: syntax)
- Caching strategies (convert CircleCI cache keys to actions/cache)
- Artifact uploads (convert to actions/upload-artifact)
- Service containers (convert to services: in the job)
4. Use ubuntu-latest as the runner unless the CircleCI config specifies a specific OS
5. Convert CircleCI orb commands to their equivalent GitHub Actions or inline steps
6. Preserve any workflow-level dependencies between jobs using needs:
7. Remove the .circleci/ directory
Do not modify any application code, test commands, or build scripts, only migrate the CI configuration.
Run it on one repository and you have a template you trust. The question that decides how long the whole migration takes is whether you apply that template one pull request at a time, or across every repo in one initiative.
Run this migration across your repos: tidra.ai/initiatives/migrate-circleci-to-github-actions
Frequently asked questions
How long does a CircleCI to GitHub Actions migration take? Converting a single repository takes roughly 30 minutes for someone who knows both platforms. Across a large codebase the limiting factor is coordination, not conversion: reviewing, merging, and following up across dozens of teams is what stretches the timeline into weeks or months. Running the conversion and the pull requests across all repos at once removes most of that calendar time.
Can you automate the CircleCI to GitHub Actions migration?
Yes. The conversion from .circleci/config.yml to a .github/workflows/ file is mechanical enough to automate per repository. Tidra reads each config, generates the equivalent workflow, removes the CircleCI directory, and opens a pull request, while you review the plan and diffs before anything merges.
Do CircleCI orbs work in GitHub Actions?
No. Orbs are specific to CircleCI. During migration each orb command is replaced by a Marketplace action or an inline run: step. For example, the node orb’s install-packages command becomes actions/setup-node followed by npm ci.
What happens to CircleCI environment variables when I migrate?
Environment variables stored in CircleCI project settings do not transfer automatically. You recreate them as GitHub Actions repository or organization secrets and reference them through env: in the workflow. List the required secrets in the migration pull request so the owning team can configure them before merging.
Do I need to run both CI systems during the migration? Many teams run CircleCI and GitHub Actions in parallel while repos are converted one at a time, which means paying for both systems until the migration finishes. Converting every repo in a single initiative shortens that overlap and reduces the time you carry two CI platforms at once.