私は普段、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目次
  1. なぜ移すのか:目的は「AWS の運用実績」
  2. 一度は「Supabase は残す」と決めていた
  3. 移行前と移行後の構成
  4. 進め方:本番に触るのは最後の 1 日だけ
  5. 費用:月 100 USD 前後と、止めるスイッチ
  6. 採らなかった案とその理由
  7. シリーズの目次
  8. 試験のツボ
  9. まとめ

なぜ移すのか:目的は「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
Route 53 から ALB、ECS Fargate、RDS へつながり、ECS が S3・Cognito と NAT Gateway 経由のインターネットへ出る全体構成図。Terraform が土台を作り、GitHub Actions が OIDC でイメージを入れ替える
移行後の全体の構成。アプリと DB は private サブネットに置き、外へは NAT Gateway から出る

主なサービスを一言ずつ説明しておきます。

  • 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 を組み立てる間はずっとデモ版を使いました。

ALB の名前で開いたデモ版のダッシュボード。まだ HTTPS ではなく、アドレス欄に「保護されていない通信」と出ている
ALB の名前で開いたデモ版のダッシュボード。まだ HTTPS ではなく、アドレス欄に「保護されていない通信」と出ている

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

AWS 版に本番データの写しが入ったダッシュボード
AWS 版に本番データの写しが入ったダッシュボード(取引先名は伏せています)

切り替えの当日は 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 した瞬間から課金が始まるので、作ってから気づくのでは遅いからです。

AWS Budgets で月の予算を 120 USD に更新した画面
AWS Budgets で月の予算を 120 USD に更新した画面

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 月時点):