Real-world CI/CD conversion showcase

Hand-curated examples covering common pipeline shapes: monorepos, multi-arch Docker builds, language-specific test runs, mobile builds, and tag releases. Each example shows the source YAML, the converter's output, and notes on what changed.

GitHub ActionsGitLab CI

React monorepo: GitHub Actions → GitLab CI

A typical React monorepo with lint + test + build, converted to GitLab CI with stages and parallel matrix.

GitHub Actions
name: ci
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npm run lint
  test:
    runs-on: ubuntu-latest
    needs: [lint]
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npm test -- --shard=${{ matrix.shard }}/4
GitLab CI
stages:
  - stage-0
  - stage-1
lint:
  stage: stage-0
  image: node:20
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths: [.npm/]
  script:
    - npm ci
    - npm run lint
test:
  stage: stage-1
  image: node:20
  needs: [lint]
  parallel:
    matrix:
      - shard: ["1", "2", "3", "4"]
  script:
    - npm ci
    - npm test -- --shard=$shard/4
  • Stages auto-derived from `needs:`.
  • Cache hoisted from a per-step actions/cache to a job-level cache: block.
  • matrix.shard rewritten to GitLab-compatible parallel:matrix entry.

GitHub ActionsCircleCI

Multi-arch Docker build: GitHub Actions → CircleCI

docker/build-push-action with QEMU and buildx becomes shell + setup_remote_docker on CircleCI.

GitHub Actions
name: build
on:
  push:
    branches: [main]
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-qemu-action@v3
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          push: true
          platforms: linux/amd64,linux/arm64
          tags: myorg/myapp:latest
CircleCI
version: 2.1
jobs:
  docker:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run: |
          docker run --privileged --rm tonistiigi/binfmt --install all
          docker buildx create --use --name builder
          echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
          docker buildx build --platform linux/amd64,linux/arm64 --tag myorg/myapp:latest --push .
workflows:
  main:
    jobs:
      - docker:
          filters: { branches: { only: [main] } }
  • docker/* actions inlined into a single bash block.
  • setup_remote_docker added with docker_layer_caching.
  • Branch filter moved from `on:` to workflow filters.

GitLab CIBitbucket Pipelines

Python FastAPI: GitLab CI → Bitbucket Pipelines

A typical FastAPI service pipeline with poetry caching, pytest, and Docker push.

GitLab CI
stages: [test, build]
variables:
  POETRY_VIRTUALENVS_IN_PROJECT: "true"
test:
  stage: test
  image: python:3.11
  cache:
    paths: [.venv/]
  script:
    - pip install poetry==1.8.0
    - poetry install
    - poetry run pytest
build:
  stage: build
  image: docker:24
  services: [docker:24-dind]
  needs: [test]
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Bitbucket Pipelines
image: python:3.11
definitions:
  caches:
    poetry: .venv

pipelines:
  default:
    - step:
        name: test
        caches: [poetry]
        script:
          - pip install poetry==1.8.0
          - poetry install
          - poetry run pytest
  branches:
    main:
      - step:
          name: test
          caches: [poetry]
          script:
            - pip install poetry==1.8.0
            - poetry install
            - poetry run pytest
      - step:
          name: build
          image: docker:24
          services: [docker]
          script:
            - docker build -t $REGISTRY_URL:$BITBUCKET_COMMIT .
            - docker push $REGISTRY_URL:$BITBUCKET_COMMIT
  • Custom poetry cache definition added at the top.
  • Stages collapsed into sequential steps within the branches.main pipeline.
  • GitLab Container Registry vars rewritten to Bitbucket equivalents — recreate the secret on import.

CircleCIGitHub Actions

Go service: CircleCI → GitHub Actions

A Go service with race-detector tests and golangci-lint on PRs.

CircleCI
version: 2.1
jobs:
  lint:
    docker: [{ image: golangci/golangci-lint:v1.59.1 }]
    steps:
      - checkout
      - run: golangci-lint run ./...
  test:
    docker: [{ image: cimg/go:1.22 }]
    steps:
      - checkout
      - restore_cache:
          keys: [go-mod-{{ checksum "go.sum" }}]
      - run: go mod download
      - save_cache:
          key: go-mod-{{ checksum "go.sum" }}
          paths: [/home/circleci/go/pkg/mod]
      - run: go test -race -coverprofile=coverage.out ./...
workflows:
  main:
    jobs: [lint, test]
GitHub Actions
name: ci
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    container: golangci/golangci-lint:v1.59.1
    steps:
      - uses: actions/checkout@v4
      - run: golangci-lint run ./...
  test:
    runs-on: ubuntu-latest
    container: cimg/go:1.22
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        with:
          key: go-mod-${{ hashFiles('go.sum') }}
          path: ~/go/pkg/mod
      - run: go mod download
      - run: go test -race -coverprofile=coverage.out ./...
  • restore_cache + save_cache merged into a single actions/cache@v4.
  • {{ checksum "..." }} → ${{ hashFiles('...') }}.
  • Workflow flattened — both jobs run in parallel with no `needs:` between them.

Bitbucket PipelinesGitHub Actions

Rust workspace: Bitbucket → GitHub Actions

A Rust workspace pipeline with cargo caching and cross-arch builds.

Bitbucket Pipelines
image: rust:1.78

definitions:
  caches:
    cargo: ~/.cargo
    target: target

pipelines:
  default:
    - step:
        name: test
        caches: [cargo, target]
        script:
          - cargo test --workspace --all-features
  branches:
    main:
      - step:
          name: build linux
          caches: [cargo, target]
          script:
            - cargo build --release
          artifacts: ['target/release/myapp']
GitHub Actions
name: ci
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    container: rust:1.78
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        with:
          path: |
            ~/.cargo
            target
          key: cargo-${{ hashFiles('**/Cargo.lock') }}
      - run: cargo test --workspace --all-features
  build_linux:
    runs-on: ubuntu-latest
    container: rust:1.78
    if: github.ref == 'refs/heads/main'
    needs: [test]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        with:
          path: |
            ~/.cargo
            target
          key: cargo-${{ hashFiles('**/Cargo.lock') }}
      - run: cargo build --release
      - uses: actions/upload-artifact@v4
        with:
          name: myapp
          path: target/release/myapp
  • Multiple Bitbucket caches merged into a single actions/cache step with a path list.
  • Branch-specific pipeline collapsed into an if: github.ref check on the build job.
  • Artifact converted from step-level to actions/upload-artifact.

