Skip to content

GitHub Actions

GitHub Actions を使えば、例えば main ブランチに push したら、まず pytest を実行して、PASS したら 自分のローカルネットワークにあるサーバーでデプロイする、っていう CI(Continuous Integrity)/CD(Continuous Deployment) パイプラインを構築できる。

必要なのは、ワークフローファイルを .github/workflows/my-actions.yml (ファイル名はなんでもいい)のように作成しておいて、 GitHub にソースコードと一緒にあげること。


ワークフローファイルのサンプル

# .github/workflows/my-actions.yml
name: CI/CD pipeline

on:
  push:
    branches: [ "main" ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Install uv
        uses: astral-sh/setup-uv@v8.0.0
        with:
          enable-cache: true
          cache-dependency-glob: "uv.lock"

      - name: Install dependencies
        run: uv sync --frozen

      - name: Run tests
        run: uv run pytest

  deploy:
    needs: test
    runs-on: self-hosted
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Install dependencies
        run: uv sync --frozen

      - name: Restart service
        run: sudo systemctl restart fastapi-app

テストが成功したときだけデプロイする

deployneeds: test をつけてるので、 test ジョブが成功したときだけ deploy ジョブが走る。
pytest が失敗したらデプロイされない: cf. Defining prerequisite jobs (needs)


test ジョブ(ubuntu-latest)

cf. GitHub-hosted runners

  • runs-on: ubuntu-latest — GitHub がホストするサーバーで pytest を実行する。
  • actions/checkout@v6 — リポジトリのコードを GitHub サーバーにコピーする。
  • astral-sh/setup-uv@v8.0.0uv をインストールする。 enable-cachecache-dependency-glob: "uv.lock" で、 uv.lock が変わらない限り依存をキャッシュして高速化する。
  • uv sync --frozenuv.lock の内容そのままで依存をインストールする。 --frozen をつけると uv.lock を更新せず、ロックと pyproject.toml がズレてたらエラーにする。 CI ではロックを勝手に書き換えてほしくないので --frozen にしておく方がいいか。
  • uv run pytest — テストの実行: cf. pytest

deploy ジョブ(self-hosted)

cf. About self-hosted runners

デプロイ先のサーバーに self-hosted runner をセットアップしておくと、そのサーバー上でジョブが走る。ここではコードを取得して依存を同期し、 systemd で FastAPI のサービスを再起動する。

  • runs-on: self-hosted — 自前のサーバー(self-hosted runner)で実行する。 runner がチェックアウトした場所で、そのまま依存の同期とサービス再起動をやる。
  • uv sync --frozen — デプロイ先でも同じロックファイルから依存をそろえる。
  • sudo systemctl restart fastapi-app — FastAPI を動かしてる systemd サービスを再起動して、新しいコードを反映する。

Note

このサンプルは、デプロイ先サーバーに self-hosted runner と FastAPI の systemd サービス、 sudoers が用意されてる前提。セットアップ手順は self-hosted runner のセットアップ を参照。


self-hosted runner のセットアップ

cf. Adding self-hosted runners

デプロイ先のサーバー(ここでは RockyLinux、ユーザーは alice)に runner をインストールする。 GitHub リポジトリの Settings → Actions → Runners → New self-hosted runner → Linux を選ぶと、ダウンロードと config.sh での登録( token 付き)のコマンドが表示されるので、それをサーバーで順に実行する。

  • ダウンロードして展開する。 URL / バージョン / token は GitHub の画面に出てくるものを使う。

    $ cd ~
    
    $ mkdir actions-runner && cd actions-runner
    
    $ curl -o actions-runner-linux-x64-2.333.1.tar.gz -L https://github.com/actions/runner/releases/download/v2.333.1/actions-runner-linux-x64-2.333.1.tar.gz
    
    $ tar xzf ./actions-runner-linux-x64-2.333.1.tar.gz
    
  • config.sh で runner を登録する。いくつか聞かれるけど、基本そのまま Enter でいい。

    $ ./config.sh --url https://github.com/<your-account>/my-fastapi-app --token <TOKEN>
    
  • ./run.sh で起動すると、 GitHub を監視する状態( Listening for Jobs )になる。

    $ ./run.sh
    
    √ Connected to GitHub
    Current runner version: '2.333.1'
    2026-04-20 02:02:44Z: Listening for Jobs
    

    この状態で main に push すると、ジョブが自動で走る。

    2026-04-20 02:02:44Z: Listening for Jobs
    2026-04-20 02:04:43Z: Running job: test
    2026-04-20 02:05:12Z: Job test completed with result: Succeeded
    
  • ./run.sh はターミナルを閉じると止まるので、常時 Listening for Jobs を維持するにはサービスとして登録する。

    $ sudo ./svc.sh install
    
    $ sudo ./svc.sh start
    

    Note

    svc.sh install は、 runner のファイルを持ってるユーザー(ここでは alice)で動くサービスとして登録される。なのでジョブも alice で走り、チェックアウト先は /home/alice/actions-runner/_work/my-fastapi-app/my-fastapi-app のようになる。

    • <runnerインストール先>/_work/ は、 config.sh の work フォルダ設定(デフォルト _work )で決まる。
    • リポジトリ名が 2 回繰り返される部分は、 GITHUB_WORKSPACE を参照

runner のセットアップができたら、 FastAPI 本体を systemd サービスとして登録する。 runner と同じユーザー( alice )で動かすシステムサービス設定例。

  • systemd の設定

    WorkingDirectory を runner のチェックアウト先( <runnerインストール先>/_work/<リポジトリ名>/<リポジトリ名> )にしておけば、デプロイステップの uv sync と再起動がそのまま反映される(コピー処理は不要)。

    # /etc/systemd/system/fastapi-app.service
    [Unit]
    Description=FastAPI App
    After=network.target
    
    [Service]
    User=alice
    Group=alice
    WorkingDirectory=/home/alice/actions-runner/_work/my-fastapi-app/my-fastapi-app
    ExecStart=/home/alice/.local/bin/uv run gunicorn -c gunicorn_config.py app.main:app
    Restart=always
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target
    
  • sudoers の設定

    alice ユーザーがパスワードなしで sudo systemctl restart fastapi-app を実行できるようにしておく。

    # /etc/sudoers.d/fastapi-deploy
    alice ALL=(root) NOPASSWD: /usr/bin/systemctl restart fastapi-app
    
  • gunicorn の設定

    gunicorn_config.py を作成しておく。

    # gunicorn_config.py
    import multiprocessing
    
    worker_class = "uvicorn_worker.UvicornWorker"
    workers = multiprocessing.cpu_count() * 2 + 1
    bind = "0.0.0.0:8000"
    

Secrets

デプロイにトークンなどの秘密情報が必要なら、リポジトリの Settings → Secrets and variables → Actions に登録して、ワークフローから ${{ secrets.NAME }} で参照する: cf. Using secrets in GitHub Actions

      - name: Deploy
        run: ./deploy.sh
        env:
          SOME_TOKEN: ${{ secrets.SOME_TOKEN }}