---
# === IDENTITY ===
id: software/migrations/circleci-to-github-actions/2026
canonical_question: "How do I migrate from CircleCI to GitHub Actions?"
aliases:
  - "CircleCI to GitHub Actions migration"
  - "convert CircleCI config to GitHub Actions"
  - "replace CircleCI with GitHub Actions"
  - "CircleCI config.yml to GitHub workflow"
  - "CI/CD migration CircleCI to GitHub"
  - "circleci orbs to github actions"
  - "github actions importer circleci"
  - "migrate CI pipeline from CircleCI"
entity_type: software_reference
domain: software > migrations > circleci_to_github_actions
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-05-29
confidence: 0.92
freshness: evolving
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "GitHub Actions Importer GA (2024-04); hosted-runner pricing cut up to 39% + $0.002/min platform charge (Jan 2026); self-hosted platform charge shelved after backlash (Jan 2026)"
  next_review: 2026-11-25
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Do not use CircleCI's pre-built cimg/* Docker images in GitHub Actions — they set USER to 'circleci' which causes permission errors on GitHub-hosted runners"
  - "GitHub Actions artifact retention defaults to 90 days (configurable to 1-400 days) vs CircleCI's 30-day default — adjust retention policies to avoid storage cost surprises"
  - "GitHub-hosted standard runners (ubuntu-latest) provide only 2 vCPU / 7 GB RAM — do not assume CircleCI resource_class: xlarge (8 CPU / 16 GB) performance without explicitly requesting larger runners"
  - "GitHub Actions Importer cannot automatically migrate CircleCI contexts, project-level environment variables, or custom orbs — these require manual conversion"
  - "Each GitHub Actions workflow file has a 6-hour job timeout and 35-day workflow run limit — CircleCI allows configurable no_output_timeout per step"
  - "GitHub Actions free tier for private repos is 2,000 min/mo (vs CircleCI's 6,000 min/mo Linux) — calculate cost impact before migrating high-volume pipelines"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Migrating from Jenkins (not CircleCI) to GitHub Actions"
    use_instead: "software/migrations/jenkins-to-github-actions/2026"
  - condition: "Authoring a GitHub Actions workflow from scratch for a Node.js project (no CircleCI source)"
    use_instead: "software/devops/github-actions-nodejs/2026"
  - condition: "Building a CI pipeline on GitLab CI rather than migrating away from it"
    use_instead: "software/devops/gitlab-ci-pipeline/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: orb_count
    question: "How many CircleCI orbs does the project use?"
    type: choice
    options: ["0-3 (simple)", "4-8 (moderate)", "8+ or custom orbs (complex)"]
  - key: runner_needs
    question: "What runner types does the project require?"
    type: choice
    options: ["Linux only", "Linux + macOS", "Linux + Windows", "All platforms + arm64/GPU"]
  - key: migration_tool
    question: "Would you like to use the automated GitHub Actions Importer or migrate manually?"
    type: choice
    options: ["Automated (gh actions-importer)", "Manual conversion", "Not sure"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/migrations/circleci-to-github-actions/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-29)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/migrations/jenkins-to-github-actions/2026"
      label: "Jenkins to GitHub Actions Migration"
    - id: "software/migrations/docker-compose-to-kubernetes/2026"
      label: "Docker Compose to Kubernetes"
  alternative_to:
    - id: "software/devops/github-actions-nodejs/2026"
      label: "GitHub Actions CI for Node.js"
    - id: "software/devops/gitlab-ci-pipeline/2026"
      label: "GitLab CI Pipeline Setup"
  often_confused_with:
    - id: "software/devops/github-actions-docker/2026"
      label: "GitHub Actions Docker build/push (workflow authoring, not migration)"

