atlassian-cli is an independent, community open-source project, not affiliated with, endorsed by, or sponsored by Atlassian, and is not Atlassian's official CLI (acli). Product names are used only to identify compatibility.

The Mental Model

To convert Bitbucket Pipelines to GitHub Actions, you move one file (bitbucket-pipelines.yml at the repo root) into one or more workflow files under .github/workflows/, then translate four things: the pipelines: sections become on: triggers, each step becomes a job, each script: list becomes run: commands, and the step image: becomes a container: or a runner plus a setup action. That is the whole job. Everything else, caches, services, secrets, and deployments, is a variation on those four moves.

The two systems share a philosophy (declarative YAML, containerized steps, secrets injected as environment variables) but differ in structure. Bitbucket keeps all CI in a single file with one implicit build; GitHub Actions expects many small workflow files, each scoped to a set of events. That difference is the source of most confusion, and also the reason a migration is a good moment to split a monolithic pipeline into focused workflows.

This guide translates each construct with side-by-side YAML, then shows how to use the atlassian-cli command-line tool to inventory pipelines across dozens of repositories so you know the full migration surface before touching a single workflow file.

How this differs from Atlassian's own CLI: atlassian-cli is an independent, community open-source project. It is not Atlassian's official CLI (acli) and is not affiliated with Atlassian. Use the official acli for first-party vendor support; use atlassian-cli if you want a single free Rust binary spanning Jira, Confluence, Bitbucket, and JSM, which is handy here for reading pipeline configs straight from the Bitbucket API without cloning every repo.

Concept Mapping Table

Keep this table open in a second tab while you translate. Each row is a construct you will hit in a real bitbucket-pipelines.yml and its closest GitHub Actions equivalent.

