私は普段、Web 制作と React・TypeScript のアプリ開発をしている個人事業主です。自分の仕事のために、見積・請求・経費・確定申告の集計をまとめて管理する「フリーランス支援ツール」を Next.js で作り、Vercel と Supabase の上で毎日使っています。
2026 年 10 月、このアプリを Vercel+Supabase から AWS へ、ホスティングも DB も認証もファイル置き場も全部移しました。10 月 4 日に作業を始め、11 月 1 日に本番を切り替えています。AWS はほぼ初めてで、つまずいた所もたくさんありました。このシリーズは、その全部を順番に書いていく記録です。
第0回の今回は、手を動かす前の話です。なぜ移すのか、移す前と後で何がどう変わるのか、どんな順番で進めたのか、いくらかかるのか。そして、検討したけれど採らなかった案とその理由を書きます。
この記事でわかること:
- 動いているアプリをわざわざ AWS へ移す理由(目的から構成を選ぶ考え方)
- 移行前(Vercel+Supabase)と移行後(Route 53・ALB・ECS Fargate・RDS・S3・Cognito)の構成
- 本番を止めずに進めるための段階の分け方
- 月額の目安と、使わない時間に払わないための「スイッチ」
- SST・Amplify・Supabase のセルフホストなど、採らなかった案とその理由
※料金は 2026 年 11 月時点の公式ページの数値と、私のアカウントでの実測(1 日あたりの目安)です。最新の情報は記事末尾のリンクで確認してください。
CONTENTS目次
なぜ移すのか:目的は「AWS の運用実績」
最初に正直に書いておくと、Vercel+Supabase で困っていたことはありません。Vercel は main に push すれば 1 分ほどで本番に出ますし、Supabase は DB・ログイン・ファイル置き場が一式そろっています。個人のアプリを動かす場所としては、とても楽な組み合わせです。
それでも移した理由は一つで、フリーランスとしての単価を上げるために、AWS で Web アプリを設計・運用した実績を作りたかったからです。インフラまで任される案件では、VPC・RDS・ECS・IaC(インフラをコードで管理すること)・CI/CD といった言葉が要件に並びます。勉強しただけでなく「自分のアプリを実際にその構成で動かしている」と言えることに価値があると考えました。
前提条件も整理しておきます。
- 利用者は私 1 人。マルチテナント(利用者ごとにデータを分ける)の作りにはなっているが、今のところ使うのは自分だけ
- データは DB とファイルを合わせて約 2GB。DB の引っ越しは短いメンテナンス時間で済む量
- 費用が増えることは判断の軸から外す。今より高くなるのは承知のうえで、実績として説明できる構成を優先する
目的が「安く動かすこと」なら、今のままがいちばんです。目的が「実績づくり」なので、構成も「案件でよく見る標準的な形」を選びました。この判断の順番が、このあとの選択すべての土台になっています。
一度は「Supabase は残す」と決めていた
実は最初から全部移すつもりだったわけではありません。9 月の時点では、ホームページ(WordPress)とこのアプリの 2 本を AWS へ移す計画で、アプリについては「ホスティングだけ AWS に移し、DB・認証・ファイルは Supabase に残す」と決めていました。
そのときの理由はこうです。Supabase は単なる DB ではなく、ログイン・RLS・ファイル置き場・API がひとまとまりになったものです。RLS(Row Level Security=行ごとの読み書きを DB が判定する仕組み)のポリシーはどれも auth.uid() という Supabase のログインに結びついた関数を使っているので、Cognito に替えたら全部動かなくなる。剥がすのは「移行」ではなく「作り直し」になる、と考えました。
10 月 2 日、全面移行をもう一度検討するにあたって、このときの前提を今のコードで測り直しました。すると、思い込みがいくつも見つかりました。
| 項目 | 9 月の判断 | 10 月に測り直した結果 |
|---|---|---|
| RLS のポリシー | 39 本。全部動かなくなる | 67 本/24 テーブルに増えていたが、ほぼすべて auth.uid() = user_id の同じ形。auth.uid() と同じ振る舞いの関数を移行先に置けばそのまま効く |
| ログインの参照 | 52 ファイル | 呼び出しは 94 か所あるが、サーバー側の 1 ファイルに寄せてある。差し替える口は 1 つ |
| ISR(ページを一定時間ごとに作り直す仕組み) | 使っている。AWS で挙動が変わる | 使っていなかった(見ていたのは Server Action の revalidatePath だった) |
| Vercel への依存 | — | 東京リージョンの指定 1 行だけ。Vercel 専用のパッケージは 0 |
| 本当の難所 | ログイン(Supabase Auth) | DB の呼び出し方(supabase-js の PostgREST)。RDS には PostgREST が無いので、約 30 ファイルの書き換えが要る |
利用者が 1 人なので、ログインの移行は「アカウントを作り直す」で済みます。いちばん重いのは、アプリから DB への話し方を変えることでした。そしてその作業は、DB を Supabase に載せたまま先にやれることにも気づきました。これで「全部移す」が現実的な作業量に収まり、10 月 4 日に全面移行に決めました。
あわせて、ホームページは移さないことにしました。実績にしたいのは Web アプリ開発の側で、WordPress を別のサーバーに載せ替えることではないからです。こちらの判断は番外編に書いています。
ここから得た教訓は、過去の判断の数字は時間が経つと古くなるので、覆す前にも、守る前にも測り直すことです。
移行前と移行後の構成
移行前と移行後を、役割ごとに並べます。
| 役割 | 移行前 | 移行後 |
|---|---|---|
| アプリを動かす場所 | Vercel(関数は東京リージョン) | ECS Fargate(private サブネット)。前に ALB |
| DB | Supabase の PostgreSQL | RDS for PostgreSQL 17(private サブネット・インターネットに公開しない) |
| アプリから DB への話し方 | supabase-js(PostgREST 経由) | Drizzle で直接 SQL。RLS は書き換えずにそのまま使う |
| ログイン | Supabase Auth | Cognito のユーザープール |
| ファイル(領収書・ロゴ・音楽・写真) | Supabase Storage(非公開バケット 4 つ) | S3(非公開・署名付き URL) |
| DNS | freelance の A レコード → Vercel(76.76.21.21) |
freelance だけ Route 53 に任せ、ALB へ向ける |
| デプロイ | main に push → Vercel | main に push → GitHub Actions(OIDC で AWS に入る)→ ECR → ECS |
| インフラの管理 | 各サービスの管理画面 | Terraform(state は S3) |
| 監視 | 特になし | CloudWatch のアラーム 4 つ+SNS のメール+AWS Budgets |