# === SOURCES ===
sources:
  - id: src1
    title: "Migrating from CircleCI to GitHub Actions"
    author: GitHub
    url: https://docs.github.com/en/actions/migrating-to-github-actions/manually-migrating-to-github-actions/migrating-from-circleci-to-github-actions
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src2
    title: "GitHub Actions Documentation"
    author: GitHub
    url: https://docs.github.com/en/actions
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src3
    title: "CircleCI Configuration Reference"
    author: CircleCI
    url: https://circleci.com/docs/configuration-reference/
    type: official_docs
    published: 2025-07-01
    reliability: authoritative
  - id: src4
    title: "CircleCI vs GitHub Actions: Feature Comparison"
    author: CircleCI Blog
    url: https://circleci.com/blog/github-actions-vs-circleci/
    type: technical_blog
    published: 2025-06-15
    reliability: high
  - id: src5
    title: "GitHub Actions Marketplace"
    author: GitHub
    url: https://github.com/marketplace?type=actions
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src6
    title: "GitHub Actions: Self-hosted Runners"
    author: GitHub
    url: https://docs.github.com/en/actions/hosting-your-own-runners
    type: official_docs
    published: 2025-08-01
    reliability: authoritative
  - id: src7
    title: "Migrating from CircleCI with GitHub Actions Importer"
    author: GitHub
    url: https://docs.github.com/actions/migrating-to-github-actions/automated-migrations/migrating-from-circleci-with-github-actions-importer
    type: official_docs
    published: 2024-04-01
    reliability: authoritative
  - id: src8
    title: "2026 Pricing Changes for GitHub Actions"
    author: GitHub
    url: https://resources.github.com/actions/2026-pricing-changes-for-github-actions/
    type: official_docs
    published: 2025-12-01
    reliability: authoritative
---

# How to Migrate from CircleCI to GitHub Actions

## How do I migrate from CircleCI to GitHub Actions?

## TL;DR

- **Bottom line**: CircleCI and GitHub Actions share similar YAML-based config. Map CircleCI `jobs` to GitHub Actions `jobs`, `orbs` to marketplace actions, `executors` to `runs-on` / `container:`, and `workflows` to GitHub workflow triggers + `needs:` dependencies.
- **Key tool/command**: Use `gh actions-importer migrate circle-ci` for automated conversion, or manually move `.circleci/config.yml` logic into `.github/workflows/ci.yml` -- the YAML structures are 80% similar. [src7]
- **Watch out for**: CircleCI orbs bundle complex functionality (commands + executors + jobs). Each orb must be individually replaced with GitHub Actions marketplace equivalents -- there is no 1:1 mapping. [src1]
- **Works with**: Any GitHub repository; GitHub-hosted runners (Linux, macOS, Windows, arm64) or self-hosted runners. GitHub Actions Importer supports CircleCI config versions 2.0 and 2.1. [src2, src7]

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Do not use CircleCI's pre-built `cimg/*` Docker images in GitHub Actions -- they set `USER` to `circleci` which causes permission errors on GitHub-hosted runners. Use official images or actions instead. [src1]
- GitHub-hosted standard runners (`ubuntu-latest`) provide only 2 vCPU / 7 GB RAM -- do not assume CircleCI `resource_class: xlarge` (8 CPU / 16 GB) performance without explicitly requesting larger runners or self-hosted. [src4, src6]
- GitHub Actions Importer cannot automatically migrate CircleCI contexts, project-level environment variables, or custom orbs -- these require manual conversion. [src7]
- Each GitHub Actions workflow file has a 6-hour job timeout (configurable) and 35-day workflow run limit. CircleCI allows configurable `no_output_timeout` per step. [src2]
- GitHub Actions free tier for private repos is 2,000 min/mo vs CircleCI's 6,000 min/mo (Linux). As of Jan 2026, GitHub reduced hosted-runner prices by up to 39% but introduced a $0.002/min platform charge for self-hosted runners in private repos (postponed for re-evaluation). [src8]
- Artifact size limits differ: GitHub Actions allows up to 10 GB per artifact (500 MB free tier); CircleCI workspace limits vary by plan. Always verify artifact sizes before migration. [src2]

## Quick Reference

