Qiita Advent Calendar 2025
こんにちはかつをと申します!
普段はFlutterエンジニアとして開発業務に従事しつつ、
個人開発している 家事アプリ「CAJICO(カジコ)」 を運営してたりしてます。
カジコはリリースから2年半開発と運用を続け、
ありがたいことにユーザー数は7万人を超えました!
それを受け、今まで使っていたHerokuサーバーに限界を感じ、先月11月にAWSへ完全移行が完了したので、その内容を書きたいと思います。
私みたいな個人開発者が、
「サクッとHerokuでデプロイしてみたけどそろそろAWSに移行したいな」
って人は意外と多いと思うので、インフラの知識がない私の失敗談をみて参考になる人がいたら嬉しいです。
具体的な手順はネットにもたくさんあると思うので、今回は移行した経緯や全体的な流れ、そして移行した時の失敗やつまずきポイントをお話します。
移行内容
だいぶざっくり書いてますが、要はサーバーやDB周りを全部AWSにお引越ししました!
え?なんでEC2単体で動かしてるかって...?
もちろん 金が無いからです!!!
個人の財布から泣く泣くお金を払ってる人にとって、安さこそ正義!
(将来的にはECS × Fargateに移行したいと思ってます)
とはいえ、インフラ初心者の私がこの構成に至るまでにだいぶ厳しい道のりがありました...
なんでHerokuを使ってたのか
楽だから
これに尽きます。
主要言語のビルドパックが用意されていて、
インフラの知識が浅い人でもgit push heroku mainするだけでカンタンにリリースできちゃう。
しかもアクセス少なければ激安で運用できることから、
「ニーズあるかわからんけど、とりあえずリリースしてみっかー」
みたいな試しアプリには最適なサービスといえます。
今でも他アプリで愛用してます。
じゃあなんでAWSに移行したのか
理由は大きく3つあります。
1. ユーザーからレスポンス速度が遅いと苦情が多かった
先日アプリ内でユーザーアンケートを取ったところ、一番多かったのが
「アプリをもっと軽くしてほしい」
という要望
これには大きく2つの要因があると思います
- サーバー&DBがUSリージョンにある
- シンプルにスペックが低い
CAJICOでは毎日の家事をアプリに登録しますが、その登録作業に時間がかかると記録が面倒(≒離脱)になるので、これは喫緊の課題だなと感じました。
2. DBのクエリ制限に引っかかるようになってきた
HerokuでMySQLを使う場合、JawsDBというAdd-Onを利用する場合が多いと思いますが、JawsDBはある程度のプランまでアップグレードしないと max_question という制限がついて1時間あたりのクエリ数が制限されたり、ストレージもだいぶ小さかったりします。
もちろんアップグレードすれば解消はできるんですが、割高なのと、一定プランに上げるタイミングでデータ移行が必要になるので、
「そこまでしてHerokuを使う必要あるか?」 となりました。
3. セキュリティの担保
上記と被ってきますが、
JawsDBは性質上DBがpublicに配置されており、IP制限やSSHでの接続制限などもハードルが高いため、 比較的リスクのあるマネージドDB という感覚があります。(違ったらすみません💦)
とはいえ、根本の原因は 「高いから」 です。
上記の理由はHerokuでもアップグレードすればだいたい解決できます。
けど、Herokuって最初は安いのに、アップグレードするたびにどんどん割高感が出てきますし、そこまでしてHerokuに依存する必要なくね...?となってくるわけです。
個人サービスにとってコスト増は致命傷。
今までインフラの勉強をサボり続けてきた私も遂に重い腰を上げてAWSへの移行を決意しました。
移行手順
ざっくり以下の手順で移行していきました。
1. TerraformでAWSにインフラを構築
以下のようなディレクトリ構成で作ってます。
偉そうに書いてますが、Terraformは人のを多少読んでいじるくらいだったので、ほとんどAIに書いてもらいました😇
.
├── Makefile # init/plan/apply の簡易ターゲット
├── guards.tf # 実行アカウントのガードチェック
├── main.tf # ルートモジュール(VPC/EC2/RDS と通知/Chatbot 設定)
├── outputs.tf # ルート出力定義
├── providers.tf # AWS プロバイダ設定
├── variables.tf # ルート変数定義
├── versions.tf # Terraform / provider のバージョン制約
├── envs
│ └── prod
│ └── terraform.tfvars # tokyo/prod の変数値
└── modules
├── ec2 # 単体 EC2 + SG/EIP/監視
│ ├── main.tf # EC2/SG/EIP/CloudWatch Alarm 定義
│ └── variables.tf # EC2 モジュールの入力変数
├── rds # MySQL + SG + SSM 認証参照
│ ├── main.tf # RDS/SG/Parameter Group 定義
│ └── variables.tf # RDS モジュールの入力変数
└── vpc # VPC/サブネット/DB Subnet Group
├── main.tf # VPC/IGW/RT/Subnet/DB Subnet Group 定義
├── outputs.tf # VPC モジュール出力
└── variables.tf # VPC モジュールの入力変数
機密情報はAWSのパラメータストアに登録してそこから取るような形にしてます。
変則的なものはほぼなくて、オーソドックスな構成かなと思います。
2. EC2セットアップ
上記で構成をapplyするだけだと空っぽのEC2インスタンスが出来上がるだけなので、ここに必要なパッケージをインストールしてプロジェクトをデプロイする必要があります。
プロジェクトによって作業はだいぶ変わりますが、私の場合はざっくり以下のセットアップを行いました。
- 必要なパッケージのインストール(Dockerとかgitとかnginxとか)
- Githubリポジトリをclone
-
.envの配置 - デプロイスクリプトの作成
- Nginxの設定(証明書含む)
- 手動デプロイ
2. Heroku側にカスタムドメインを設定 & アプリ側でAPIのドメインを変更
つまづきポイントなので後述しますが、
アプリAPIはもともとHerokuのデフォドメイン(herokuapp.com)で叩いてたため、EC2に移行する際、新しいドメインへ変える必要がありました
3. 夜中にメンテナンスモード
これもやらかしたので後述しますが、
アプリ側でメンテナンスにしてユーザーがさわれないようにします。
4. 既存DBをdumpしてRDSのDBにインポート
もっと良いやり方があったかもですが、今回は変に凝らずにダウンタイムをしっかり取ってシンプルにdump&import方式で移行しました。
5. ドメインの向き先をEC2へ向ける
具体的には以下を行いました。
- Herokuへドメイン設定したCNAMEレコードの削除
- EC2の固定IPのAレコードの設定
- nginx側のconfファイルにドメイン設定&証明書の取得
ここもやらかしたので後述します。(やらかしすぎ...)
6. アプリで動作検証
7. メンテナンスモードを解除して移行作業完了
こんな感じです。
やらかした・躓いたポイント
その1:DBだけ移そうとしてしまった
実は当初サーバー移すの面倒すぎて、最初DBだけ移してしまったんです。
しかし移してみて以下の問題が発覚しました。
- herokuはUSリージョンなので、RDSもUSに合わせなきゃいけなくなった
→ パフォーマンス改善できない - サーバーがHerokuにあるのでDBはpublic accessにしなきゃいけない
→ セキュリティリスク高いまま
→対策としてIP制限やSSH接続をしたいが、Herokuからは難しい
(コスト的にも手間的にも)
結果的にサーバーも至急移行しなきゃいけなくなったうえに、リージョンが違うのでRDSだけ先に移行したメリットもほぼなく、結局一から全部移行するのと同然の二度手間が発生しました😭
(やる前に気づけって話ですが...)
これからHeroku→AWSに移行を考えている方、
やるなら段階移行ではなく、一気に全部移行することを強くお勧めします😭
その2:ドメインを変える必要があった
CAJICOはカスタムドメインを使用せずにHerokuのデフォルトドメイン(herokuapp.com)を使ってAPI疎通をしていたため、AWSに移行する場合はドメイン自体も設定し直す必要がありました。
しかし、スマホアプリの場合、アップデートには 「ストアの申請・承認」 と 「ユーザーの端末内アップデート」 の障壁があり、移行時に一斉にドメイン切替ということが難しいです。
そのため上述の通り、
「事前にHeroku側に移行後のドメインを設定しておき、アプリユーザーがそのドメインを叩くように」
しておく必要があります。
強制アップデートさせたとしても全ユーザーに浸透させるには数日はかかるので、移行前に忘れずに対応しておきましょう。
その3:メンテナンスモードをサボった
今回「その1」で話した理由で、無駄に何回か移行作業をする羽目にはったわけですが、
最初の移行ではメンテナンス対応をせずに夜中の3~4時くらいに普通にやっちゃってました...
そして案の定 エラー通知出まくりました。 😇
理由はシンプルに 「メンテナンスモードを実装するのがめんどい」 って思い込んでたからなんですが、いざ作ったら めっちゃ簡単に作れました。
ざっくりこんな感じです
1. アプリ側で簡易的なメンテナンス画面を用意
死ぬほどシンプルですがメンテナンス画面なんてこんなもんでOK!