主なサービスを一言ずつ説明しておきます。
- VPC(Virtual Private Cloud):AWS の中に作る自分専用のネットワーク。中を public サブネット(インターネットへの道があるところ)と private サブネット(外から届かないところ)に分ける
- ALB(Application Load Balancer):外からの HTTPS を受けて、中のアプリに渡す受付。証明書(ACM・無料)もここに付ける
- ECS Fargate:Docker のイメージ(アプリを入れた箱)を、サーバーを管理せずに動かすサービス。イメージは ECR に置く
- NAT Gateway:private サブネットのアプリが、外へ出ていくためだけの出口。外から中へは入れない
- RDS:AWS が面倒を見てくれる PostgreSQL。バックアップや更新を任せられる
- Cognito:ユーザーのログインを管理するサービス。パスワードの保管や再設定のメールも任せる
- Terraform:AWS の構成をコードで書いて作るための道具。同じものを何度でも作り直せる
- GitHub Actions の OIDC:GitHub が「このリポジトリの main からのデプロイです」という一時的な証明を出し、AWS がそれを確かめて短時間だけ権限を渡す仕組み。AWS のアクセスキー(長期の鍵)を GitHub に置かずに済む
いちばん大きな判断は、アプリを private サブネットに置き、NAT Gateway を 1 つだけ使うことでした。アプリに外から直接届く経路が構造的に無い、という案件の要件書でよく見る 3 層の構成です。NAT は AZ(データセンターの区画)ごとに置くのが本来の形ですが、利用者 1 人なので 1 つにしました。NAT のある AZ が落ちると外への通信が止まりますが、それは承知のうえです。「なぜ 1 つなのか」を説明できること自体も実績になると考えました。
もう一つ、RDS は Multi-AZ(別の AZ に予備を置く構成)にしていません。料金がほぼ倍になるのに対して、止まってもバックアップから戻せれば足りるからです。
作業の分担も書いておきます。Terraform・AWS の操作・git は私が自分の手で打ちました。アプリのコードの多くと手順の下書き・確認は AI(Claude Code)に手伝ってもらい、私はレビューと AWS 側の操作を担当しました。
進め方:本番に触るのは最後の 1 日だけ
全部を一度に移すと、何かが壊れたときに原因が分かりません。そこで、一つずつ入れ替え、そのたびに確かめる形で段階を分けました。決めたルールは 2 つです。
- 本番(Vercel+Supabase)は最後の切り替えの日まで止めない。AWS 側は別のドメイン(
aws.freelance.monochrome17.com)で組み立てる - 同じコードを両方に出し、環境変数で切り替える。たとえばファイル置き場は
STORAGE_BACKEND=s3を ECS にだけ入れる。Vercel 側はそのまま Supabase を使い続ける
| 段階 | やったこと | 時期 | 本番への影響 |
|---|---|---|---|
| 段階1 | アプリと DB のあいだを supabase-js から Drizzle に入れ替える。DB は Supabase のまま | 10/4〜10/12 | 少しずつ本番に反映。いつでも戻せる |
| 段階0(土台) | AWS アカウントの設定、Terraform の state、VPC、ECR、RDS、踏み台、ECS と ALB、HTTPS、GitHub Actions | 10/12〜10/25 | なし(別ドメイン) |
| 段階2 | DB を RDS へ。練習用の DB でリハーサルしてから、本番データの写しを流す | 10/19〜10/25 | なし(写しで動かす) |
| 段階3 | ファイル置き場を S3 へ | 10/29 | なし(ECS だけ S3) |
| 段階4 | ログインを Cognito へ | 10/29 | なし(ECS だけ Cognito) |
| 前準備 | 監視とアラーム、当日用のスクリプト、DNS の委任(向け先は Vercel のまま) | 10/31 | なし(見た目は何も変わらない) |
| 切り替え | 書き込みを止めて DB とファイルを写し、DNS の向け先を ALB に替える | 11/1(19 分) | ここだけ |
番号が「段階1 → 段階0」と前後しているのは、最初に立てた計画の番号をそのまま使っているからです。計画では AWS の土台(段階0)から始めるつもりでしたが、段階1 は AWS のアカウントが無くても始められ、いちばん重い作業でもあったので先にやりました。
AWS 側は、まずデモ版を動かすところから始めました。このアプリには、DB の接続情報が無いと見本のデータで動く「デモモード」があります。秘密情報を一切入れずに「箱が正しく動くか」だけを確かめられるので、ECS と ALB を組み立てる間はずっとデモ版を使いました。

