rpn LP自動デプロイ設計書
1. 概要
現状の課題
- Next.js(静的エクスポート)製のLP「rpn」を公開日に手動でビルド→FTPツールで手動アップロードしている
- 1回の公開で複数パスへの配置が必要(環境変数を変えて複数回ビルド)
- 公開日に人手が必要で、休日や早朝のリリースに対応しづらい
- 手作業のため、アップロードミスや対応漏れのリスクがある
目的
GitHub Actions(GHA)を使い、**指定した日時に複数パスへ自動でFTPアップロード(予約デプロイ)**を実現する。
前提
- Next.jsの静的エクスポート(
next export→out/ディレクトリ) - ビルドは開発者がローカルで手動実行する(GHA上ではビルドしない)
- ビルド時に
BASE_PATH/NEXT_PUBLIC_BASE_PATH環境変数でパスを切り替え、複数回ビルドする - ビルド成果物はリポジトリ内の指定フォルダにコミットして管理する
- FTPアップロード先はロリポップサーバー(FTPS接続)
2. デプロイ対象の構成
1案件で複数パスへの配置が発生する。以下は具体例。
rpnファイルの配置先
| ビルド時 BASE_PATH | FTPアップロード先 | 内容 |
|---|---|---|
/202604/21_FB/0/rpn | /202604/21_FB/0/rpn/ | rpn本体(パターン0) |
/202604/21_FB/4/rpn | /202604/21_FB/4/rpn/ | rpn本体(パターン4) |
/rpn | /rpn/ | rpn本体(共通) |
リダイレクトファイルの配置先
| FTPアップロード先 | リダイレクト先 |
|---|---|
/202604/21_FB/0/ | /202604/21_FB/0/rpn/ |
/202604/21_FB/4/ | /202604/21_FB/4/rpn/ |
3. ビルド手順(ローカル)
基本コマンド
# パスごとに環境変数を切り替えてビルド
export BASE_PATH=/202604/21_FB/0/rpn
export NEXT_PUBLIC_BASE_PATH=/202604/21_FB/0/rpn
npm run build
# → out/ にビルド成果物が生成される
# → builds/{案件名}/202604_21_FB_0_rpn/ にコピー
export BASE_PATH=/202604/21_FB/4/rpn
export NEXT_PUBLIC_BASE_PATH=/202604/21_FB/4/rpn
npm run build
# → builds/{案件名}/202604_21_FB_4_rpn/ にコピー
export BASE_PATH=/rpn
export NEXT_PUBLIC_BASE_PATH=/rpn
npm run build
# → builds/{案件名}/rpn/ にコピー
既存のビルドスクリプトは
scripts/ディレクトリを参照。
ローカル開発
export BASE_PATH=""
export NEXT_PUBLIC_BASE_PATH=""
npm run dev
4. 全体フロー
[開発者] 環境変数を切り替えながらローカルで複数回ビルド
↓
[開発者] builds/{案件名}/{パス別フォルダ}/ にそれぞれ配置
↓
[開発者] リダイレクトファイルも builds/{案件名}/redirects/ に配置
↓
[開発者] コミット&プッシュ
↓
[開発者] GitHub Actions UI → "Run workflow" でデプロイ予約
(案件名、公開日時を入力)
↓
[GHA] deploy-schedule.json に予約情報を追記&コミット
↓
[開発者] リリース時刻に "デプロイ実行" を手動dispatch
(または cron で自動検知 ※オプション)
↓
[GHA] deploy-config.json を読み取り
各パスのビルド成果物をFTPS経由でアップロード
↓
[GHA] ステータスを "deployed" に更新
5. アーキテクチャ
2つのGitHub Actions Workflowで構成する。
Workflow 1: schedule-deploy.yml(デプロイ予約登録)
| 項目 | 内容 |
|---|---|
| トリガー | workflow_dispatch(手動実行) |
| 役割 | デプロイ予約情報を deploy-schedule.json に登録 |
入力パラメータ
| パラメータ | 説明 | 例 |
|---|---|---|
project_name | 案件名(builds/ 配下のフォルダ名) | 202604_21_FB |
deploy_datetime | デプロイ予定日時(JST) | 2026-04-01 10:00 |
Workflowサンプル
name: デプロイ予約登録
on:
workflow_dispatch:
inputs:
project_name:
description: '案件名(builds/配下のフォルダ名)'
required: true
type: string
deploy_datetime:
description: 'デプロイ日時(JST) 例: 2026-04-01 10:00'
required: true
type: string
jobs:
register:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 入力値バリデーション
run: |
# ビルド成果物フォルダの存在確認
if [ ! -d "builds/${{ github.event.inputs.project_name }}" ]; then
echo "::error::builds/${{ github.event.inputs.project_name }} が見つかりません"
exit 1
fi
# deploy-config.json の存在確認
if [ ! -f "builds/${{ github.event.inputs.project_name }}/deploy-config.json" ]; then
echo "::error::deploy-config.json が見つかりません"
exit 1
fi
# 日時フォーマットチェック(YYYY-MM-DD HH:MM)
if ! echo "${{ github.event.inputs.deploy_datetime }}" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$'; then
echo "::error::日時フォーマットが不正です(例: 2026-04-01 10:00)"
exit 1
fi
- name: スケジュール登録
run: |
DEPLOY_ISO=$(echo "${{ github.event.inputs.deploy_datetime }}:00+09:00" | tr ' ' 'T')
# deploy-schedule.json が無ければ空配列で初期化
if [ ! -f deploy-schedule.json ]; then
echo '[]' > deploy-schedule.json
fi
# 新規エントリを追加
cat deploy-schedule.json | jq \
--arg name "${{ github.event.inputs.project_name }}" \
--arg dt "$DEPLOY_ISO" \
'. += [{
"project_name": $name,
"deploy_datetime": $dt,
"status": "pending",
"deployed_at": null
}]' > tmp.json && mv tmp.json deploy-schedule.json
- name: コミット&プッシュ
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add deploy-schedule.json
git commit -m "chore: デプロイ予約追加 - ${{ github.event.inputs.project_name }}"
git push
Workflow 2: deploy.yml(デプロイ実行)
| 項目 | 内容 |
|---|---|
| トリガー | workflow_dispatch(手動実行)※メイン |
| トリガー(オプション) | schedule(cron)※リリース日前後のみ有効化 |
| 役割 | 予約時刻に達した案件の全パスをFTPS経由でアップロード |
運用方針: リリースは月1回のため、常時cronは不要。リリース時刻に手動dispatchで実行するのが基本。必要に応じてリリース前後だけcronを有効化する。
処理フロー
deploy-schedule.jsonを読み込みstatus: "pending"かつdeploy_datetime ≤ 現在時刻のエントリを検出- 該当案件の
deploy-config.jsonを読み取り、各デプロイ先を取得 - 各パスのビルド成果物をFTPS経由でアップロード
- リダイレクトファイルもアップロード
- ステータスを
deployed(成功時)またはfailed(失敗時)に更新
Workflowサンプル
name: デプロイ実行
on:
# 手動実行(メイン)
workflow_dispatch:
# cron(オプション:リリース日前後のみ有効化する場合はコメントアウトを外す)
# schedule:
# - cron: '*/15 * * * *'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 対象エントリの確認
id: check
run: |
if [ ! -f deploy-schedule.json ]; then
echo "has_targets=false" >> $GITHUB_OUTPUT
exit 0
fi
NOW=$(TZ=Asia/Tokyo date +"%Y-%m-%dT%H:%M:%S+09:00")
TARGETS=$(cat deploy-schedule.json | jq \
--arg now "$NOW" \
'[.[] | select(.status == "pending" and .deploy_datetime <= $now)]')
COUNT=$(echo "$TARGETS" | jq 'length')
if [ "$COUNT" -eq 0 ]; then
echo "デプロイ対象なし"
echo "has_targets=false" >> $GITHUB_OUTPUT
else
echo "デプロイ対象: ${COUNT}件"
echo "has_targets=true" >> $GITHUB_OUTPUT
echo "$TARGETS" > targets.json
fi
- name: FTPSデプロイ
if: steps.check.outputs.has_targets == 'true'
env:
FTP_SERVER: ${{ secrets.FTP_SERVER }}
FTP_USERNAME: ${{ secrets.FTP_USERNAME }}
FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
run: |
# lftp インストール
sudo apt-get update && sudo apt-get install -y lftp
# 各対象案件をデプロイ
cat targets.json | jq -c '.[]' | while read -r entry; do
PROJECT_NAME=$(echo "$entry" | jq -r '.project_name')
NOW=$(TZ=Asia/Tokyo date +"%Y-%m-%dT%H:%M:%S+09:00")
CONFIG_FILE="builds/${PROJECT_NAME}/deploy-config.json"
DEPLOY_SUCCESS=true
echo "=========================================="
echo "案件デプロイ開始: $PROJECT_NAME"
echo "=========================================="
# deploy-config.json から各デプロイ先を読み取り
cat "$CONFIG_FILE" | jq -c '.deployments[]' | while read -r deploy; do
LOCAL_DIR=$(echo "$deploy" | jq -r '.local_dir')
REMOTE_PATH=$(echo "$deploy" | jq -r '.remote_path')
DESCRIPTION=$(echo "$deploy" | jq -r '.description // ""')
echo "--- デプロイ: ${DESCRIPTION} ---"
echo " ローカル: builds/${PROJECT_NAME}/${LOCAL_DIR}"
echo " リモート: ${REMOTE_PATH}"
# FTPS経由でアップロード
lftp -c "
set ftp:ssl-allow yes
set ftp:ssl-force yes
set ssl:verify-certificate no
open -u $FTP_USERNAME,$FTP_PASSWORD $FTP_SERVER
mirror --reverse --delete builds/${PROJECT_NAME}/${LOCAL_DIR} ${REMOTE_PATH}
bye
"
if [ $? -ne 0 ]; then
echo "::error::デプロイ失敗: ${LOCAL_DIR} → ${REMOTE_PATH}"
DEPLOY_SUCCESS=false
else
echo "デプロイ成功: ${LOCAL_DIR} → ${REMOTE_PATH}"
fi
done
# ステータス更新
if [ "$DEPLOY_SUCCESS" = true ]; then
cat deploy-schedule.json | jq \
--arg name "$PROJECT_NAME" \
--arg now "$NOW" \
'map(if .project_name == $name and .status == "pending" then .status = "deployed" | .deployed_at = $now else . end)' \
> tmp.json && mv tmp.json deploy-schedule.json
else
cat deploy-schedule.json | jq \
--arg name "$PROJECT_NAME" \
'map(if .project_name == $name and .status == "pending" then .status = "failed" else . end)' \
> tmp.json && mv tmp.json deploy-schedule.json
fi
done
- name: ステータス更新コミット
if: steps.check.outputs.has_targets == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add deploy-schedule.json
git diff --cached --quiet || git commit -m "chore: デプロイステータス更新"
git push
6. リポジトリ構成
プロジェクトルート/
├── builds/
│ └── {案件名}/ # 案件ごとのフォルダ
│ ├── deploy-config.json # この案件のデプロイ設定(パスのマッピング)
│ ├── 202604_21_FB_0_rpn/ # ビルド成果物(パターン0)
│ │ ├── index.html
│ │ └── ...
│ ├── 202604_21_FB_4_rpn/ # ビルド成果物(パターン4)
│ │ └── ...
│ ├── rpn/ # ビルド成果物(共通)
│ │ └── ...
│ └── redirects/ # リダイレクトファイル
│ ├── 202604_21_FB_0/
│ │ └── index.html # → /202604/21_FB/0/rpn/ へリダイレクト
│ └── 202604_21_FB_4/
│ └── index.html # → /202604/21_FB/4/rpn/ へリダイレクト
├── scripts/ # 既存のビルドスクリプト
├── deploy-schedule.json # デプロイ予約管理ファイル
└── .github/
└── workflows/
├── schedule-deploy.yml # Workflow 1: 予約登録
└── deploy-cron.yml # Workflow 2: 定期チェック&実行
7. deploy-config.json(案件ごとのデプロイ設定)
各案件フォルダ内に配置し、ビルド成果物とFTPパスのマッピングを定義する。
{
"project_name": "202604_21_FB",
"deployments": [
{
"description": "rpn本体(パターン0)",
"local_dir": "202604_21_FB_0_rpn",
"remote_path": "/202604/21_FB/0/rpn/"
},
{
"description": "rpn本体(パターン4)",
"local_dir": "202604_21_FB_4_rpn",
"remote_path": "/202604/21_FB/4/rpn/"
},
{
"description": "rpn本体(共通)",
"local_dir": "rpn",
"remote_path": "/rpn/"
},
{
"description": "リダイレクト(パターン0)",
"local_dir": "redirects/202604_21_FB_0",
"remote_path": "/202604/21_FB/0/"
},
{
"description": "リダイレクト(パターン4)",
"local_dir": "redirects/202604_21_FB_4",
"remote_path": "/202604/21_FB/4/"
}
]
}
ポイント:
deploy-config.jsonにすべてのデプロイ先を列挙することで、Workflow側はこのファイルを読むだけで全パスにデプロイできる。案件ごとにパス構成が異なっても柔軟に対応可能。
8. deploy-schedule.json スキーマ
[
{
"project_name": "202604_21_FB",
"deploy_datetime": "2026-04-01T10:00:00+09:00",
"status": "pending",
"deployed_at": null
}
]
| フィールド | 型 | 説明 |
|---|---|---|
project_name | string | 案件名(builds/ 配下のフォルダ名と一致) |
deploy_datetime | string (ISO 8601) | デプロイ予定日時(JST) |
status | string | pending / deployed / failed / cancelled |
deployed_at | string | null | 実際にデプロイされた日時 |
9. GitHub Secrets 設定
リポジトリの Settings → Secrets and variables → Actions に以下を登録する。
| Secret名 | 内容 |
|---|---|
FTP_SERVER | FTPサーバーホスト名 |
FTP_USERNAME | FTPアカウント |
FTP_PASSWORD | FTPパスワード |
重要: FTPクレデンシャルは絶対にコードやドキュメントに直接記載しないこと。
10. 運用フロー
デプロイ予約の手順
-
ビルド: 環境変数を切り替えながらローカルで複数回ビルド
export BASE_PATH=/202604/21_FB/0/rpn export NEXT_PUBLIC_BASE_PATH=/202604/21_FB/0/rpn npm run build cp -r out/ builds/202604_21_FB/202604_21_FB_0_rpn/(パスごとに繰り返す)
-
リダイレクトファイル配置: 必要に応じてリダイレクト用HTMLを作成・配置
-
deploy-config.json 作成: デプロイ先のマッピングを定義
-
コミット&プッシュ: 全成果物+設定ファイルをリポジトリにプッシュ
-
予約登録: GitHub → Actions → "デプロイ予約登録" → "Run workflow"
project_name: 案件フォルダ名(例:202604_21_FB)deploy_datetime: 公開日時(例:2026-04-01 10:00)
-
確認:
deploy-schedule.jsonにpendingステータスで登録されたことを確認
デプロイの確認
- GitHub Actions の "デプロイ定期チェック&実行" ワークフローの実行ログで確認
deploy-schedule.jsonのstatusがdeployedに変わっていれば成功
予約のキャンセル
deploy-schedule.json の該当エントリの status を手動で cancelled に変更してコミットする。
11. 注意事項・制約
| 項目 | 詳細 |
|---|---|
| デプロイ実行 | 基本は手動dispatch。cronを使う場合は最大15〜30分の遅延あり |
| リポジトリサイズ | ビルド成果物をコミットするため、リポジトリサイズが増加する。不要になった成果物は定期的に削除すること |
| 同時デプロイ | 同一案件名で複数の pending エントリがある場合、すべてが実行される。重複登録に注意 |
| GHA無料枠 | パブリックリポジトリは無制限。プライベートリポジトリは月2,000分(Freeプラン) |
| FTPS接続 | ロリポップサーバーはFTPS対応。lftp で暗号化接続する |
| リダイレクトファイル | FTPの --delete オプションにより既存ファイルが削除される可能性がある。リダイレクト先のディレクトリとrpnディレクトリが別アップロードの場合、順序に注意 |
12. 今後の拡展案(参考)
- デプロイ完了時にSlack通知を送る
- デプロイ失敗時のリトライ機能
- ビルド成果物の自動クリーンアップWorkflow
- ビルド自体もGHA上で実行する(環境変数+マトリクスビルド)
作成日時: 2026/03/26