Bitbucket Pipelines GitHub Actions Notes
bitbucket-pipelines.yml .github/workflows/*.yml One file becomes one or many workflow files.
pipelines: default on: [push] Runs for every branch without a more specific match.
pipelines: branches: main on: push: branches: [main] Branch-scoped triggers.
pipelines: pull-requests on: pull_request PR-triggered checks.
pipelines: tags on: push: tags Release tag builds.
pipelines: custom on: workflow_dispatch Manually triggered pipelines.
step job (or a step in a job) Each Bitbucket step is its own fresh environment.
image: container: or runs-on + setup Pin the image or use a setup action on ubuntu-latest.
script: run: Shell commands, one per line.
caches: actions/cache Or the cache option built into setup actions.
artifacts: upload-artifact / download-artifact Explicit upload and download between jobs.
definitions: services: services: Job-level sidecar containers.
parallel: separate jobs / matrix Jobs run in parallel unless linked with needs:.
deployment: production environment: production Environments carry approvals and scoped secrets.
repository / deployment variables secrets / vars / environment secrets Re-enter secret values; they do not export.
clone: depth actions/checkout fetch-depth Checkout is implicit in Bitbucket, explicit in Actions.
max-time timeout-minutes Per-step vs per-job timeout.

Triggers: pipelines to on:

In Bitbucket, every trigger lives under one pipelines: key and the build is implied. In GitHub Actions, each workflow file declares its own on: events and you name every job. Here is a minimal default build on both sides.

# bitbucket-pipelines.yml
image: node:20
pipelines:
  default:
    - step:
        name: Build and test
        caches:
          - node
        script:
          - npm ci
          - npm test
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    container: node:20
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

Two differences matter. First, GitHub Actions does not clone your code for you: the actions/checkout step is mandatory, whereas Bitbucket clones implicitly. Second, the caches: node shorthand has no direct GitHub equivalent, so you either add cache: npm to a setup-node step or wire up actions/cache by hand (covered below).

Branch-specific and pull-request pipelines split cleanly into scoped triggers:

# bitbucket-pipelines.yml
pipelines:
  pull-requests:
    '**':
      - step:
          script:
            - npm run lint
  branches:
    main:
      - step:
          name: Deploy
          deployment: production
          script:
            - ./deploy.sh
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - run: ./deploy.sh

Notice the lint check moved to a separate workflow triggered on pull_request. Splitting concerns like this is idiomatic in Actions and keeps each workflow's logs readable.

Steps, Scripts, and Images

A Bitbucket step is a fresh container with its own image. A GitHub Actions job is the closest match: it also runs in isolation and can pin a container. Steps inside a Bitbucket step map to run: lines, while separate Bitbucket steps map to separate jobs linked with needs: when order matters.

Two Bitbucket steps where the second depends on the first:

# bitbucket-pipelines.yml
pipelines:
  default:
    - step:
        name: Build
        script:
          - npm ci
          - npm run build
        artifacts:
          - dist/**
    - step:
        name: Publish
        script:
          - ./publish.sh dist/
# .github/workflows/release.yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
  publish:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - run: ./publish.sh dist/

This is the single most common gotcha in the migration: Bitbucket passes artifacts between sequential steps automatically, but GitHub Actions jobs share nothing by default. You must upload-artifact in the producing job and download-artifact in the consuming job, and connect them with needs:. If you skip the upload, the second job starts with an empty workspace and fails in a confusing way.

Caches, Artifacts, and Services

Bitbucket ships named caches (node, pip, maven, docker) as a convenience. In Actions you spell out the cache key and paths, or lean on the cache support built into language setup actions:

# GitHub Actions: explicit cache
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('package-lock.json') }}

# or, simpler, let setup-node manage it
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

Service containers (databases, queues, and other backing services your tests need) translate almost one to one. A Bitbucket service definition becomes a services: block on the job, with variables moved to env: and a ports: mapping added so the job can reach it.

# bitbucket-pipelines.yml
pipelines:
  default:
    - step:
        script:
          - npm test
        services:
          - postgres
definitions:
  services:
    postgres:
      image: postgres:16
      variables:
        POSTGRES_DB: test
        POSTGRES_PASSWORD: secret
# .github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: test
          POSTGRES_PASSWORD: secret
        ports:
          - 5432:5432
    steps:
      - uses: actions/checkout@v4
      - run: npm test

Secrets do not export from one platform to the other for security reasons. Bitbucket repository and deployment variables have to be re-entered as GitHub repository secrets, organization secrets, or environment secrets. Plan for that step: it is manual, and it is the most common cause of a "green in Bitbucket, red in Actions" first run.

Parallel Steps and Custom Pipelines

Bitbucket runs steps sequentially unless you wrap them in a parallel: block. GitHub Actions is the opposite: jobs run in parallel by default, and you introduce ordering only with needs:. So a parallel block becomes two independent jobs with no needs: between them.

# bitbucket-pipelines.yml
pipelines:
  default:
    - parallel:
        - step:
            name: Lint
            script: [npm run lint]
        - step:
            name: Unit tests
            script: [npm test]
# .github/workflows/ci.yml
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run lint
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

Custom pipelines, the ones you trigger by hand rather than on push, map to on: workflow_dispatch. In Bitbucket you would launch a named custom pipeline with the CLI like this:

# Trigger a named custom pipeline in Bitbucket
atlassian-cli bitbucket --workspace myteam pipeline trigger api-service \
  --ref-name main --custom-pipeline s3-access-test

The GitHub Actions equivalent is a workflow with a workflow_dispatch trigger, optionally exposing inputs so the person running it can pass parameters instead of hard-coding a separate pipeline per variation. Before you migrate, list the custom pipeline names you actually have so none get dropped:

# See every pipeline (incl. custom) configured for a repo
atlassian-cli bitbucket --workspace myteam pipeline list api-service

Migrating at Scale

Translating one repository is a 20-minute exercise. Translating 60 repositories is a program of work, and the hard part is not the YAML: it is knowing which repos even have a pipeline, which ones use services, and which custom pipelines are load-bearing. This is where the CLI earns its place. Instead of cloning every repo to read its config, pull each bitbucket-pipelines.yml straight from the default branch over the API.

First, confirm you are authenticated and enumerate the repositories in the workspace:

# Verify the active Bitbucket identity
atlassian-cli bitbucket whoami

# List repositories as JSON for scripting
atlassian-cli bitbucket --workspace myteam repo list --format json

Then loop over every repo and read its pipeline file with commit browse, which prints a file at a given ref without a clone. Repos that print nothing simply have no pipeline to migrate:

# Audit the migration surface across a whole workspace
atlassian-cli bitbucket --workspace myteam repo list --format json \
  | jq -r '.[].slug' \
  | while read -r repo; do
      echo "== $repo =="
      atlassian-cli bitbucket --workspace myteam commit browse "$repo" \
        --commit main --path bitbucket-pipelines.yml 2>/dev/null \
        || echo "  (no bitbucket-pipelines.yml)"
    done

Redirect that output to a file and you have a single document listing every pipeline config in the workspace. Grep it for services: to find repos that need database containers, for parallel: to find fan-out builds, and for deployment: to find anything that touches production. That inventory becomes your migration backlog, sorted by risk.

One more scale tip: before you flip a repo to Actions, capture a known-good baseline by running its Bitbucket pipeline once and noting the result, so you have something to diff the first GitHub Actions run against.

# Kick a baseline run, then confirm it finished
atlassian-cli bitbucket --workspace myteam pipeline trigger api-service --ref-name main
atlassian-cli bitbucket --workspace myteam pipeline list api-service

For the deeper triggering options, the sibling post trigger Bitbucket Pipelines from the CLI covers refs, variables, and custom pipelines in detail. If you are moving the repositories themselves rather than just the CI, see migrating Bitbucket to GitHub. Every flag used above is documented in the command reference, and a repeatable audit script lives in the Bitbucket repo audit runbook.

FAQ

How do I convert bitbucket-pipelines.yml to a GitHub Actions workflow?

The mapping is mechanical, not magic. Move bitbucket-pipelines.yml from the repo root to .github/workflows/ci.yml. Turn each pipelines: section into an on: trigger, each step into a job or a step inside a job, and each script: list into run: commands. The step image: becomes a container: (or a runs-on runner plus a setup action). There is no one-command rewrite, but once you have translated one repo the pattern repeats for the rest.

What is the GitHub Actions equivalent of a custom pipeline in Bitbucket?

A Bitbucket custom pipeline (one you trigger manually rather than on push) maps to on: workflow_dispatch in GitHub Actions. Each named custom pipeline becomes either its own workflow file with a workflow_dispatch trigger, or an input on a shared dispatch workflow. To find every custom pipeline name before migrating, list them with atlassian-cli bitbucket --workspace myteam pipeline list api-service.

How do Bitbucket Pipelines services map to GitHub Actions?

A service defined under definitions: services: in Bitbucket becomes a services: block on the GitHub Actions job. The service image carries over directly, service variables become the service env: map, and you usually add a ports: mapping so the job can reach the container. For example a postgres service in Bitbucket becomes services: postgres: image: postgres:16 with an env block and ports: 5432:5432.

Is there a tool that automatically converts Bitbucket Pipelines to GitHub Actions?

There is no guaranteed one-to-one auto-converter, because caches, services, deployment environments, and secrets are configured differently in each system. atlassian-cli does not rewrite your YAML, but it does inventory the migration surface: it lists which repositories have pipelines, reads each bitbucket-pipelines.yml straight from the default branch, and enumerates custom pipeline names, so you know exactly what needs translating before you start.

Audit your pipelines before you migrate

Install the free, open-source atlassian-cli and read every bitbucket-pipelines.yml across your workspace in one command.

Install atlassian-cli

Related Resources