土台ができてから、本番モードのイメージに替えて RDS につなぎ、本番データの写しを入れました。ファイル置き場(段階3)とログイン(段階4)も、この AWS 版の上で一つずつ入れ替えています。本番は最後まで Vercel+Supabase のままで、11 月 1 日に「最新のデータを写して、DNS の向け先を替える」だけを行いました。

切り替えの当日は 20 時 45 分に始めて 21 時 4 分に終わりました。ただし、その途中で DNS のレコードが約 1 分消える障害を起こしています。この話は第11回でくわしく書きます。
費用:月 100 USD 前後と、止めるスイッチ
移行後の構成で、全部を動かしたときの目安です。1 日あたりは私のアカウントで請求を見た実測、月額はそこからの見込みです。
| 項目 | 目安 | メモ |
|---|---|---|
| NAT Gateway | 約 45 USD/月(約 1.5 USD/日) | いちばん高い。作った瞬間から 1 時間ごとに課金 |
| NAT の公開 IPv4 アドレス | 約 3.6 USD/月 | 公開 IPv4 は 1 つごとに課金される |
| ALB | 約 0.6 USD/日 | |
| RDS(db.t4g.micro・シングル AZ) | 約 20〜22 USD/月 | 止めても DB は残す |
| ECS Fargate(0.25 vCPU・0.5GB・arm64・1 タスク) | 約 0.25 USD/日 | arm64(Graviton)は x86 より約 2 割安い |
| 踏み台(EC2 t4g.nano) | 約 4 USD/月 | DB の作業用。本番には要らないので止めてよい |
| Route 53 のホストゾーン 2 つ | 1 USD/月 | 1 つ 0.5 USD/月 |
| CloudWatch のアラーム 4 つ | 約 0.4 USD/月 | |
| S3(約 153MB)/Cognito(Lite・1 人) | 数円/0 円 | |
| 合計 | 約 3.4 USD/日(月 100 USD 前後) | 為替によるが 1.5 万円前後 |
正直、個人のアプリには高い金額です。ただ、最初に書いたとおり費用は判断の軸から外しています。そのうえで、使わない時間には払わない工夫を入れました。
お金がかかるものだけに「スイッチ」を付ける
Terraform の変数で、課金されるものだけを作ったり消したりできるようにしました。
# infra/main/terraform.tfvars(スイッチの状態を git で追う)
enable_nat = true # NAT Gateway と公開 IP
enable_bastion = true # 踏み台
enable_app = true # ALB と ECS のサービス
3 つとも false にして apply すると、NAT・踏み台・ALB・ECS のサービスが消え、RDS と無料のもの(VPC・セキュリティグループ・IAM のロール・ECS のクラスターなど)だけが残ります。これで約 3.4 USD/日 → 約 0.7 USD/日に下がりました。移行の作業中は、何日か手を止めるときにこれを使っています。
スイッチを -var で渡さず tfvars に書いているのは、付け忘れると次の plan で黙って消えるからです。くわしくは第3回と第4回で書きます。
⚠ 本番を AWS に切り替えたあとは、enable_nat と enable_app は止めてはいけないスイッチになりました。費用のスイッチが、そのまま本番の電源になったからです。止めてよいのは踏み台だけです。
予算アラートは先に上げておく
AWS Budgets(予算を超えそうなときにメールで知らせる機能)は、アカウントを作った直後に月 20 USD で設定しました。RDS を作る前に、完成形の見込み(月 100 USD)に合わせて 120 USD に上げています。RDS は apply した瞬間から課金が始まるので、作ってから気づくのでは遅いからです。