| CircleCI Concept | GitHub Actions Equivalent | Notes |
|---|---|---|
| `.circleci/config.yml` | `.github/workflows/*.yml` | Both YAML; GHA uses one file per workflow [src1] |
| `jobs:` | `jobs:` | Nearly identical structure [src1] |
| `steps: - run:` | `steps: - run:` | Same keyword [src1] |
| `executors:` | `runs-on:` or `container:` | VM or Docker-based [src1] |
| `orbs:` | Marketplace actions (`uses:`) | e.g., `circleci/node` to `actions/setup-node` [src1, src5] |
| `workflows:` | `on:` triggers + `jobs:` with `needs:` | Trigger-based instead of named workflows [src1] |
| `when:` / `unless:` | `if:` expressions | Conditional execution [src1] |
| `persist_to_workspace` | `actions/upload-artifact@v4` | Artifact passing between jobs [src1] |
| `attach_workspace` | `actions/download-artifact@v4` | Restore artifacts in downstream jobs [src1] |
| `save_cache` / `restore_cache` | `actions/cache@v4` | Single action handles both save and restore [src1] |
| `store_test_results` | `dorny/test-reporter@v1` or similar | No built-in test insights dashboard [src5] |
| `store_artifacts` | `actions/upload-artifact@v4` | Test reports, binaries [src2] |
| `parameters:` | `workflow_dispatch: inputs:` | Manual trigger params [src2] |
| `contexts` | `environments` + secrets | Environment-scoped secrets with approval gates [src2] |
| `resource_class:` | `runs-on:` labels | `xlarge` to larger runner label; pricing differs [src1, src6] |
| Pipeline variables | `github` context | `<< pipeline.git.branch >>` to `${{ github.ref_name }}` [src1] |
| `matrix:` (via orbs) | `strategy: matrix:` | Built-in matrix support in GHA [src2] |
| Service containers | `services:` key in job | CircleCI uses additional docker images; GHA uses explicit `services:` [src1] |

## Decision Tree

```
START
|-- Is code hosted on GitHub?
|   |-- YES -> GitHub Actions is the natural fit [src2]
|   +-- NO -> Consider staying on CircleCI or migrating to GitHub first
|-- Want automated migration?
|   |-- YES -> Install gh actions-importer, run audit + migrate [src7]
|   +-- NO -> Manual YAML conversion (more control)
|-- How many orbs are used?
|   |-- 0-3 orbs -> Simple migration (1-2 days) [src1]
|   |-- 4-8 orbs -> Moderate (find marketplace replacements) [src5]
|   +-- 8+ or custom orbs -> Complex (build composite actions) [src1]
|-- Do you use CircleCI contexts?
|   |-- YES -> Map to GitHub environments + secrets [src2]
|   +-- NO -> Use repository-level secrets
|-- Need macOS, Windows, or arm64 runners?
|   |-- YES -> Available on both platforms (pricing restructured Jan 2026) [src8]
|   +-- NO -> Standard Linux runners (cheaper on GitHub for public repos)
|-- Need CircleCI test splitting?
|   |-- YES -> Use matrix strategy + third-party test-splitting action [src4]
|   +-- NO -> Standard job parallelism via needs: + matrix
+-- DEFAULT -> Start with simplest workflow, migrate incrementally
```

## Decision Logic

Structured if/then rules an agent can apply once it knows the project's CI shape. Each rule maps a condition to a concrete recommendation.

### If the repo is already on GitHub and the CircleCI config uses 0-3 standard orbs
--> Run `gh actions-importer dry-run circle-ci` then manually finish the workflow; expect a 1-2 day migration with >80% auto-conversion. [src1, src7]

### If the config relies on custom orbs, CircleCI contexts, or project-level env vars
--> Do not trust the importer for these — migrate contexts to GitHub `environments` + secrets and rebuild custom orbs as composite actions by hand; the importer flags them as unsupported. [src7]

### If a job declares `resource_class: large`/`xlarge` (8+ CPU)
--> Do not map to `ubuntu-latest` (2 vCPU / 7 GB); request a GitHub larger runner label or self-hosted runner, and model cost using the Jan 2026 per-minute rates (hosted reduced up to 39%, $0.002/min platform charge). [src4, src6, src8]

### If the pipeline uses `cimg/*` Docker images as the execution environment
--> Replace them with official base images plus `actions/setup-*` actions — `cimg/*` images set `USER=circleci` and cause permission errors on GitHub-hosted runners. [src1]

### If the project depends on CircleCI automatic test splitting across parallel containers
--> Plan extra effort: GitHub Actions has no built-in equivalent — use `strategy: matrix` plus a third-party splitting action (e.g. `chaosaffe/split-tests`) keyed on timing data. [src4]