GitLab CICircleCI

Release on tag: GitLab CI → CircleCI

Tag-triggered release pipeline with binary artifacts.

GitLab CI
release:
  image: node:20
  rules:
    - if: '$CI_COMMIT_TAG'
  script:
    - npm ci
    - npm run build:release
  artifacts:
    paths: [dist/]
CircleCI
version: 2.1
jobs:
  release:
    docker: [{ image: node:20 }]
    steps:
      - checkout
      - run: npm ci
      - run: npm run build:release
      - store_artifacts: { path: dist }
      - persist_to_workspace: { root: ., paths: [dist] }
workflows:
  release:
    jobs:
      - release:
          filters:
            branches: { ignore: /.*/ }
            tags: { only: /.*/ }
  • Tag rule translated to CircleCI workflow filters.
  • GitLab artifacts: paths becomes both store_artifacts (UI) and persist_to_workspace (downstream).

Bitbucket PipelinesGitLab CI

PHP Laravel: Bitbucket → GitLab CI

Laravel test pipeline with composer caching and a MySQL service.

Bitbucket Pipelines
image: php:8.3

definitions:
  services:
    mysql:
      image: mysql:8
      variables:
        MYSQL_ROOT_PASSWORD: secret
        MYSQL_DATABASE: testing

pipelines:
  default:
    - step:
        name: test
        services: [mysql]
        caches: [composer]
        script:
          - composer install
          - cp .env.testing .env
          - php artisan migrate
          - php artisan test
GitLab CI
stages:
  - stage-0
default-1:
  stage: stage-0
  image: php:8.3
  services:
    - name: mysql:8
      alias: mysql
  variables:
    MYSQL_ROOT_PASSWORD: secret
    MYSQL_DATABASE: testing
  cache:
    paths: [vendor/]
  script:
    - composer install
    - cp .env.testing .env
    - php artisan migrate
    - php artisan test
  • Bitbucket services definitions inlined into the GitLab job.
  • composer cache emitted with the conventional vendor/ path.

GitHub ActionsGitLab CI

Multi-environment deploy: GitHub Actions → GitLab CI

Deploy to staging on every main push, deploy to prod with manual approval.

GitHub Actions
name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy_staging:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy.sh staging
  deploy_prod:
    runs-on: ubuntu-latest
    environment: production
    needs: [deploy_staging]
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/deploy.sh production
GitLab CI
stages:
  - stage-0
  - stage-1
deploy_staging:
  stage: stage-0
  image: ubuntu:22.04
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  script:
    - ./scripts/deploy.sh staging
deploy_prod:
  stage: stage-1
  image: ubuntu:22.04
  needs: [deploy_staging]
  when: manual
  script:
    - ./scripts/deploy.sh production
  • GitHub `environment: production` → GitLab `when: manual` (approval semantics differ; configure protected environments in GitLab settings).

CircleCIBitbucket Pipelines

iOS / Fastlane: CircleCI → Bitbucket

macOS-executor iOS pipeline rewritten for Bitbucket macOS runners.

CircleCI
version: 2.1
jobs:
  test:
    macos: { xcode: 15.4.0 }
    steps:
      - checkout
      - run: bundle install
      - run: bundle exec fastlane test
workflows:
  main:
    jobs: [test]
Bitbucket Pipelines
image: atlassian/default-image:4

pipelines:
  default:
    - step:
        name: test
        runs-on:
          - self.hosted
          - macos
        script:
          - bundle install
          - bundle exec fastlane test
  • macOS jobs require self-hosted macOS runners on Bitbucket; review and configure runner labels.

GitHub ActionsCircleCI

Cypress e2e tests: GitHub Actions → CircleCI

Parallel Cypress e2e with split test files and recorded artifacts.

GitHub Actions
name: e2e
on: [push, pull_request]
jobs:
  cypress:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        containers: [1, 2, 3]
    steps:
      - uses: actions/checkout@v4
      - uses: cypress-io/github-action@v6
        with:
          start: npm start
          parallel: true
          record: true
          group: 'PR'
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
CircleCI
version: 2.1
jobs:
  cypress:
    parameters:
      container:
        type: string
    docker: [{ image: cypress/included:13.7.0 }]
    parallelism: 3
    steps:
      - checkout
      - run: npm ci
      - run: npx cypress run --record --parallel --group PR
        environment:
          CYPRESS_RECORD_KEY: $CYPRESS_RECORD_KEY
workflows:
  main:
    jobs:
      - cypress:
          matrix:
            parameters:
              container: ["1", "2", "3"]
  • cypress-io/github-action wrapper translated to direct cypress-cli invocation.
  • Container splitting via CircleCI parallelism: 3.

Related Tools