この記事はプログラミング学習者がアプリ開発中に躓いた内容を備忘録として記事におこしたものです。内容に不備などあればご指摘頂けると助かります。
0.前提条件
この記事は以前RubyとRuby on Railsのみを使って制作したTwitterクローンをAWSの本番環境にデプロイする過程を備忘録を兼ねて記録したものです。
失敗内容も掲載しており回り道が多くてかなり冗長な内容になっていますので、ご了承くださいますようお願いします。使用したPC
MacBookAir M2のCPU
OS macOS Tahoe
使用した主要なgemなど
ruby '3.2.1'
gem 'rails', '~> 7.0'
gem 'aws-actionmailer-ses'
gem 'aws-sdk-ses'
1.実装概要
AWS構成の条件
- ECS Fargateでデプロイすること
- マルチAZ構成にすること(ECSをマルチAZで配置、データベースは一つのRDSを共有)
- ロードバランサーを使用すること
- ドメインを購入してhttpsで通信できるようにすること
- 画像をS3へ保存できるようにすること
- メールをSESで送信できるようにすること
最初はDocker CLI経由でdocker-compose.ymlを使ったECSのデプロイ方法を選択しました。
Docker CLIで下記のインスタンスを作成します。
- VPCを作る
- サブネットを2種類、4個作る
- VPCとサブネットの内容をdocker-compose.ymlに追記
- RDSとセキュリティグループを作る
- RDSの情報をdocker-compose.ymlに追記
- Dockerイメージを作る時のコマンド注意
私のPCはAppleシリコンのMacbookAirなので、何も設定しなかったらplartformがarm64でイメージが作成されてしまう。そして、ECSなどで使われるリソースはamd64が前提になっているので正常に起動できない。
上記の不具合を避けるために下記のコマンドのようにplatformを指定してdockerのイメージを作成した。
docker buildx bake -f docker-compose.yml --set "*.platform=linux/amd64"
そして、着手当初はコマンドラインからECSを構築できる方法でやろうとしていたので、下記のコマンドを実行してみた。
docker tag twitter-clone-ecs:latest 513636860721.dkr.ecr.ap-northeast-1.amazonaws.com/rails_templaet:latest
docker push アカウントNo.dkr.ecr.ap-northeast-1.amazonaws.com/twitter-clone-ecs:latest
docker compose create ecs my-ecs-context
docker: 'docker context create' requires 1 argument
この構築方法は2023年11月に廃止されていることに途中で気付いたので別のやり方を模索した。
似たようなやり方でAWS copilot CLI も試そうとしたけど、非推奨であり2026年10月くらいに廃止予定だったので、これもやめて別のやり方に移行した。
結局、手動でECSのクラスター、サービス、タスクを作ってデプロイすることにした。
サービス作成時に下記のエラーが発生
Resource handler returned message: "VPC vpc-00ac7aca6085fd54f has no internet gateway (Service: ElasticLoadBalancingV2, Status Code: 400,
VPCにインターネットゲートウェイが関連づけられていなかったのが原因
サブネットが登録されているルートテーブルでルートを編集 → ルートの追加で0.0.0.0/0を追加することでインターネットゲートウェイを関連付け完了
ALBにinternet facingの機能がついていない可能性を考えて、サービスより先に作ってみたが、エラー内容は変わらず。
パブリックサブネットのIPアドレスの自動割り当て設定を有効にできていなかった。
この操作をすることでインスタンスに自動的にパブリックIPアドレスが付与される。
有効にすることでインスタンスが外部からアクセス可能になる。Webサーバーなどでは有効化が必要
次にALBが既に存在しているというエラーが発生した。
ECSサービスの作成時は既存のALBを使う設定にしていたけが、サービス作成が始まったらなぜかALBを最初から作ろうとして既存のALBと名前衝突したエラーが発生していた。
AWS Resource handler returned message: "Error occurred during operation 'ECS Deployment Circuit Breaker was triggered'."のエラーがログに出力されていた。
CloudFormationで残っているスタックが原因だったようで、ECSサービスを最初に作ろうとしていた時はサービスと同時作成でやっていたので、どうもそれが邪魔していたようなので削除するとやっとサービスの作成が始まった。
deployがdevelopmentで走っていたので、docker-compose.ymlとDockerfileを見直した。
前者にはRAILS_ENV=productionの記載があったが、後者の起動コマンドには環境の指定がなかったので下記のように変更。
CMD [ "bundle", "exec", "rails", "server", "-e", "production", "-p", "3000", "-b", "0.0.0.0"]
新しいエラーに遭遇
raise ArgumentError, "Missing secret_key_base for '#{Rails.env}' environment, set this string with bin/rails credentials:edit"
docker-compose.ymlにsecret_key_baseを記入して再度ECRにイメージをPushして、タスクの新しいリビジョンを作成、サービスを更新したが、エラー文は変わらなかった。
docker-compse.ymlの内容はローカルでビルドした場合にのみ有効で、本番環境は別の方法で対応する必要がある。
タスク定義で新しいリビジョンを作成する時に環境変数として、RAILS_ENV, SECRET_KEY_BASE, DATABSE_URLを追記した。
secret_key_baseのエラーは解決され、ブラウザにロードバランサーのDNSURLを入力したら、504 Gateway Time-outのエラーに変わった。
このエラーは、ゲートウェイ(リバースプロキシやロードバランサー)が、バックエンドサーバーからの応答を受け取れず、一定時間が経過したために発生します。
ロードバランサーやターゲットグループからコンテナを見ると、ヘルスチェックの結果がunhealthyになっていたので、これが原因の可能性がある。
2026年4月12日 11:33
Use Ctrl-C to stop
rails-app
2026年4月12日 11:33
Puma starting in single mode...
rails-app
2026年4月12日 11:33
* Puma version: 5.6.9 (ruby 3.2.1-p31) ("Birdie's Version")
rails-app
2026年4月12日 11:33
* Min threads: 5
rails-app
2026年4月12日 11:33
* Max threads: 5
rails-app
2026年4月12日 11:33
* Environment: production
rails-app
2026年4月12日 11:33
* PID: 1
rails-app
2026年4月12日 11:33
* Listening on http://0.0.0.0:3000
rails-app
2026年4月12日 11:33
=> Booting Puma
rails-app
2026年4月12日 11:33
=> Rails 7.0.8.7 application starting in production
rails-app
2026年4月12日 11:33
=> Run `bin/rails server --help` for more startup options
ALBのDNSにアクセスしても反応しているログが出てこないので、どうやら設定が間違ってECSのコンテナまで辿り着いていないようだ。
タスクのセキュリティグループに誤ってALB(80番ポート許可)のセキュリティグループを設定していたので、タスク用のセキュリティグループを新規作成して3000番ポートを許可するようにした。
タスクのセキュリティグループを設定すると新しいエラーに遭遇
The task cannot pull registry auth from Amazon ECR: There is a connection issue between the task and Amazon ECR. Check your task network configuration. operation error ECR:
なんとセキュリティグループの設定を間違えて、インバウンドとアウトバウンドの両方に3000番だけの許可を設定していた。
アウトバウンド側のルールを全てのポートで許可するように修正すると、やっとRailsのエラー画面に出会うことができた。
Railsが出したエラーの内容からマイグレーションが必要であることが分かったので、
Webアプリ用と同じイメージを使ってマイグレーション用のタスクを定義して、
Dockerfile設定のコマンド部分に/bin/sh -c "bundle exec rails db:migrate"
を追記してからこのタスクを使ってサービスを立ち上げ直した。
マイグレーションが成功すると
Sprockets::Rails::Helper::AssetNotFound in Home#index
The asset "画像ファイル" is not present in the asset pipeline.
のRailsエラーが発生。
マイグレーションが成功したと思ったら、していませんでした...
開発環境で実装時に途中でドロップしたテーブルのマイグレーションファイルが原因でドロップしていたテーブルが見つからないと、エラーが発生していました。
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: relation "削除していたテーブル名" does not exist (ActiveRecord::StatementInvalid)
取り急ぎ、削除したテーブルに関するテーブル操作の部分をコメントアウトして再度イメージをECRにPushしてやってみることにしました。
削除テーブルに関するマイグレーションエラーは無くなりましたが、新たなエラーが発生。
undefined local variable or method `place' for #
class AddColumnsToUsers < ActiveRecord::Migration[7.0]
def change
change_table :users, bulk: true do |t|
t.string place
t.string profile
t.string website
end
end
end
Userテーブルにカラムを追加しているマイグレーションファイルでplaceが未定義ですよ、と。
これは本番環境だとbulk: trueオプションでは change_table ブロック内で t が自動生成されないだからのようです。
以下のようにtを短縮ではなくtableのフル表記に書き換えることで、マイグレーションエラーを解消できました。
class AddColumnsToUsers < ActiveRecord::Migration[7.0]
def change
change_table :users, bulk: true do |table|
table.string :place
table.string :profile
table.string :website
end
end
end
次のエラーは
/myapp/db/migrate/20241007125907_remove_follower_id_from_relations.rb:5:in `change'
PG::UndefinedColumn: ERROR: column "follower_id" of relation "relations" does not exist (PG::UndefinedColumn)
follower_idがrelationsテーブルに存在しないことを指摘するエラーで、これは当初follower_idを通常のカラムとして追加していましたが、他のテーブルから外部キーとして取得する方法に切り替えることになったため、不要なカラムとして削除することにしたことが要因となっていました。
とりあえず、今回は削除 or コメントアウトで対応することにしました。
class RemoveFollowerIdFromRelations < ActiveRecord::Migration[7.0]
def change
remove_column :relations, :follower_id, :bigint
end
end
次にマイグレーションと画像ファイルなどをプリコンパイルできるようになりましたが、Rrailsの画面が表示されなくなりました。
代わりに502 Bad Gatewayの画面が表示され、ECSを起動してしばらくするとコンテナをプロビジョニング解除して停止してしまう現象が発生しました。
ALBのアクセスログを確認するために、S3バケットを用意してこの中にログを保管するようにしました。
http 2026-04-13T10:10:29.677669Z
app/twitter-clone-alb/53d7307b1481a745 210.170.49.187:56348 10.0.31.211:3000 -1 -1 -1 502 - 516 679
"GET アプリのDNS:80/ HTTP/1.1"
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
-1 -1 -1 502の部分でALBがターゲットから有効なHTTPレスポンスを受け取れなかったことを意味している。
ECSのタスクに関連づけられているセキュリティグループを見ると、アウトバウンドルールを見るとタイプ:すべてのTCPに対して、0 - 65535 のポート範囲を許可している設定になっていたので、タイプをすべてのトラフィックに変更して再度ECSを起動してみた。
特に変化は無かったので、別の視点でアプローチしてみることに。
ECSコンテナのログを見ると、マイグレーションやyarn installを繰り返していた。
例えば、[1/4] Resolving packages...が何度も表示されていたりした。
yarn installを1回だけで終わるようにDockerfileに変更を加えてみる。
RUN yarn install
RUN yarn install --production --frozen-lockfile
これでも症状は改善されなかったので、ヘルスチェックの猶予期間をチェックすることに。
設定された時間は30秒と短すぎたので、360秒に変更しました。
これでも挙動は変わらなかったので、猶予期間を720秒に延長。
加えて、ターゲットグループの設定を甘くした。
- 間隔: 60秒
- タイムアウト: 30秒
- 正常しきい値: 5
- 異常しきい値: 10
- 成功ステータス: 200-399
ここで、使っていたタスクはマイグレーション用だったことに気付いた。
rails serverのコマンドを実行していないので、サーバーが起動しなくてアクセスできないのは当然ですよね。。。
イメージを切り替えて、Railsのエラー画面に到達
ActionView::Template::Error (The asset "画像ファイル" is not present in the asset pipeline.):のエラーに戻ってきました。
Dockerfile内で実行されるentrypoint.shに下記の内容を追記することでWebアプリの画面を表示できるようになりました。
# production環境の場合のみ
if [ "$RAILS_ENV" = "production" ]; then
bundle exec rails assets:precompile
# --------------------------------------
# 本番環境(AWS ECS)への初回デプロイ時に利用
# 初回デプロイ後にコメントアウト
bundle exec rails db:create
# --------------------------------------
# マイグレーション処理
bundle exec rails db:migrate
fi
但し、画像やCSSが反映されていなかったので、環境変数を追加することで実装通りのページ内容を表示することができた。
RAILS_SERVE_STATIC_FILES = true
静的ファイルの配信を設定することを意味する。
次にアプリへの外部アクセスがhttp:80だけ対応していたので、https:443からのアクセスができるように変更していきます。
- ALBでリスナーにhttps:443を追加します。設定の途中でACMで取得した証明書を使います。
- また、登録済みのリスナーhttp:80へルールを追加します。
http:80へアクセスがきた場合、アクセスURLを/*として、http:443へリダイレクトさせる設定を加えます。 - ALBが所属しているセキュリティグループのインバウンドルールでhttp:443からの通信を許可するようにルールを追加します。
- 独自ドメインをRoute 53で作成し、Route 53のメニューからホストゾーン(作成したドメイン)を選択して、レコードを作成していく。レコードタイプはAレコードを選択肢し、値としてエイリアスで作成しておいたALBを選択する。この設定により独自ドメインにアクセスがきた時にALBへ遷移させることができる。
これらの設定を加えることで、ブラウザからhttps通信でドメイン名を使ってアプリにアクセスすることができるようになりました。
次にSESでメールを送信できるように設定していきます。
マネジメントコンソールのID登録でアプリのメール送信に使用するメールアドレスを登録して、AWSからメール認証のために送られてきたメッセージのリンクにアクセスしてプロセスを完了させます。
EメールアドレスをIDとして登録して、本番アクセスをリクエストしようと思ったけど、Eメールアドレスでは対応できませんでした。代わりにドメインを登録してやってみることに。
ドメインを登録したら、本番アクセスをリクエストができるようになったので、早速申請を出しました。
そうすると、AWSからフォームを使った問い合わせがきました。
メール送信のプロセスや手順についてできる限り詳しく教えて欲しいとのことです。
2026年4月19日13:06に返信しました。
Amazon SES の使用計画についてご説明いたします。
1. **メール送信頻度**
- トランザクションメール(ユーザー登録確認、パスワードリセット、通知)
- 1日最大 100 通想定(ユーザー数に比例)
- ピーク時でも1時間あたり10通未満を想定
2. **受信者リストのメンテナンス方法**
- ユーザー登録時のメールアドレス(オプトイン)
- 退会・削除時に即時リスト削除
- 重複・無効アドレスは登録時バリデーションで対応
3. **バウンス管理(未着)**
- SNS通知設定でバウンス受信
- ハードバウンス → 即時ユーザー削除
- ソフトバウンス → 3回リトライ後削除
4. **申し立て(スパム)の管理**
- SNS通知で苦情受信
- 受信即時 → ユーザー無効化・リスト削除 ※ユーザーが受信を希望していないと判断
5. **解除申請の管理**
- 各メールに退会リンク(1クリック)
- 退会処理 → 申請者のアドレスを即時全リスト削除
**メールサンプル**
From: 自分の検証済みメールアドレス
To: 登録ユーザーのメールアドレス
Subject: アクション通知のお知らせ
本文:
平素は弊社サービスをご利用頂きありがとうございます。
◯◯さんがあなたをフォローしました。
今後とも弊社サービスをよろしくお願いいたします。
**用途**: アプリの認証・通知メール
**検証済みID**: 自分の検証済みメールアドレス
ご審査のほど、よろしくお願いいたします。
SESの結果を待ちながらActive Storageで使うS3を作っていきます。
ブロックパブリックアクセス (バケット設定)は下記の2項目を有効にしました。
- 新しいパブリックバケットポリシーまたはアクセスポイントポリシーを介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする
これはパブリックアクセスを可能とするバケットポリシーの追加を禁止する設定で、パブリックアクセスを可能とするバケットポリシーの追加がエラーになります。 - 任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介したバケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする
これはパブリックアクセスを可能とするバケットポリシーを無効化する設定で、パブリックアクセスを可能とするバケットポリシーは無効化されます。
要するに上記2つのブロックを有効にしておくと、バケットポリシーは追加できないし、元々書かれていたバケットポリシーが存在してもそれは無効化されるんですね。
S3の設定としてはAWSのマネジメントコンソールでS3のバケットを作成して、storage.ymlの内容を下記のように書き換えました。
amazon:
service: S3
access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
region: ap-northeast-1
bucket: <%= ENV['AWS_BUCKET_NAME'] %>
ENVの部分は環境変数としてAWSのタスクに登録して読み取りれるようにしています。
この設定でアプリの操作を通して画像を扱うとS3バケットに保存されることが確認できました。
SESの本番アクセスをリクエスト申請は承認が下りませんでした、と問い合わせに返信して2日ほどで回答がありました。ちょっと悲しい。
とりあえず、学習用なのでサンドボックス内だけで運用して必要な作業が確認できれば良し、とすることにしました。
サンドボックス内なので、不特定多数のメールアドレスにメールを送ることはできず、送信先のメールアドレスも事前に検証されていることが必要となります。
Gemfileに2つのgemを追加してDockerイメージを作り直しました。
gem 'aws-actionmailer-ses'
gem 'aws-sdk-ses'
dcoker buildx bakse -f docker-compose.yml --set "*.platform=linux/amd64"を実行すると
以下のエラーが発生
So, because Gemfile depends on rails ~> 7.0.0 and Gemfile depends on aws-actionmailer-ses >= 0, version solving has failed.
Gemfileでrails ~> 7.0.0(これは7.0.1より小さいバージョンを許可)を指定していましたが、aws-actionmailer-ses側では~> 7.0(7.1より大きいバージョン)が必要になるので、Gemfile内でRailsのバージョン指定を~> 7.0に変更しました。
変更後は件のエラーがなくなり、イメージの作成が問題なくできました。
SESからメールを送ろうとしたらエラー発生
Aws::SESV2::Errors::AccessDeniedException in Users::RegistrationsController#create
User `arn:aws:iam::513636860721:user/aws-and-infra-wpadmin' is not authorized to perform `ses:SendRawEmail' on resource `arn:aws:ses:ap-northeast-1:513636860721:identity/sampleact2024@gmail.com'
最初はユーザーにSESでメールを送信する権限が付与されていないので、それが原因だから新しくポリシーを付与しようと思ったのですが、エラーの原因は別にありました。
別のユーザーの環境変数(シークレットキーとシークレットアクセスキー)を誤って使っていたために発生しているエラーでした。
環境変数を書き換えて、再度トライしたところこのエラーは解消されました。
Aws::SESV2::Errors::InvalidSignatureException in Users::RegistrationsController#create
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
環境変数を見直して判明したのですが、誤ってsecret_access_keyではなく、secret_key_baseを変更していました。
secret_key_baseは分からなくなったので、rails secretで再度生成してsecret_key_baseを修正して、secret_access_keyも正しい内容に書き換えました。
また、イメージも再度ECRにPushし直して、タスクも新しいリビジョンで作り直してサービスの更新を行いました。
これらの修正を行なった結果、SESからdevise_token_authのアカウント確認メールが届くようになりました。
画像が関係する動作を行なった時に下記のエラーが発生
ArgumentError in Home#index
Can't resolve image into URL: undefined method `persisted?' for nil:NilClass
問題のコード
object = link_to image_tag(tweet.user.icon, alt: "Icon image", class: "icon_image", size: '50x50'), profile_path(tweet.user.id)
ユーザー詳細画面などにimage_tagを使っていますが、画像が未設定なのでエラーになっていたようです。
開発環境の時には出てきていなかったので、ちょっと驚きました。
if文を使って画像がある時だけimage_tagを使うように修正しました。
これで画像が無くてもエラーが発生しないように修正完了です。
- if tweet.user.icon.attached?
= link_to image_tag(tweet.user.icon , alt: "Icon image", class: "icon_image", size: '50x50') , profile_path(tweet.user.id)
以上でアプリは正常に動作するようになりました。
ECSとRDSへの接続方法
コンテナ内で作業することはありませんでしたが、RDSのテーブル内容を確認したり、便宜上テーブルのデータを操作することがあったので、下記のコマンドで接続して調整することがありました。
ECSのコンテナに接続した後にそこからRDSへ接続します。
RDSはプライベートサブネットに設置しているので、パブリックサブネットに設置されているECSのコンテナを経由しないと接続することができないです。
ECS→コンテナ
aws ecs execute-command --cluster <クラスター名> --task <タスクID> --container <コンテナ名> --interactive --command "/bin/sh"
ECSのコンテナコンテナに接続した後に
ECS→RDS
psql -h <RDSエンドポイント> -U <ユーザー名> -d <データベース名>
2.参考にしたサイト
個人的備忘録:「504 Gateway Time-out」エラーの一般的な原因と対策を体系的にまとめてみた
本番環境でのassets:precompile未実施エラー/ 初めてのAWS忘備録④
RailsのECS環境構築で詰まったこと
AWS ECS(Fargate), ALB, RDSの構成でRailsデプロイしてみた
【S3】「パブリックアクセスのブロック」を整理する
あらためて、じっくり動かすFargate
【ECS・Fargate・Docker】ECS・Fargateを使用してコンテナ技術の基礎を学習してみた
ActiveStorageを試し、image_tagのエラー文などについて少し調べた
【AWS】IAMロールとポリシーを理解してRailsからSESを安全に使う
PostgreSQLの基本的なコマンド
サブネットへのインターネットアクセスを追加する
AWS ECS(Fargate), ALB, RDSの構成でRailsデプロイしてみた
