はじめに
CircleCIとGitHub Actionsは、どちらも自動的にコードのビルド、テスト、公開、リリース、デプロイを行うワークフローを作成できます。 CircleCIとGitHub Actionsは、ワークフローの設定において似ているところがあります。
- ワークフローの設定ファイルはYAMLで書かれ、リポジトリに保存されます。
- ワークフローには1つ以上のジョブが含まれます。
- ジョブには1つ以上のステップもしくは個別のコマンドが含まれます。
- ステップもしくはタスクは、再利用とコミュニティとの共有が可能です。
詳しくは、「GitHub Actions を理解する」をご覧ください。
主要な相違点
CircleCIから移行する際には、以下の差異を考慮してください。
- CircleCIの自動テストの並列性は、ユーザが指定したルールもしくは過去のタイミングの情報に基づいて、自動的にテストをグループ化します。 この機能はGitHub Actionsには組み込まれていません。
- コンテナはユーザのマッピングが異なるので、Dockerコンテナ内で実行されるアクションは、権限の問題に敏感です。 Dockerfile で USER命令を使わなければ、これらの問題の多くを回避できます。 GitHub ホステッド ランナーでの Docker ファイルシステムについて詳しくは、「GitHub ホステッド ランナー」をご覧ください。
ワークフローとジョブの移行
CircleCI によって、config.yml ファイルに workflows が定義されます。これにより、1 つ以上のワークフローを構成できるようになります。 GitHub では、ワークフローごとに 1 つのワークフロー ファイルが必要なので、workflows を宣言する必要はありません。 config.yml に構成されている各ワークフローに対して、新しいワークフロー ファイルを作成する必要があります。
CircleCI と GitHub Actions のどちらを使っても、同様の構文を使って、構成ファイル内に jobs が構成されます。 CircleCI ワークフロー内で requires を使ってジョブ間に依存関係を構成する場合は、同様の GitHub Actions needs 構文を使うことができます。 詳しくは、「GitHub Actions のワークフロー構文」をご覧ください。
orbsからアクションへの移行
CircleCIとGitHub Actionsは、どちらもワークフロー中のタスクを再利用し、共有するための仕組みを提供しています。 CircleCIはorbsという概念を利用します。これはYAMLで書かれ、ワークフロー中で再利用できるタスクを提供します。 GitHub Actionsはアクションと呼ばれる強力で柔軟な再利用できるコンポーネントを持っており、これはJavaScriptファイルもしくはDockerイメージで構築できます。 GitHub の API やパブリックに利用可能なサード パーティ API との統合など、任意の方法でリポジトリと対話するカスタム コードを書いてアクションを作成できます。 たとえば、アクションで npm モジュールを公開したり、緊急の問題が発生したときに SMS アラートを送信したり、実稼働可能なコードをデプロイしたりできます。 詳しくは、「自動化の再利用」をご覧ください。
CircleCI では、YAML のアンカーとエイリアスを使ってワークフローの一部を再利用できます。 GitHub Actions では、再利用性を高めるために YAML のアンカーとエイリアスがサポートされており、さまざまな構成でジョブを実行できるマトリックスも用意されています。 マトリックスの詳細については、「ワークフローでのジョブのバリエーションの実行」を参照してください。
Dockerイメージの利用
CircleCIとGitHub Actionsは、どちらもDockerイメージ内でのステップの実行をサポートします。
CircleCIは、共通の依存関係を持つ一連のビルド済みのイメージを提供します。 これらのイメージでは、USER が circleci に設定されていますが、これにより、権限が GitHub Actions と競合します。
GitHub Actionsへの移行に際しては、CircleCIの構築済みイメージから離脱することをおすすめします。 多くの場合、必要な追加の依存関係のインストールにアクションを使うことができます。
Docker ファイルシステムの詳細については、「GitHub ホステッド ランナー」を参照してください。
GitHub ホスト ランナー イメージで使用できるツールとパッケージの詳細については、「GitHub ホステッド ランナー」を参照してください。
変数とシークレットの利用
CircleCI と GitHub Actions では、構成ファイル内での変数の設定と、CircleCI または GitHub の UI を使ったシークレットの作成がサポートされています。
詳細については、「変数リファレンス」および「GitHub Actions でのシークレットの使用」を参照してください。
キャッシュ
CircleCIとGitHub Actionsは、設定ファイル中で手動でファイルをキャッシュする方法を提供しています。
以下は、それぞれのシステムにおける構文の例です。
キャッシュの CircleCI 構文
- restore_cache:
    keys:
      - v1-npm-deps-{{ checksum "package-lock.json" }}
      - v1-npm-deps-
