How to Migrate From CircleCI to GitHub Actions
The syntax translates in an afternoon. What takes the time is orbs with no equivalent, workspace semantics that differ, and the contexts and required checks nobody remembers until merges start blocking.
Most CircleCI to GitHub Actions migrations stall for the same reason: teams translate config.yml into workflow.yml, find it mostly works, cut over, and then spend two weeks on the parts that did not translate. The translation is the easy half.
This guide maps the concepts directly, flags the three that have no clean equivalent, and gives you a cutover sequence that does not put merges at risk. Read the mapping table first; it tells you how big your migration actually is.
Concept mapping
| CircleCI | GitHub Actions | Notes |
|---|---|---|
jobs: | jobs: | Direct equivalent |
workflows: | on: plus needs: | Ordering is expressed per job, not centrally |
executor / docker: | runs-on: plus container: | Direct equivalent |
orbs | uses: (actions) | Not one-to-one; some orbs have no action equivalent |
workspace (persist_to_workspace) | actions/upload-artifact + download-artifact | Different semantics, see below |
save_cache / restore_cache | actions/cache | Key syntax and fallback behaviour differ |
contexts | Environments plus secrets | Approval gates move to environments |
parameters | inputs (workflow_call / dispatch) | Typed inputs, stricter than CircleCI |
matrix | strategy.matrix | Direct equivalent |
requires: | needs: | Direct equivalent |
when / unless | if: | Expression syntax is entirely different |
The three that do not translate cleanly
Everything above the line is mechanical. These three are where migrations actually spend their time, so scope them before committing to a date.
- Orbs. An orb is a packaged set of jobs, commands, and executors. Some map to a marketplace action, many map to several steps you write yourself, and a few (particularly vendor deployment orbs) have no equivalent and become shell scripts. Inventory your orbs first; that list is your real migration estimate.
- Workspaces. CircleCI workspaces persist a directory between jobs in a workflow and are cheap and implicit. GitHub artifacts are uploaded and downloaded explicitly, are slower for large trees, and are billed for storage. A pipeline that leans heavily on workspaces will feel slower after a naive translation.
- Contexts. CircleCI contexts carry secrets plus optional approval. In Actions that splits: secrets go to repository or environment secrets, and approval becomes an environment protection rule. The split is fine once done, but it is a permissions conversation with whoever owns production access, not a YAML edit.
A worked translation
# CircleCI
version: 2.1
jobs:
test:
docker:
- image: cimg/node:22.0
steps:
- checkout
- restore_cache:
keys: [v1-deps-{{ checksum "package-lock.json" }}]
- run: npm ci
- save_cache:
key: v1-deps-{{ checksum "package-lock.json" }}
paths: [~/.npm]
- run: npm test
# GitHub Actions
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm # replaces both cache steps
- run: npm ci
- run: npm testCut over without risking merges
- Inventory orbs and contexts. This is the estimate; everything else is mechanical.
- Translate one non-blocking job first, and run it in parallel with CircleCI on the same commits.
- Compare results for at least a week, on failures as well as successes. A migration that changes which tests fail is a regression you have not found yet.
- Recreate contexts as environments with the same approvers before migrating anything that deploys.
- Add the new checks to branch protection as non-required first, so they report without blocking.
- Flip required status checks only once the new workflow has been green for a full sprint, then remove the CircleCI ones in the same change so merges are never gated on both.
- Keep CircleCI configured but disabled for a further sprint. Rolling back is then a toggle rather than a re-migration.
What changes about cost
CircleCI bills credits against machine classes; GitHub bills per minute per runner size. The comparison is not like for like, and the surprise usually runs one way.
- GitHub-hosted Linux 2-core lists at $0.006/min, and private repositories get 2 vCPU with 8 GB.
- Standard runners are free and unlimited on public repositories, at a larger 4 vCPU and 16 GB.
- GitHub rounds every job up to the nearest minute, so a wide matrix of short jobs costs more than the raw time suggests.
- Actions cache is capped at 10 GB per repository. A CircleCI pipeline with a large cache can hit that ceiling and silently stop persisting.
Frequently asked questions
How long does a CircleCI to GitHub Actions migration take?
Is there an automatic CircleCI to GitHub Actions converter?
What is the GitHub Actions equivalent of a CircleCI orb?
uses:, or a composite action you write yourself. The mapping is not one-to-one: an orb bundles jobs, commands, and executors together, whereas an action is a single step, so one orb frequently becomes several steps.What replaces CircleCI workspaces?
actions/upload-artifact and actions/download-artifact. The semantics differ: artifacts are explicit, slower for large directories, and billed for storage, whereas workspaces were implicit and cheap. Pipelines that pass large trees between jobs feel slower after a direct translation.