2. Firebase Remote Configでメンテナンスフラグを作成
Remote Config初めて使いましたが、
そんな自分でもすぐ理解できるほどわかりやすいUIで助かりました。

3. アプリ側で起動時に受け取ってONならメンテナンス画面に遷移
厳密に言えば起動時以外でもメンテナンス有無を監視できたほうが良いですが、ONにしてある程度時間をおけばほとんどのトラフィックは遮断できるので、精神安定剤としても絶対メンテナンス機構は作ったほうがいいです!
※実際夜中に大量にエラー通知が来ると死ぬほどメンタルえぐれます。
やってみたら30分もかからず作れました!
その4:Herokuのカスタムドメインを外し忘れた
痛恨のミス...
AWSのRoute53側でHerokuへのドメインCNAMEは削除してたので、もうHeroku側にアクセスが行くことはないだろうと思ってたんですが、まさかの朝起きたら全体からすると1%にも満たないですがアクセスが来ており、Herokuの古いDBに新しいデータが混在してしまいました...
既に新DBには同じIDに別のデータが入ってるためシンプルに移すこともできず、仕方なく手動でデータ移行する羽目に、、、
データが少なかったから良かったものの、大量にあったら大事故に繋がっていたので、絶対に忘れずにHeroku側のドメイン設定も消すようにしましょう(普通する)
移行後のパフォーマンス
- レスポンス速度: 39.6%改善 🎉
- ランニングコスト: 51%削減 🎉🎉
精神的にだいぶダメージを食らった今回のインフラ移行作業でしたが、結果的にUXもお金もめちゃくちゃ改善されたので良かったです。
(コストはHerokuでやってたAuto Scaling分がカットされてるので、シンプルなサーバー費用だとそこまでインパクトはないかも?)
普通のIT企業ならインフラエンジニアいると思うのでこんな無駄なミスせずに済むかもしれませんが、インフラ知識ない人がサーバー触るのはそれだけでも心臓に悪いので...
是非そんな方々のご参考になれば幸いです。