### If the repo is public and CI cost is the main driver
--> Migrate: GitHub-hosted standard runners are free on public repos (vs CircleCI's metered credits), so the move is almost always net-positive. [src2, src8]

### If the team relies daily on CircleCI SSH debugging or the built-in test-insights dashboard
--> Weigh staying on CircleCI; GitHub Actions only approximates these via `mxschmitt/action-tmate` and `dorny/test-reporter`, neither of which is a 1:1 replacement. [src4]

## Step-by-Step Guide

### 1. Audit existing CircleCI configuration

Assess migration complexity before writing any code. [src7]

```bash
# Install GitHub Actions Importer
gh extension install github/gh-actions-importer
gh actions-importer update

# Configure credentials (CircleCI token + GitHub token)
gh actions-importer configure

# Audit all CircleCI projects in your org
gh actions-importer audit circle-ci --output-dir tmp/audit

# Dry-run a single project (preview without creating PR)
gh actions-importer dry-run circle-ci \
  --output-dir tmp/dry-run \
  --circle-ci-project my-project
```

**Verify**: Check `tmp/audit/` for conversion rate report -- successful, partially successful, unsupported, and failed conversions.

### 2. Map CircleCI config to GitHub Actions YAML

Side-by-side conversion of the most common patterns. [src1]

```yaml
# BEFORE: .circleci/config.yml
version: 2.1
orbs:
  node: circleci/node@5
executors:
  default:
    docker:
      - image: cimg/node:20.0
jobs:
  test:
    executor: default
    steps:
      - checkout
      - node/install-packages
      - run:
          name: Run tests
          command: npm test
      - store_test_results:
          path: test-results
  build:
    executor: default
    steps:
      - checkout
      - node/install-packages
      - run: npm run build
      - persist_to_workspace:
          root: .
          paths: [dist]
  deploy:
    executor: default
    steps:
      - attach_workspace:
          at: .
      - run: ./deploy.sh
workflows:
  main:
    jobs:
      - test
      - build:
          requires: [test]
      - deploy:
          requires: [build]
          filters:
            branches:
              only: main
```

```yaml
# AFTER: .github/workflows/ci.yml
name: CI/CD Pipeline
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    container: node:20
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm test
      - uses: dorny/test-reporter@v1
        if: always()
        with: { name: Tests, path: 'test-results/*.xml', reporter: jest-junit }

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci && npm run build
      - uses: actions/upload-artifact@v4
        with: { name: dist, path: dist/ }

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/download-artifact@v4
        with: { name: dist }
      - run: ./deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
```

### 3. Replace CircleCI orbs with GitHub Actions

Map each orb to its marketplace equivalent. [src1, src5]

```yaml
# CircleCI orb -> GitHub Action mapping:

# circleci/node@5 -> actions/setup-node@v4
- uses: actions/setup-node@v4
  with: { node-version: '20', cache: 'npm' }

# circleci/docker@2 -> docker/build-push-action@v6
- uses: docker/build-push-action@v6
  with: { push: true, tags: 'myapp:latest' }

# circleci/aws-cli@4 -> aws-actions/configure-aws-credentials@v4
- uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: us-east-1

# circleci/slack@4 -> slackapi/slack-github-action@v2
- uses: slackapi/slack-github-action@v2
  with: { channel-id: '#deploys', slack-message: 'Deployed!' }

# circleci/python@2 -> actions/setup-python@v5
- uses: actions/setup-python@v5
  with: { python-version: '3.12', cache: 'pip' }

# circleci/terraform@3 -> hashicorp/setup-terraform@v3
- uses: hashicorp/setup-terraform@v3
  with: { terraform_version: '1.7' }
```

### 4. Migrate caching

CircleCI uses separate save/restore; GitHub Actions combines them. [src1]

```yaml
# CircleCI:
- save_cache:
    key: node-deps-{{ checksum "package-lock.json" }}
    paths: [node_modules]
- restore_cache:
    keys:
      - node-deps-{{ checksum "package-lock.json" }}
      - node-deps-

# GitHub Actions -- single action handles both:
- uses: actions/cache@v4
  with:
    path: node_modules
    key: node-deps-${{ hashFiles('package-lock.json') }}
    restore-keys: node-deps-
```

### 5. Migrate contexts to GitHub environments

Map CircleCI contexts to GitHub environments with optional approval gates. [src2]

```bash
# CircleCI contexts -> GitHub environments with secrets
# Create environment in GitHub repo Settings > Environments

# Set environment-specific secrets:
gh secret set DEPLOY_TOKEN --env production --body "prod-token"
gh secret set DEPLOY_TOKEN --env staging --body "staging-token"
```

```yaml
# Use in workflow with optional approval gate:
deploy:
  runs-on: ubuntu-latest
  environment: production  # Uses production secrets + approval rules
  steps:
    - run: ./deploy.sh
      env:
        TOKEN: ${{ secrets.DEPLOY_TOKEN }}
```

### 6. Migrate service containers (databases, Redis, etc.)

CircleCI uses additional Docker images alongside the primary; GitHub Actions uses the `services:` key. [src1]

```yaml
# CircleCI:
jobs:
  test:
    docker:
      - image: cimg/node:20.0    # primary
      - image: cimg/postgres:15  # service
    steps:
      - checkout
      - run: npm test

# GitHub Actions:
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: test
        ports: ['5432:5432']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci && npm test
        env:
          DATABASE_URL: postgres://postgres:test@localhost:5432/postgres
```

## Anti-Patterns

### Wrong: Duplicating orb logic inline

```yaml
# BAD -- reimplementing what a marketplace action already does
steps:
  - run: |
      curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
      sudo apt-get install -y nodejs
      node --version
```

### Correct: Use marketplace actions

```yaml
# GOOD -- uses official action, maintained, cached [src5]
steps:
  - uses: actions/setup-node@v4
    with: { node-version: '20' }
```

### Wrong: Not using caching

```yaml
# BAD -- installs dependencies from scratch every time (slow)
steps:
  - run: npm ci  # 2-3 minutes every run
```

### Correct: Cache dependencies

```yaml
# GOOD -- cache restores in seconds [src1]
steps:
  - uses: actions/setup-node@v4
    with: { node-version: '20', cache: 'npm' }  # Built-in caching
  - run: npm ci
```

### Wrong: Using CircleCI pre-built images in GitHub Actions

```yaml
# BAD -- cimg/* images set USER=circleci, causing permission errors [src1]
jobs:
  test:
    runs-on: ubuntu-latest
    container: cimg/node:20.0
    steps:
      - uses: actions/checkout@v4  # Permission denied errors
```

### Correct: Use official images or setup actions

```yaml
# GOOD -- use official Docker images or setup actions
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci && npm test
```

### Wrong: Hardcoding secrets in workflow files

```yaml
# BAD -- secrets visible in logs and repo history
steps:
  - run: curl -H "Authorization: Bearer ghp_abc123" https://api.example.com
```

### Correct: Use GitHub secrets and environments

```yaml
# GOOD -- secrets are masked in logs, scoped to environments [src2]
steps:
  - run: curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" https://api.example.com
```

## Common Pitfalls

- **Orbs are not 1:1 with actions**: A single CircleCI orb may bundle multiple commands, executors, and jobs. Each piece may need a separate GitHub Action or composite action. [src1]
- **Workspace vs. artifacts**: CircleCI's `persist_to_workspace` passes files within a single workflow run. GitHub Actions uses `upload/download-artifact`, which has different size limits (500 MB free, 10 GB max per artifact) and 90-day default retention. [src1, src2]
- **Cache key syntax differs**: CircleCI uses `{{ checksum "file" }}`; GitHub Actions uses `${{ hashFiles('file') }}`. Forgetting to convert this syntax silently breaks cache restoration. [src1]
- **CircleCI pipelines vs. GitHub workflows**: CircleCI can have multiple named workflows in one config.yml triggered by different conditions. In GitHub Actions, each `.yml` file IS a workflow with its own triggers. [src1]
- **Resource classes**: CircleCI's `resource_class: xlarge` (8 CPU, 16 GB) maps to GitHub's `ubuntu-latest` which is only 2 CPU / 7 GB. For larger runners, use GitHub's larger runner labels (4-core, 8-core, etc.) or self-hosted runners. Prices reduced up to 39% as of Jan 2026. [src4, src6, src8]
- **Docker layer caching**: CircleCI has built-in DLC. GitHub Actions needs explicit `docker/build-push-action` with `cache-from`/`cache-to` using GitHub Actions cache or registry backend. [src4]
- **Test splitting**: CircleCI has automatic test splitting across parallel containers based on timing data. GitHub Actions requires manual splitting with `matrix` strategy or third-party actions like `chaosaffe/split-tests`. [src4]
- **SSH debugging**: CircleCI's `circleci ssh` has no native GitHub Actions equivalent. Use `mxschmitt/action-tmate` for interactive debugging sessions. [src4]

## Diagnostic Commands

```bash
# Analyze CircleCI config complexity
grep -c "jobs\|orbs\|executors\|workflows" .circleci/config.yml

# List all orbs used
grep -oP "circleci/[a-z-]+@\d+" .circleci/config.yml

# Audit migration feasibility with GitHub Actions Importer
gh actions-importer audit circle-ci --output-dir tmp/audit

# Preview converted workflow without creating PR
gh actions-importer dry-run circle-ci \
  --output-dir tmp/dry-run \
  --circle-ci-project my-project

# Validate GitHub Actions workflow syntax
actionlint .github/workflows/ci.yml

# Check workflow runs after migration
gh run list --limit 10
gh run view <run-id> --log

# Compare CI run times before/after migration
gh run list --workflow=ci.yml --json databaseId,conclusion,createdAt,updatedAt \
  --jq '.[] | "\(.createdAt) -> \(.updatedAt) [\(.conclusion)]"'
```

## Version History & Compatibility

| Feature | CircleCI | GitHub Actions |
|---|---|---|
| Config format | YAML (config.yml v2.0/2.1) | YAML (workflow files) |
| Extensibility | Orbs (registry.circleci.com) | Marketplace actions (github.com/marketplace) |
| Runners | Cloud or self-hosted | GitHub-hosted or self-hosted |
| Free tier (Linux) | 6,000 min/mo | 2,000 min/mo (private) / unlimited (public) |
| Docker layer caching | Built-in | External action required |
| Test insights | Built-in dashboard | Third-party actions (dorny/test-reporter) |
| Reusable config | Orbs + commands | Reusable workflows + composite actions |
| Matrix builds | Via orbs | Built-in `strategy: matrix:` |
| Manual approvals | N/A (use contexts) | Environment protection rules |
| Pricing (Jan 2026) | Per-credit model | Per-minute; hosted runners reduced up to 39% [src8] |

## When to Use / When Not to Use

| Migrate When | Don't Migrate When | Use Instead |
|---|---|---|
| Code is hosted on GitHub | Heavily invested in custom CircleCI orbs | Stay on CircleCI; refactor orbs incrementally |
| Want unified GitHub workflow (code + CI in one UI) | Need CircleCI's built-in test insights dashboard | CircleCI + GitHub integration |
| Simplifying DevOps toolchain | Complex resource class requirements (8+ CPU) | CircleCI or self-hosted runners |
| Public repos (unlimited free CI) | Team relies on CircleCI SSH debugging daily | Stay on CircleCI |
| Need GitHub environment protection rules | CI budget is tight (CircleCI 3x free minutes) | CircleCI free tier |
| GitHub Actions Importer can convert >80% of config | Migration would take >2 weeks of engineering time | Gradual migration or stay |

## Important Caveats

- CircleCI has better built-in test splitting across parallel containers with automatic timing-based distribution; GitHub Actions requires manual test splitting with third-party actions. [src4]
- CircleCI's SSH debugging (`circleci ssh`) has no native GitHub Actions equivalent -- use `mxschmitt/action-tmate` for similar functionality, but it requires a public SSH key. [src4]
- GitHub Actions free tier (2,000 min/mo for private repos) may be less than CircleCI's free tier (6,000 min/mo). As of Jan 2026, GitHub reduced hosted-runner prices by up to 39% but introduced a $0.002/min platform charge (self-hosted charge postponed for re-evaluation). [src8]
- CircleCI's `dynamic config` feature (generated config.yml) requires GitHub Actions `workflow_call` + matrix strategies or `dorny/paths-filter` to replicate. [src1]
- GitHub Actions Importer audit reports may overstate conversion success -- always manually review partially-successful conversions for correctness. [src7]
- As of 2025, GitHub Actions now defaults to more secure pull_request_target behavior, anchoring execution to trusted default-branch workflow definitions. Review security settings after migration. [src2]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Jenkins to GitHub Actions Migration](/software/migrations/jenkins-to-github-actions/2026)
- [Docker Compose to Kubernetes](/software/migrations/docker-compose-to-kubernetes/2026)
- [GitHub Actions CI for Node.js](/software/devops/github-actions-nodejs/2026)
- [GitLab CI Pipeline Setup](/software/devops/gitlab-ci-pipeline/2026)
- [GitHub Actions Docker build/push](/software/devops/github-actions-docker/2026)