キャッシュの GitHub Actions 構文
- name: Cache node modules
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: v1-npm-deps-${{ hashFiles('**/package-lock.json') }}
    restore-keys: v1-npm-deps-
GitHub Actionsは、CircleCIのDocker Layer Caching(DLC)に相当する機能を持っていません。
ジョブ間でのデータの永続化
CircleCIとGitHub Actionsは、どちらもジョブ間でデータを永続化するための仕組みを提供しています。
以下は、CircleCIとGitHub Actionsの設定構文の例です。
ジョブ間でデータを永続化するための CircleCI 構文
- persist_to_workspace:
    root: workspace
    paths:
      - math-homework.txt
...
- attach_workspace:
    at: /tmp/workspace
ジョブ間でデータを永続化するための GitHub Actions 構文
- name: Upload math result for job 1
  uses: actions/upload-artifact@v4
  with:
    name: homework
    path: math-homework.txt
...
- name: Download math result for job 1
  uses: actions/download-artifact@v5
  with:
    name: homework
詳しくは、「ワークフロー成果物を使ったデータの格納と共有」をご覧ください。
データベースとサービスコンテナの利用
どちらのシステムでも、データベース、キャッシング、あるいはその他の依存関係のための追加コンテナを含めることができます。
CircleCI では、config.yaml で最初に示されているイメージが、コマンドの実行で主に使用されているイメージです。 GitHub Actions では、明示的なセクションを使います。主要なコンテナーには container を使い、追加のコンテナーのリストを services に表示します。
以下は、CircleCIとGitHub Actionsの設定構文の例です。
データベースとサービス コンテナーを使うための CircleCI 構文
---
version: 2.1
jobs:
  ruby-26:
    docker:
      - image: circleci/ruby:2.6.3-node-browsers-legacy
        environment:
          PGHOST: localhost
          PGUSER: administrate
          RAILS_ENV: test
      - image: postgres:10.1-alpine
        environment:
          POSTGRES_USER: administrate
          POSTGRES_DB: ruby26
          POSTGRES_PASSWORD: ""
    working_directory: ~/administrate
    steps:
      - checkout
      # Bundle install dependencies
      - run: bundle install --path vendor/bundle
      # Wait for DB
      - run: dockerize -wait tcp://localhost:5432 -timeout 1m
      # Setup the environment
      - run: cp .sample.env .env
      # Setup the database
      - run: bundle exec rake db:setup
      # Run the tests
      - run: bundle exec rake
workflows:
  version: 2
  build:
    jobs:
      - ruby-26
...
- attach_workspace:
    at: /tmp/workspace
データベースとサービス コンテナーを使うための GitHub Actions 構文
name: Containers
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    container: circleci/ruby:2.6.3-node-browsers-legacy
    env:
      PGHOST: postgres
      PGUSER: administrate
      RAILS_ENV: test
    services:
      postgres:
        image: postgres:10.1-alpine
        env:
          POSTGRES_USER: administrate
          POSTGRES_DB: ruby25
          POSTGRES_PASSWORD: ""
        ports:
          - 5432:5432
        # Add a health check
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
    steps:
      # This Docker file changes sets USER to circleci instead of using the default user, so we need to update file permissions for this image to work on GH Actions.
      # See https://docs.github.com/actions/using-github-hosted-runners/about-github-hosted-runners#docker-container-filesystem
      - name: Setup file system permissions
        run: sudo chmod -R 777 $GITHUB_WORKSPACE /github /__w/_temp
      - uses: actions/checkout@v5
      - name: Install dependencies
        run: bundle install --path vendor/bundle
      - name: Setup environment configuration
        run: cp .sample.env .env
      - name: Setup database
        run: bundle exec rake db:setup
      - name: Run tests
        run: bundle exec rake
