Pull request を作成したら、レビュアーがすぐに動作確認できるプレビュー環境が欲しい。Fly.io は東京リージョン(nrt)を持ち、KVM ベースのハードウェア仮想化コンテナでアプリを動かすプラットフォームです。
Fly.io には Pull Request (PR) プレビュー環境の組み込み機能はありませんが、GitHub Actions と superfly/fly-pr-review-apps を組み合わせることで実現できます。
今回、前回の Railway 編と同じ Rails 8.1 + PostgreSQL のアプリを Fly.io にデプロイし、PR プレビュー環境が動くところまでを検証しました。
- GitHub: rails-preview-demo
Fly.io とは
Fly.io は、アプリケーションをグローバルに分散デプロイできるプラットフォームです。
| 特徴 | 説明 |
|---|---|
| Machines | KVM ベースのハードウェア仮想化コンテナ。必要なときだけ起動 |
| グローバルデプロイ | 18 リージョンに展開。東京(nrt)対応 |
| 自動停止/起動 | リクエストがなければ Machine を停止し、コストを削減 |
| Fly Postgres | マネージド PostgreSQL クラスタをリージョン指定で作成 |
| CLI 中心 | flyctl CLI でアプリの作成・デプロイ・管理を完結 |
| セキュリティ | Rust/Go スタック。SOC2 Type 2 対応 |
技術スタック
検証に使用した技術スタックです。Railway 編と同じアプリを使用しています。
| 技術 | バージョン |
|---|---|
| Ruby | 3.4.8 |
| Rails | 8.1.2 |
| PostgreSQL | 17 |
アプリは Post の CRUD を scaffold した最小構成です。ルーティングやヘルスチェック(GET /up)、Dockerfile の詳細は Railway 編を参照してください。
Fly.io へのデプロイ
1. fly.toml を作成する
リポジトリのルートに fly.toml を追加します。
app = "rails-preview-demo"
primary_region = "nrt"
[build]
[deploy]
release_command = "bin/rails db:prepare"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 0
[checks.status]
port = 3000
type = "http"
interval = "10s"
timeout = "2s"
grace_period = "30s"
method = "GET"
path = "/up"
[[vm]]
memory = "512mb"
cpu_kind = "shared"
cpus = 1| 設定 | 値 | 説明 |
|---|---|---|
primary_region | nrt | 東京リージョンにデプロイ |
release_command | bin/rails db:prepare | デプロイ前にマイグレーションを実行 |
internal_port | 3000 | Rails(Puma)のデフォルトポート |
auto_stop_machines | stop | アイドル時に Machine を停止 |
auto_start_machines | true | リクエスト時に Machine を自動起動 |
min_machines_running | 0 | 最小稼働数 0(完全停止を許可) |
path(checks) | /up | Rails のヘルスチェックエンドポイント |
Railway との大きな違いは release_command です。Fly.io はデプロイ時に release_command を専用の一時的な Machine で実行してから、アプリ本体の Machine を起動します。db:prepare はここで実行されるため、docker-entrypoint 経由で実行する必要はありません。
2. CLI でアプリを作成する
Fly.io CLI(flyctl)を使ってアプリと PostgreSQL クラスタを作成します。
# CLI のインストール
curl -L https://fly.io/install.sh | sh
# ログイン
fly auth login
# アプリの作成
fly apps create rails-preview-demo
# PostgreSQL クラスタの作成(東京リージョン)
fly postgres create --name rails-preview-demo-db --region nrt
# PostgreSQL をアプリにアタッチ(DATABASE_URL が自動設定される)
fly postgres attach rails-preview-demo-db --app rails-preview-demo
# RAILS_MASTER_KEY を設定
fly secrets set RAILS_MASTER_KEY=<config/master.key の値> --app rails-preview-demo
# デプロイ
fly deployfly postgres attach を実行すると、DATABASE_URL が自動的にアプリの環境変数に設定されます。Railway のように手動で変数参照構文を書く必要はありません。
3. デプロイを確認する
デプロイが完了すると <app-name>.fly.dev のドメインでアプリにアクセスできます。
GitHub Actions による本番デプロイ
Fly.io は Dashboard から GitHub リポジトリを接続して自動デプロイする方法もありますが、PR プレビュー環境と合わせて管理するために GitHub Actions を使います。
.github/workflows/fly-deploy.yml を作成します。
name: Fly Deploy
on:
push:
branches: [main]
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
deploy:
runs-on: ubuntu-latest
concurrency: deploy-group
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-onlymain ブランチに push されると、flyctl deploy --remote-only が実行され、Fly.io のリモートビルダーで Docker イメージをビルドしてデプロイします。
必要な GitHub Secrets
| Secret | 説明 |
|---|---|
FLY_API_TOKEN | Fly.io の org レベルトークン |
RAILS_MASTER_KEY | config/master.key の値 |
FLY_API_TOKEN は以下のコマンドで取得します。
fly tokens create orgorg レベルのトークンが必要な理由は後述します。
PR プレビュー環境
ここからが本題です。Fly.io には Railway のような組み込みの PR プレビュー機能はありません。代わりに、superfly/fly-pr-review-apps という公式の GitHub Action を使います。
ワークフロー
.github/workflows/fly-preview.yml を作成します。
name: Fly Preview
on:
pull_request:
types: [opened, reopened, synchronize, closed]
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
jobs:
preview:
runs-on: ubuntu-latest
concurrency:
group: pr-${{ github.event.number }}
steps:
- uses: actions/checkout@v4
- uses: superfly/fly-pr-review-apps@1.5.0
with:
region: nrt
org: personal
postgres: rails-preview-demo-db
secrets: RAILS_MASTER_KEY=${{ secrets.RAILS_MASTER_KEY }}動作の仕組み
PR のイベントに応じて、ワークフローは以下を自動で実行します。
| イベント | 動作 |
|---|---|
opened / reopened | PR 用の Fly アプリを新規作成し、デプロイ |
synchronize | PR ブランチに push されるたびに再デプロイ |
closed | PR 用の Fly アプリを削除 |
superfly/fly-pr-review-apps は内部で以下の処理を行います。
| ステップ | 内容 |
|---|---|
| 1. アプリの作成 | pr-{番号}-{リポジトリ名} の命名規則で Fly アプリを作成 |
| 2. DB のアタッチ | 既存の PostgreSQL クラスタに新しいデータベースを作成しアタッチ |
| 3. シークレットの設定 | RAILS_MASTER_KEY などを Fly アプリに設定 |
| 4. ビルド・デプロイ | Fly.io のリモートビルダーで Docker イメージをビルドし起動 |
| 5. DB マイグレーション | release_command(db:prepare)を実行 |
PR がマージまたはクローズされると、プレビュー用の Fly アプリは自動的に削除されます。
プレビュー URL の通知
superfly/fly-pr-review-apps 自体は PR にコメントを投稿しません。ワークフローにステップを追加して、プレビュー URL を PR にコメントします。
- name: Set preview URL
if: github.event.action != 'closed'
id: preview-url
run: |
APP_NAME="pr-${{ github.event.number }}-$(echo '${{ github.repository }}' | tr '/' '-')"
echo "url=https://${APP_NAME}.fly.dev" >> "$GITHUB_OUTPUT"
- name: Comment preview URL on PR
if: github.event.action != 'closed'
env:
GH_TOKEN: ${{ github.token }}
run: |
MARKER="<!-- fly-preview-url -->"
BODY="${MARKER}
**Fly.io Preview:** ${{ steps.preview-url.outputs.url }}"
EXISTING=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" \
--jq "[.[] | select(.body | startswith(\"${MARKER}\"))] | first | .id" 2>/dev/null)
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
gh api "repos/${{ github.repository }}/issues/comments/${EXISTING}" \
-X PATCH -f body="${BODY}"
else
gh pr comment "${{ github.event.number }}" --repo "${{ github.repository }}" --body "${BODY}"
fiHTML コメント <!-- fly-preview-url --> をマーカーとして使い、同じ PR への複数回 push で既存コメントを上書きする仕組みです。
GitHub Deployments との連携
さらに、GitHub Deployments API を使ってプレビュー環境のステータスを GitHub 上で管理することもできます。
- name: Create GitHub deployment
if: github.event.action != 'closed'
uses: actions/github-script@v7
with:
script: |
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.pull_request.head.sha,
environment: `preview-pr-${context.payload.number}`,
auto_merge: false,
required_contexts: [],
transient_environment: true,
});
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.data.id,
state: 'success',
environment_url: '${{ steps.preview-url.outputs.url }}',
log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
});transient_environment: true を指定すると、PR がクローズされたときに GitHub が自動でデプロイメントを非アクティブにします。
ハマりどころ — deploy トークンでは権限が足りない
検証中に遭遇した問題を共有します。
問題
fly tokens create deploy で発行したデプロイトークンを FLY_API_TOKEN に設定したところ、PR プレビュー環境のデプロイで以下のエラーが発生しました。
Not authorized to deploy this app
原因
デプロイトークンは特定のアプリに対する操作のみを許可します。PR プレビューのワークフローは pr-{番号}-{リポジトリ名} という新しい Fly アプリを動的に作成するため、既存アプリへのデプロイ権限だけでは不足します。
解決策
org レベルのトークンを使用します。
fly tokens create orgorg レベルのトークンは組織内のすべてのアプリに対する操作を許可するため、新しいアプリの作成も可能になります。
Railway との比較
同じアプリを Railway と Fly.io の両方にデプロイした結果を比較します。
| 観点 | Railway | Fly.io |
|---|---|---|
| PR プレビュー | 組み込み機能 | GitHub Actions が必要 |
| 設定ファイル | railway.json | fly.toml + 2 つのワークフロー |
| DB 管理 | PR ごとに独立した PostgreSQL インスタンスを自動作成 | 既存クラスタに DB を追加 |
| リージョン | 自動選択 | 明示的に指定(nrt など) |
| マイグレーション | docker-entrypoint 経由 | release_command で実行 |
| Bot PR 対応 | Dashboard で有効化が必要 | GitHub Actions なので区別なし |
| カスタマイズ性 | 低い(組み込みのため) | 高い(ワークフローを自由に書ける) |
Railway は設定の手軽さ、Fly.io はワークフローのカスタマイズ性に優れています。PR にプレビュー URL をコメントする方法や、GitHub Deployments との連携など、Fly.io では細かい制御が可能です。
まとめ
Fly.io と GitHub Actions を組み合わせれば、Rails アプリの PR プレビュー環境を構築できます。
fly.toml+ 2 つの GitHub Actions ワークフローで構成superfly/fly-pr-review-appsが PR ごとのアプリ作成・削除を自動化- 東京リージョン(
nrt)にデプロイ可能 release_commandでdb:prepareを実行するため docker-entrypoint の競合がない- org レベルのトークンを使うこと(deploy トークンでは新規アプリを作成できない)
- プレビュー URL の通知や GitHub Deployments 連携はワークフローで自由にカスタマイズ
Railway の組み込み機能と比べると設定ファイルは増えますが、その分ワークフローを自在に制御できます。
以上、Rails を Fly.io にデプロイして PR ごとのプレビュー環境を構築してみた、現場からお送りしました。