1 か月の実測のあとで、費用を下げる段階を設ける
切り替えの直前に「NAT をやめて安くする」案も考えましたが、今の構成のまま切り替えることにしました。理由は 2 つです。
- 実績として効くのは「標準の構成で本番を動かし、実際の請求を見てから、根拠を持って直した」という過程だと考えたから
- 切り替えの直前に構成を変えると、確かめ直しになる。変更は一度に一つにしたかったから
1 か月本番で動かして請求を見たあと、費用を下げる段階を別に設けます。候補は、NAT Gateway の代わりに小さな EC2(t4g.nano で月 3 USD 前後)を NAT として使って private を保つ案と、Fargate を public サブネットに出して公開 IP で外へ出る案です。前後の金額は番外編(12 月)で書く予定です。
なお、「VPC エンドポイント(AWS のサービスへの専用の近道)だけにして NAT を無くす」案は使えません。このアプリは経費の為替レートを外部の API から取るので、インターネットへ出る道がどうしても要るからです。
採らなかった案とその理由
決めるまでに検討して、採らなかった案です。どれも悪い案ではなく、今回の目的(AWS で通しの運用実績を作る)に合わなかったものです。
| 案 | 良いところ | 採らなかった理由 |
|---|---|---|
| ホスティングだけ AWS(Supabase は残す) | 安全で作業が少ない | RDS・Cognito・VPC を運用した実績にならない |
| SST/OpenNext+Lambda | 安い。Next.js との相性も良い | AWS の中身を隠してくれる分、実績として説明できることが減る |
| Amplify Hosting | GitHub をつなぐだけで動く | 同じく抽象度が高い。使っている Next.js 16 への対応も未確認だった |
| Supabase を EC2 でセルフホスト | アプリのコードをほぼ変えずに済む | RDS・Cognito の実績にならず、Supabase 一式(十数個のコンテナ)の運用を背負う |
| PostgREST を自分で運用 | DB の呼び出し方を書き換えずに済む | 運用するものが 1 つ増え、案件でもあまり使われない |
| Better Auth(アプリの中でログインを持つ) | パスワードをそのまま移せる | 利用者 1 人ならその利点が消える。AWS の実績にもならない |
| Aurora Serverless v2+Data API | VPC や NAT を避けられる | 国内の案件でよく使われるのは RDS |
| AWS CDK(TypeScript で IaC) | アプリと同じ言語で書ける。AI との相談では最初これを勧められた | AWS 専用。求人で見かけるのは Terraform のほうが多く、他のクラウドにも使える |
| アプリを public サブネットに置く(NAT なし) | 月 40 USD ほど安い | 経路はあってファイアウォールで閉じているだけ。設定を誤ると開く |
| private+VPC エンドポイントだけ(NAT なし) | 外へ出ない | 必要な数を並べると NAT より高い。外部の API には結局出口が要る |
| NAT を AZ ごとに 2 つ/RDS を Multi-AZ | 止まりにくい | どちらも料金が大きく増える。利用者 1 人には過剰 |
正直に書くと、最初に出てきた案は「SST+Lambda」「Better Auth」のような、安く手軽な組み合わせでした。それはそれで正しい答えです。ただ、目的が「実績づくり」だとはっきりした時点で、案件で説明できる標準的な構成に寄せ直しました。同じアプリでも、目的が変われば正解の構成も変わります。
また、最初の案では ALB の前に CloudFront(世界中の拠点から配信するサービス)も置くつもりでしたが、実際の構成には入れていません。ファイルの署名付き URL を CloudFront で出す案も考えましたが、段階3 の作業が倍になるので見送りました。
シリーズの目次
このシリーズは、実際にやった順番に近い形で全 12 回(+番外編)で書きます。
| 回 | 題材 |
|---|---|
| 第0回 | なぜ Vercel+Supabase から AWS へ全部移すのか(この記事) |
| 第1回 | DB を変える前に「アプリと DB のあいだ」を入れ替える(supabase-js → Drizzle・新旧の突き合わせ) |
| 第2回 | AWS アカウントの最初の設定(root の MFA・予算アラート・IAM Identity Center・CLI の SSO) |
| 第3回 | Terraform の土台と VPC(state を S3 に・public と private・NAT はスイッチ) |
| 第4回 | パスワードを誰も知らない RDS と、SSH なしでつなぐ踏み台(SG を名札で・SSM) |
| 第5回 | Supabase の DB を RDS へ。RLS とアプリを 1 行も変えずに持っていく |
| 第6回 | Next.js を Docker にして ECS Fargate+ALB+HTTPS で動かす(ECR の脆弱性スキャンも) |
| 第7回 | アクセスキーを作らずに push だけでデプロイ(GitHub Actions の OIDC)と、秘密を画面に出さない本番設定 |
| 第8回 | RLS の無い S3 で「本人のファイルだけ」を守る(Supabase Storage → S3) |
| 第9回 | ユーザー ID を変えずに Supabase Auth → Cognito |
| 第10回 | 切り替えの前準備(監視とアラーム・当日用スクリプト・DNS を 2 回に分ける) |
| 第11回 | 本番切り替え 19 分と、DNS が 1 分消えた話(障害の記録) |
| 番外編 | 1 か月動かした実際の費用(12 月に書く予定) |
試験のツボ
AWS 認定試験(Solutions Architect – Associate/SysOps Administrator – Associate)の観点で、今回出てきた構成の押さえどころです。
- public サブネットと private サブネット:違いはサブネットの設定ではなく、ルートテーブルにインターネットゲートウェイへの経路(
0.0.0.0/0)があるかどうか。private から外へ出るには NAT Gateway を経由する - NAT Gateway は AZ ごとのリソース:可用性を上げるなら AZ ごとに置き、各 private サブネットを同じ AZ の NAT に向ける。1 つだけだと、その AZ の障害で全体の外向き通信が止まる(単一障害点)
- ALB は 2 つ以上の AZ が必要:作るときに別々の AZ のサブネットを 2 つ以上指定する。だから利用者 1 人でも VPC は 2 AZ で切る
- RDS の Multi-AZ:別の AZ に同期した予備を置き、障害時に自動で切り替える。予備は読み取りには使えない(読み取りを分けたいならリードレプリカ)。料金はほぼ倍
- Fargate と EC2 起動タイプ:Fargate はサーバーの管理が不要で、タスクごとに vCPU とメモリを指定して課金される。OS の更新や台数の管理をしたくないなら Fargate
- 長期のアクセスキーを避ける:人は IAM Identity Center、CI は OIDC、アプリは IAM ロール。どれも一時的な認証情報で動かすのが推奨
まとめ
- Vercel+Supabase で困っていたわけではなく、目的は「AWS で Web アプリを設計・運用した実績を作って単価を上げる」こと。構成は目的から逆算して選んだ
- 一度は「Supabase は残す」と決めたが、測り直すと RLS は同じ形の繰り返しで、本当の難所は DB の呼び出し方だった。それは DB を Supabase に載せたまま先にやれる
- 移行後は Route 53 → ALB → ECS Fargate(private+NAT 1 つ)→ RDS/S3/Cognito。Terraform で管理し、GitHub Actions の OIDC でデプロイする
- 本番に触るのは最後の 1 日だけ。AWS 側はデモ版から組み立て、本番データの写しで確かめてから切り替えた
- 全部動かして約 3.4 USD/日(月 100 USD 前後)。課金されるものだけスイッチで止められ、止めると約 0.7 USD/日。費用を下げるのは 1 か月の実測を見てから
次回(第1回)は、最初に手を付けた「アプリと DB のあいだ」の入れ替えです。DB は Supabase のまま、supabase-js から Drizzle に書き換え、RLS を 1 本も書き換えずに残した方法と、新旧の結果を突き合わせて確かめたやり方、その途中で見つかった元からの不具合 5 件を書きます。
参考にした公式情報(2026 年 11 月時点):