詳しくは、「Docker サービス コンテナーとの通信」をご覧ください。
コード例全体
以下は実際の例です。 左側には、thoughtbot/administrator リポジトリの実際の CircleCI config.yml が表示されています。 右は、同等のGitHub Actionsを示しています。
CircleCI の完全なサンプル コード
---
version: 2.1
commands:
  shared_steps:
    steps:
      - checkout
      # Restore Cached Dependencies
      - restore_cache:
          name: Restore bundle cache
          key: administrate-{{ checksum "Gemfile.lock" }}
      # Bundle install dependencies
      - run: bundle install --path vendor/bundle
      # Cache Dependencies
      - save_cache:
          name: Store bundle cache
          key: administrate-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      # Wait for DB
      - run: dockerize -wait tcp://localhost:5432 -timeout 1m
      # Setup the environment
      - run: cp .sample.env .env
      # Setup the database
      - run: bundle exec rake db:setup
      # Run the tests
      - run: bundle exec rake
default_job: &default_job
  working_directory: ~/administrate
  steps:
    - shared_steps
    # Run the tests against multiple versions of Rails
    - run: bundle exec appraisal install
    - run: bundle exec appraisal rake
jobs:
  ruby-25:
    <<: *default_job
    docker:
      - image: circleci/ruby:2.5.0-node-browsers
        environment:
          PGHOST: localhost
          PGUSER: administrate
          RAILS_ENV: test
      - image: postgres:10.1-alpine
        environment:
          POSTGRES_USER: administrate
          POSTGRES_DB: ruby25
          POSTGRES_PASSWORD: ""
  ruby-26:
    <<: *default_job
    docker:
      - image: circleci/ruby:2.6.3-node-browsers-legacy
        environment:
          PGHOST: localhost
          PGUSER: administrate
          RAILS_ENV: test
      - image: postgres:10.1-alpine
        environment:
          POSTGRES_USER: administrate
          POSTGRES_DB: ruby26
          POSTGRES_PASSWORD: ""
workflows:
  version: 2
  multiple-rubies:
    jobs:
      - ruby-26
      - ruby-25
GitHub Actions の完全なサンプル コード
# このワークフローはGitHubによって認定されていないアクションを使用します。
# それらはサードパーティによって提供され、
# 別個の利用規約、プライバシーポリシー、
# ドキュメントを参照してください。
# GitHub では、コミット SHA にアクションをピン留めすることが推奨されます。
# 新しいバージョンを取得するには、SHA を更新する必要があります。
# タグまたはブランチを参照することもできますが、アクションは警告なしに変更される可能性があります。
name: Containers
on: [push]
jobs:
  build:
    strategy:
      matrix:
        ruby: ['2.5', '2.6.3']
    runs-on: ubuntu-latest
    env:
      PGHOST: localhost
      PGUSER: administrate
      RAILS_ENV: test
    services:
      postgres:
        image: postgres:10.1-alpine
        env:
          POSTGRES_USER: administrate
          POSTGRES_DB: ruby25
          POSTGRES_PASSWORD: ""
        ports:
          - 5432:5432
        # Add a health check
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
    steps:
      - uses: actions/checkout@v5
      - name: Setup Ruby
        uses: eregon/use-ruby-action@ec02537da5712d66d4d50a0f33b7eb52773b5ed1
        with:
          ruby-version: ${{ matrix.ruby }}
      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: vendor/bundle
          key: administrate-${{ matrix.image }}-${{ hashFiles('Gemfile.lock') }}
      - name: Install postgres headers
        run: |
          sudo apt-get update
          sudo apt-get install libpq-dev
      - name: Install dependencies
        run: bundle install --path vendor/bundle
      - name: Setup environment configuration
        run: cp .sample.env .env
      - name: Setup database
        run: bundle exec rake db:setup
      - name: Run tests
        run: bundle exec rake
      - name: Install appraisal
        run: bundle exec appraisal install
      - name: Run appraisal
        run: bundle exec appraisal rake