GitHub Actions でブランチごとに Environment を切り替え、同名の secret を使い回す

目次
GitHub Actions で「develop にマージしたら staging、main にマージしたら production にデプロイ」といった環境の出し分けをするとき、こう書いていないでしょうか。
- secret を
DEPLOY_KEY_STG/DEPLOY_KEY_PRODのように別名で2つ用意する - ステップ内で
if [ branch = main ]してどちらの secret を使うか分岐する
これはこれで動きますが、secret 名が増え、分岐ロジックも散らかります。実は job の environment をブランチから動的に決めるだけで、同じ名前の secret を各環境に置く運用にでき、分岐そのものが消えます。
結論のコードはこれです。
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.ref_name == 'main' && 'production' || 'staging' }}
steps:
- name: Deploy
env:
SA_JSON: ${{ secrets.DEPLOY_SERVICE_ACCOUNT }}
run: ./deploy.shmain なら production、それ以外(develop)なら staging の Environment が選ばれ、secrets.DEPLOY_SERVICE_ACCOUNT は選ばれた環境の値に解決されます。secret 名は1つで済みます。
前提: GitHub Environments と Environment secret
GitHub の Environment は「デプロイ先の環境」を表す機能です(リポジトリの Settings → Environments で作成)。ポイントは、Environment ごとに専用の secret(Environment secret)を持てること。
staging環境 → secretDEPLOY_SERVICE_ACCOUNTに stg 用の値production環境 → secretDEPLOY_SERVICE_ACCOUNTに prod 用の値
job に environment: production を指定すると、その job 内の secrets.DEPLOY_SERVICE_ACCOUNT は production 環境の値に解決されます(同名の Repository secret があっても Environment secret が優先)。
つまり「どの環境を選ぶか」さえ決まれば、「どの値を使うか」は GitHub が勝手に出し分けてくれる、という仕組みです。
Before: 別名 secret を if で選ぶ
よくある書き方。secret を2つ用意し、分岐で選びます。
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Pick credentials
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
echo "$DEPLOY_KEY_PROD" > key.json
else
echo "$DEPLOY_KEY_STG" > key.json
fi
env:
DEPLOY_KEY_STG: ${{ secrets.DEPLOY_KEY_STG }}
DEPLOY_KEY_PROD: ${{ secrets.DEPLOY_KEY_PROD }}動きますが、
- secret 名が環境の数だけ増える(
_STG/_PROD/_DEV…) - 分岐が各所に散らばる
- 「本番デプロイ前に承認を挟む」といった保護を掛けにくい(Environment を使っていないため)
After: environment を動的に切り替える
environment にブランチ判定式を書くだけです。
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.ref_name == 'main' && 'production' || 'staging' }}
steps:
- name: Write credentials
env:
SA_JSON: ${{ secrets.DEPLOY_SERVICE_ACCOUNT }}
run: printf '%s' "$SA_JSON" > key.json- secret 名は
DEPLOY_SERVICE_ACCOUNTの1つだけ(各 Environment に同名で置く) - 分岐は
environment:の1行に集約 - ジョブ名やログにも環境が出るので、どの環境へ流れたかが一目でわかる
&& … || … は三項演算子の代用
GitHub Actions の式には三項演算子がありません。そこで 条件 && 真の値 || 偽の値 というイディオムで代用します。
${{ github.ref_name == 'main' && 'production' || 'staging' }}
github.ref_nameは push/マージ先のブランチ名(例:main,develop)mainなら'main' == 'main'が true →&&の右'production'が採用- それ以外なら false →
||の右'staging'にフォールバック
注意: 「真の値」が falsy だと崩れる
このイディオムには落とし穴があります。条件 && A || B は、A が falsy(空文字・false・0)だと、条件が true でも B に流れてしまうという点です。
${{ true && '' || 'fallback' }} # → 'fallback' になってしまう
今回のように A が 'production' のような非空文字列なら安全ですが、環境名に空文字やそれっぽい値を使わないよう注意してください。3分岐以上や複雑な条件になるなら、無理に1行に詰めず、後述の outputs で分けるほうが安全です。
おまけ: 本番だけ承認レビューを掛ける
Environment を使う副次的なメリットとして、**環境ごとに保護ルール(protection rule)**を設定できます。
production環境 → Required reviewers を設定- すると main へのデプロイ job は、指定レビュアーが承認するまで待機する
「staging は自動、production は人が承認してから」を、ワークフローのコードを変えずに Environment 設定だけで実現できます。Before の if 分岐方式ではこれができません。
補足: 3分岐以上にしたいとき
develop → staging, main → production の2分岐なら1行でOKですが、環境が増えるなら job outputs で明示的にマッピングするのが読みやすいです。
jobs:
resolve:
runs-on: ubuntu-latest
outputs:
env: ${{ steps.map.outputs.env }}
steps:
- id: map
run: |
case "${{ github.ref_name }}" in
main) echo "env=production" >> "$GITHUB_OUTPUT" ;;
staging) echo "env=staging" >> "$GITHUB_OUTPUT" ;;
*) echo "env=development" >> "$GITHUB_OUTPUT" ;;
esac
deploy:
needs: resolve
runs-on: ubuntu-latest
environment: ${{ needs.resolve.outputs.env }}
steps: [...]注意点
- Environment は事前に作成が必要。
environment:に書いた名前の環境が存在しないと、その環境は「保護なし」で暗黙作成される挙動なので、意図した secret が無いと解決に失敗します。先に Settings → Environments で作っておく。 github.ref_nameはイベントで中身が変わる。pushではmain/developのようなブランチ名ですが、pull_requestでは<PR番号>/mergeになります。デプロイは基本on: push(マージ後)に紐付けるのが素直です。- Environment secret はその job が対象環境を使うときだけ解決される。
environment:を付けない job から同名 secret を参照しても Environment secret は効きません(Repository secret にフォールバック)。
まとめ
- デプロイ環境の出し分けは、secret を別名で用意して分岐するより、job の
environmentをブランチから動的に決めるほうがきれい - 各 Environment に同名の secret を置けば、
secrets.Xが環境ごとに自動で出し分かる 条件 && 真 || 偽は三項演算子の代用(真の値を falsy にしないこと)- Environment を使うと、本番だけ承認レビューなどの保護も後付けできる
参考ソース
関連記事

GitHub の Repository ruleset を使って develop / main への直接 push を止め、Pull Request 経由を強制する設定を、個人〜少人数開発の視点でまとめます。Require a pull request before merging が本命であること、Required approvals 0 でも直接 push は禁止できること、Restrict updates を選んではいけない落とし穴、Bypass list の扱い、CI 必須化まで解説します。

Terraform の S3 backend が要求する state バケットを、GitHub Actions の OIDC + IAM ロールで手動ワークフローから作成する手順。backend の鶏卵問題、最小権限ロール、冪等な bootstrap、そして role-to-assume が空になるハマりどころまで解説します。

pnpm + Next.js プロジェクトに Renovate を導入し、automerge・依存のグルーピング・lockFileMaintenance を設定する手順を、Mend の Silent mode のハマりどころつきで紹介します。