2026年6月29日
2026年6月29日
GitHub ActionsでCI/CDパイプラインを構築する|WordPressテーマをテスト後に自動デプロイ
GitHub Actions で CI/CD パイプラインを作ると、「プルリクエストではテストだけ」「main に入ったらテストに通ったものだけを本番へ反映」という流れを自動化できます。この記事では、WordPress の自作テーマ(またはプラグイン)を例に、テスト用ジョブとデプロイ用ジョブを分け、needs でつなぎ、SSH と rsync でサーバーへ反映するまでのワークフローを組み立てます。手作業の FTP アップロードで起きがちな「上げ忘れ」「テストしていないコードの反映」を防ぐのが目的です。
ワークフローの基本構文(on・jobs・steps など)は入門記事で説明しているので、本記事では「パイプラインの設計」と「安全なデプロイ」に焦点を当てます。
パイプラインの全体像
作るのは次の流れです。
- プルリクエストと main への push で CI ジョブ(構文チェック・コーディング規約・必要ならユニットテスト)を実行する
- main への push のときだけ、CI ジョブが成功した場合に デプロイジョブ を実行する
- デプロイジョブは
production環境として実行し、必要なら承認を挟む - デプロイは、事前に作った成果物(不要ファイルを除いたテーマ)を rsync でサーバーへ送る
対象は wp-content 配下の自作テーマやプラグインに限定するのが現実的です。WordPress 本体や他人のプラグインは管理画面や WP-CLI で更新し、データベースや uploads ディレクトリはデプロイの対象から外します。コードはリポジトリから、コンテンツはサーバー上で、と役割を分けておくと、誤ってメディアや設定を上書きする事故を避けられます。
事前準備:デプロイ用の SSH 鍵と接続情報
デプロイ専用の SSH 鍵ペアを手元で作ります。個人の鍵を流用しないのは、漏えい時や担当者の交代時にこの鍵だけを無効化できるようにするためです。
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./deploy_key -N ""
公開鍵(deploy_key.pub)はサーバーのデプロイ用ユーザーの ~/.ssh/authorized_keys に追加します。このユーザーには、テーマのディレクトリへ書き込める最小限の権限だけを与えます。root や管理者ユーザーでのデプロイは避けてください。
接続先サーバーのホスト鍵も控えておきます。ssh-keyscan で取得した内容は、サーバーに直接ログインして ssh-keygen -lf で確認したフィンガープリントと一致するかを照合してから使います。
ssh-keyscan -t ed25519 example.com > known_hosts_entry
GitHub リポジトリの Settings から Environments を開き、production という環境を作成して、その環境のシークレットとして次を登録します。
SSH_PRIVATE_KEY: 秘密鍵(deploy_key)の中身SSH_KNOWN_HOSTS: 上で確認したホスト鍵の行DEPLOY_HOST、DEPLOY_USER、DEPLOY_PATH: 接続先とテーマのディレクトリ(DEPLOY_PATHは/var/www/example/wp-content/themes/my-theme/のように末尾まで明示)
環境(Environment)に登録したシークレットは、その環境を指定したジョブからしか参照できません。さらに環境には、承認者を必須にする保護ルールや、デプロイできるブランチの制限を設定できます。利用できる保護ルールはリポジトリの公開・非公開やプランによって異なるため、GitHub の公式ドキュメントで確認してください(2026年10月現在)。手元の秘密鍵ファイルは登録後に削除します。
ワークフローの全体
.github/workflows/ci-cd.yml として次のファイルを置きます。
name: CI/CD
on:
push:
branches: [ main ]
pull_request:
permissions:
contents: read
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- name: 依存関係をインストール
run: composer install --no-interaction --no-progress
- name: PHP の構文チェック
run: |
find . -name '*.php' -not -path './vendor/*' -print0 | xargs -0 -n1 php -l
- name: コーディング規約のチェック
run: vendor/bin/phpcs
deploy:
needs: ci
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
concurrency:
group: production-deploy
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- name: 本番用の依存関係をインストール
run: composer install --no-dev --optimize-autoloader --no-interaction --no-progress
- name: SSH の設定
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
run: |
install -m 700 -d ~/.ssh
printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
printf '%s\n' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- name: rsync でデプロイ
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
rsync -az --delete \
--exclude-from=.deployignore \
-e "ssh -i ~/.ssh/deploy_key" \
./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH"
各部分の設計意図
**needs: ci** は、CI ジョブが成功したときだけデプロイジョブを動かす指定です。これがパイプラインの中心で、テストに失敗したコードが本番に届かない仕組みになります。
**if:** でイベントとブランチを確認しているのは、同じワークフローがプルリクエストでも動くためです。プルリクエストでは CI だけを実行し、デプロイはスキップされます。
**environment: production** で、前述の環境のシークレットと保護ルールが適用されます。承認者を設定していれば、ジョブは承認待ちで止まり、承認後にデプロイが始まります。
**concurrency** は、同じグループのジョブが同時に動かないようにする設定です。短時間に続けて push したとき、2つのデプロイが並行してファイルを書き換えるのを防ぎます。cancel-in-progress: false にしているのは、デプロイを途中で打ち切ると中途半端な状態が残るおそれがあるためです。
ホスト鍵の検証: SSH の設定で known_hosts を事前に書き込んでいるのは、接続先が本物のサーバーであることを確かめるためです。ネット上の例では StrictHostKeyChecking=no でこの確認を省いているものがありますが、なりすましたサーバーへ接続しても気づけなくなるので避けてください。
シークレットを環境変数経由で渡す: ${{ secrets.X }} をコマンドの中へ直接埋め込むと、値に記号が含まれていた場合にシェルの解釈がずれることがあります。env: で環境変数に入れてから "$X" として参照すると扱いが安定します。
.deployignore と --delete の注意
--delete は、リポジトリに存在しないファイルをサーバー側から削除するオプションです。サーバーとリポジトリを一致させられる反面、DEPLOY_PATH を誤って wp-content やドキュメントルートに設定すると、uploads や他のプラグインまで消えてしまいます。最初は rsync に --dry-run を付けて実行し、ログに出る転送・削除の一覧が想定どおりかを確認してから外してください。作業の前にはサーバーのバックアップも取っておきます。
送らないファイルは .deployignore に1行ずつ書きます。
.git/
.github/
node_modules/
tests/
phpcs.xml.dist
composer.json
composer.lock
.deployignore
vendor/ は --no-dev で本番用だけを入れ直しているので送ります。逆に Composer を使っていないテーマなら、該当する行は不要です。
失敗したときの確認ポイント
- **
Permission denied (publickey)**: 公開鍵がauthorized_keysに入っているか、DEPLOY_USERが正しいか、秘密鍵の改行が崩れていないかを確認します。シークレットへ貼り付けるときは、先頭と末尾の行まで含めます。 - **
Host key verification failed**:SSH_KNOWN_HOSTSの内容が接続先と一致していません。ホスト名の表記(IP アドレスかドメインか)や、SSH のポートを変更している場合の書式を見直します。 - **
rsync: command not foundや接続直後の切断**: rsync は送る側と受ける側の両方に必要です。共有サーバーでは SSH や rsync が使えない、または事前の有効化が必要な場合があるので、サーバー会社の案内を確認します。 - ファイルは届いたのに表示が変わらない: キャッシュプラグインや CDN、OPcache の影響が考えられます。必要に応じて、デプロイ後にキャッシュを消去する手順を追加します。
- テストが手元では通るのに CI で落ちる: PHP のバージョンや拡張モジュールの違いが多い原因です。
setup-phpのphp-versionやextensionsを本番環境に合わせます。
さらに安全にするには
上の構成は、デプロイ中の数秒間に新旧のファイルが混在する可能性があります。アクセスが多いサイトでは、サーバー上にリリースごとのディレクトリを作って転送し、最後にシンボリックリンクを切り替える方式にすると、切り替えが一瞬で済み、問題があれば前のリリースに戻せます。また、本番の前にステージング環境へ自動デプロイし、確認してから本番へ進める2段構成にするのも有効です。
まとめ
- CI ジョブとデプロイジョブを分け、
needsでつないで「テストに通ったものだけを反映」する流れを作ります。 - デプロイは main への push のときだけ、
environmentで保護されたシークレットを使って実行します。 - デプロイ専用の SSH 鍵を使い、ホスト鍵を検証し、
StrictHostKeyChecking=noには頼りません。 rsync --deleteは対象ディレクトリを明示し、最初は--dry-runで確認、事前にバックアップを取ります。concurrencyで同時デプロイを防ぎ、必要ならリリースディレクトリ方式やステージング経由に発展させます。