「Cloudflare Workers にデプロイしたら 30 秒制限で落ちて、Vercel に逃げたら今度は Postgres の永続コネクションを張れなくて詰まった」— Antigravity でサーバーサイドの一部を書いている個人開発者から、最近この話を聞くことが増えました。私自身、画像生成プロキシや Stripe Webhook の再送処理を載せるときに、同じ壁に何度かぶつかっています。
そこで実用的な逃げ道として浮上するのが Railway です。Cloudflare Workers のような完全エッジでもなく、Vercel の Functions のようなサーバーレスでもない、コンテナベースの「軽い VPS のように使える PaaS」という立ち位置で、個人プロダクトと相性がよい選択肢になります。ここで Antigravity のターミナルと AI エージェントを併用しながら、Next.js + Postgres + Worker プロセスのよくある構成を Railway に乗せるまでを順を追って整理します。
なぜ Cloudflare でも Vercel でもなく Railway を選ぶ場面があるのか
選定の出発点は、流行りのプラットフォーム比較ではなく「自分のプロダクトのどこで詰まったか」です。私が Railway を採用するのは、だいたい次のいずれかに当てはまる時です。
- 30 秒以上かかる処理(PDF 生成、AI 推論、長尺動画の変換など)が含まれる
- Postgres / Redis を Managed で立て、永続コネクションを保ちたい
- バックグラウンドワーカー(cron・キュー)を 1 サービスとして常駐させたい
- ファイルシステムを書き込み可能な状態で使いたい(テンポラリの中間ファイル等)
- Docker イメージそのまま動かしたい(既存の Dockerfile を捨てたくない)
逆に、純粋な静的サイトや軽量 API、リクエスト数が爆発するエッジ需要であれば、Cloudflare Workers と Vercel の方が明確に向いています。Railway は「ちょっと厚いサーバー」が欲しい場面の選択肢として持っておくのがおすすめです。
ちなみに、選択軸を整理した類似記事として Vercel デプロイのワークフローガイド や Cloudflare Workers デプロイ実践 もあります。Railway を採るかどうかは、これらと並べて比較したうえで決めると後悔しにくいです。
Antigravity からプロジェクトを Railway 用に整える
ここからは Next.js 15 + Hono のサーバー + Postgres というよくある構成を例に進めます。Antigravity を立ち上げ、以下のような短い指示をエージェントに渡します。
このプロジェクトを Railway にデプロイできるように整えたい。
- Dockerfile(multi-stage, alpine)
- railway.json(restartPolicyType=ON_FAILURE)
- package.json の start スクリプトが PORT を尊重するか確認
- .dockerignore
を作成・更新してください。
Antigravity が出してきた diff のうち、最低限手で確認すべきは Dockerfile と start スクリプトです。Railway は PORT 環境変数を注入するため、サーバーが固定ポートを掴んでいると起動直後に落ちます。
# Dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
# Railway が PORT を注入するため、固定値はここで指定しない
EXPOSE 3000
CMD ["node", "server.js"]期待する挙動: Railway がビルドした最終イメージのサイズはおおむね 180〜220MB に収まり、Cold start は 2〜4 秒程度で立ち上がります。next.config の output: "standalone" が有効になっていない場合、最終イメージが 1GB を超えるので注意してください。
railway.json は以下のような最小構成にしています。
{
"$schema": "https://railway.app/railway.schema.json",
"build": { "builder": "DOCKERFILE", "dockerfilePath": "Dockerfile" },
"deploy": {
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10,
"healthcheckPath": "/api/healthz",
"healthcheckTimeout": 30
}
}ヘルスチェックを書いておくと、デプロイ直後にコンテナが落ちた時に Railway 側が自動で旧リビジョンへ戻してくれます。最初は省略してもよいのですが、ステージング以降では入れておく方が安全です。
Antigravity の AI レビューでハマりどころを潰す
ここが Railway × AI IDE の真価が出るところです。Antigravity の AI エージェントに次のように依頼します。
このプロジェクトを Railway にデプロイする前に、以下の観点で厳しめにレビューしてください。
1) PORT を尊重しないコード
2) ローカルファイルシステムへの永続書き込み
3) コネクション数が爆発しそうな ORM 設定(Prisma の prepared statement、コネクションプール上限)
4) Cron や setInterval を裸で使っている箇所
5) NEXT_PUBLIC_ で漏れているシークレット
このレビューを通すと、私の場合は毎回 1〜2 件は修正対象が出てきます。とくに Prisma を使っているプロジェクト では、Railway の Postgres と組み合わせる時に connection_limit を明示しないと、デプロイ直後にコネクションを使い切って 502 が連発する事故が起きやすいです。
// lib/prisma.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
datasources: {
db: {
// Railway の DATABASE_URL に ?connection_limit=5 を付与
url: process.env.DATABASE_URL,
},
},
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;期待する挙動: 個人開発レベルのトラフィックなら connection_limit=5 で十分です。同時接続数が読めないうちは、Railway の Postgres プランの最大値より明らかに小さい値で固定しておくのが、夜間に焦らないための簡単な保険になります。
環境変数と Reference 機能を使った設定の省力化
Railway の便利機能のひとつが Reference 変数です。Postgres プラグインを追加すると ${{Postgres.DATABASE_URL}} のような参照が書けるようになり、Web サービス側の環境変数に手動で URL をコピペしなくてよくなります。
DATABASE_URL=${{Postgres.DATABASE_URL}}
REDIS_URL=${{Redis.REDIS_URL}}
NEXT_PUBLIC_SITE_URL=https://${{RAILWAY_STATIC_URL}}
NEXTAUTH_URL=https://${{RAILWAY_STATIC_URL}}
NEXTAUTH_SECRET=YOUR_NEXTAUTH_SECRET
期待する挙動: プラグインの URL や認証情報がローテーションされても、Reference 変数を使っている限りアプリ側の再設定は不要です。Antigravity に「.env.example を Railway の Reference 変数前提で書き直して」と頼むと、上のような形に一括で整えてくれます。
カスタムドメインは Railway のダッシュボードから追加し、CNAME を XXXX.up.railway.app に向ける形が標準です。Cloudflare をフロントに置く場合は、まず Cloudflare 側を「DNS only(プロキシ無効)」で接続確認してから、必要に応じてプロキシを有効化すると切り分けが楽です。
バックグラウンドワーカーと Cron を 1 リポジトリで運用する
Railway は同一リポジトリの中から複数のサービスを建てられます。私のよくある構成は次の通りです。
- web: Next.js + Hono の API
- worker: BullMQ などのキュー消費プロセス
- cron: 1 日 1 回の集計バッチ
package.json の scripts を分け、Railway 側で「サービスごとに start コマンドを上書き」する運用にすると、Dockerfile を 1 本に保ったまま役割を分担できます。
{
"scripts": {
"build": "next build",
"start": "node server.js",
"start:worker": "node dist/worker.js",
"start:cron": "node dist/cron.js"
}
}期待する挙動: web 用は npm run start、worker 用は npm run start:worker のようにサービスごとに変えるだけで、3 つのプロセスを 1 リポジトリ・1 Dockerfile で運用できます。Cloudflare Workers Cron や Vercel Cron と比べると、長時間処理や常駐型ワーカーの運用は Railway の方が素直です。
スケジュール設計の参考としては、Antigravity でのキュー設計 も合わせて読むと、ワーカーをどこまで分割するかの感覚がつかみやすいと思います。
コストと制約を正直に見ておく
最後に、見落とすとあとで痛い実用的な制約を簡単に挙げておきます。
- 無料枠は実行時間ベース(執筆時点で月 500 時間相当のクレジット)。常時稼働するワーカーを 2 つ以上動かすと、すぐ枠を超えます
- リージョンは US/EU/Asia-Southeast/Asia-Northeast から選べますが、サービスごとに別リージョンに置くと内部通信レイテンシが増えます。基本は揃えるのがおすすめです
PORTを環境変数経由で受ける必要がある(固定ポート決め打ちのコードは即落ちる)- CDN は内蔵していありません。静的アセットは Cloudflare R2 や Vercel Edge Network 等と組み合わせる方が現実的です
- ログは標準出力ベース。構造化ログを出しておくと、
railway logsでの絞り込みが格段に楽になります
このあたりを Antigravity のチェックリストとして agents.md に書いておくと、次のプロジェクトでも同じ落とし穴を踏まずに済みます。私は agents.md の運用ガイド を踏まえて、デプロイ先別のレビュー観点を分けて書くようにしています。
全体を振り返って — 「2 つ目のデプロイ先」として Railway を持っておく
Cloudflare Workers と Vercel が主役の時代でも、長時間処理・永続コネクション・常駐ワーカーが絡む瞬間は必ず来ます。Railway をその「2 つ目の選択肢」として今日のうちに 1 サービスだけ実験的に動かしておくと、いざ詰まったときに迷わず逃げ込めます。
次の一歩として、いまお手元のプロジェクトの中で「30 秒以内に終わらない処理が 1 つでもあるか」を確認してみてください。心当たりが 1 つでもあれば、本稿の Dockerfile と railway.json をそのままコピーして、Antigravity に「Railway デプロイの初期セットアップを当てて」と頼むだけで、最短 15 分でステージングが立ち上がります。