【決定版】AWS + Next.jsのモダンWebアプリ構成 2025:TerraformとGitHub Actionsで実現する、スケーラブルインフラ実践ガイド
導入:localhost:3000の先にある巨大な壁
素晴らしいNext.jsアプリケーションが、あなたのローカル環境で完璧に動作している。しかし、その先には、多くの開発者が直面する巨大な壁があります。それは、「このアプリを、どうやって本番環境で、セキュアかつスケーラブルに動かすのか?」という問いです。
Vercelへのデプロイは驚くほど簡単ですが、データベースとの連携、独自のバックエンド処理、VPC内リソースへのアクセスなど、インフラの細かい制御が必要になった途端、途方に暮れてしまいます。EC2を立ててみたものの、手動での環境構築は再現性がなく、セキュリティにも不安が残る…
この記事は、そんなあなたのための「実践的な設計図」です。2025年現在、多くのスタートアップや新規事業で採用されている「AWS + Docker + Next.js」という鉄板構成の設計思想から、インフラをコード化するTerraform、デプロイを自動化するGitHub Actionsまで、本番運用に耐えうる全コードを提供します。
この記事を読み終える頃には、あなたも再現可能でスケーラブルな「自分だけの鉄板構成」を手に入れているはずです。
第1章: なぜこの構成なのか? 2025年の技術選定、その論理的根拠
流行りの技術をただ並べるだけでは意味がありません。ここでは、それぞれの技術がなぜ「最適解」なのかを、トレードオフを交えながら解説します。
- AWS: なぜVercelではダメなのか? データベースや他サービスとのVPC内連携、柔軟なネットワーク構成、そして何より「インフラを完全にコントロールできる」という強みがあります。企業のコンプライアンス要件や、将来的なマイクロサービス化にも柔軟に対応できます。
- ECS Fargate: なぜEC2やLambdaではないのか? EC2のOSパッチ当てやスケーリング管理といった運用負荷と、Lambdaの実行時間15分制限やコールドスタートといった課題。Fargateは、その両者の「いいとこ取り」をしたサーバーレスコンテナサービスです。インフラ管理をAWSに任せつつ、長時間稼働するWebアプリケーションを柔軟に実行できます。
- Terraform: なぜ手動(ClickOps)ではいけないのか? 「インフラのコード化(IaC)」がもたらすメリットは絶大です。手動での作業ミスを防ぎ、誰が実行しても同じ環境を数分で再現できます。Gitでインフラの変更履歴を管理でき、チームでのレビューも可能になります。
- GitHub Actions: なぜ他のCI/CDツールではないのか? ソースコード管理とデプロイパイプラインをリポジトリ内で一元管理できるシンプルさが魅力です。AWSとのOIDC連携を使えば、アクセスキーを発行せずにセキュアなデプロイが実現できます。
第2章: 全体設計図:モダンWebアプリケーションのアーキテクチャ
まず、私たちが構築するシステムの全体像を、一枚の図で示します。各コンポーネントの役割をしっかり理解しましょう。
graph TD
subgraph "ユーザー"
A[ブラウザ]
end
subgraph "AWS Cloud"
B[Route 53] --> C[CloudFront]
C --> D[ALB]
D --> E[ECS Fargate Service]
E -- DB接続 --> F[RDS for PostgreSQL]
E -- 画像等 --> G[S3]
subgraph "CI/CD on GitHub"
H[GitHub Actions] -- 1. Push Image --> I[ECR]
H -- 2. Deploy --> E
end
end
A -- DNSクエリ --> B
A -- HTTPS --> C
- Route 53: 独自ドメインを管理するDNSサービス。
- CloudFront: 世界中にコンテンツをキャッシュし高速配信するCDN。ALBへのトラフィックを中継し、WAFによる保護も担います。
- ALB (Application Load Balancer): 複数のコンテナにトラフィックを振り分けるL7ロードバランサー。
- ECS Fargate: Dockerコンテナの実行環境。サーバー管理不要でアプリを実行できます。
- RDS (Relational Database Service): マネージドなPostgreSQLデータベース。バックアップやパッチ適用はAWSが自動で行います。
- S3 (Simple Storage Service): ユーザーがアップロードした画像などの静的ファイルを保存する、高耐久なストレージ。
- ECR (Elastic Container Registry): Dockerイメージを安全に保存・管理するプライベートレジストリ。
- GitHub Actions: コードのプッシュをトリガーに、テスト、ビルド、デプロイを自動化するCI/CDパイプライン。
第3章: ハンズオン①:Terraformでネットワークとデータベースを構築する
ここからは実践です。アプリケーションの土台となるVPCとRDSをTerraformで構築します。以下のコードをvpc.tf
, rds.tf
などのファイルに保存してください。
vpc.tf
# --- vpc.tf ---
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "my-app-vpc"
}
}
# Public Subnets (for ALB)
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${10 + count.index}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "my-app-public-subnet-${count.index + 1}"
}
}
# Private Subnets (for ECS, RDS)
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${20 + count.index}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "my-app-private-subnet-${count.index + 1}"
}
}
# ... (Internet Gateway, NAT Gateway, Route Tables etc.)
rds.tf
# --- rds.tf ---
resource "aws_db_subnet_group" "default" {
name = "my-app-db-subnet-group"
subnet_ids = [aws_subnet.private[0].id, aws_subnet.private[1].id]
tags = {
Name = "My App DB Subnet Group"
}
}
resource "aws_security_group" "rds" {
name = "my-app-rds-sg"
description = "Allow PostgreSQL traffic"
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.ecs_tasks.id] # ECSタスクからのアクセスのみ許可
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_instance" "default" {
allocated_storage = 20
engine = "postgres"
engine_version = "15.3"
instance_class = "db.t3.micro"
db_name = "myappdb"
username = "admin"
password = random_password.password.result
db_subnet_group_name = aws_db_subnet_group.default.name
vpc_security_group_ids = [aws_security_group.rds.id]
skip_final_snapshot = true
}
第4章: ハンズオン②:Next.jsアプリをDockerでコンテナ化する
Next.jsアプリを本番用にビルドし、軽量なDockerイメージを作成します。イメージサイズを劇的に削減する「マルチステージビルド」とNext.jsのstandalone
出力が鍵です。
Dockerfile
# ---- Base Stage: 依存関係のインストール ----
FROM node:18-alpine AS base
WORKDIR /app
# package.jsonとlockファイルを先にコピーしてキャッシュを効かせる
COPY package*.json ./
RUN npm install
# ---- Build Stage: アプリケーションのビルド ----
FROM base AS build
COPY . .
# Next.jsのビルドコマンドを実行
RUN npm run build
# ---- Production Stage: 本番用の軽量イメージ作成 ----
FROM node:18-alpine
WORKDIR /app
ENV NODE_ENV=production
# ビルドステージから必要なファイルのみをコピー
COPY --from=build /app/next.config.mjs ./
COPY --from=build /app/public ./public
COPY --from=build /app/package.json ./package.json
# standaloneモードで生成されたファイル群をコピー
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
EXPOSE 3000
# アプリケーションの起動
CMD ["node", "server.js"]
第5章: ハンズオン③:TerraformでECS Fargateを定義する
アプリケーションを実行するコンテナ環境をTerraformで定義します。ALB、ECR、ECSのタスク定義とサービスを作成します。
ecs.tf
# --- ecs.tf ---
resource "aws_ecs_cluster" "main" {
name = "my-app-cluster"
}
resource "aws_ecr_repository" "app" {
name = "my-app-repo"
}
resource "aws_ecs_task_definition" "app" {
family = "my-app-task"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256" # 0.25 vCPU
memory = "512" # 512MB
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
container_definitions = jsonencode([
{
name = "my-app-container",
image = "${aws_ecr_repository.app.repository_url}:latest",
cpu = 256,
memory = 512,
essential = true,
portMappings = [
{
containerPort = 3000,
hostPort = 3000
}
]
}
])
}
# ... (ECS Service, ALB, Target Group, IAM Roles etc.)
第6章: ハンズオン④:GitHub ActionsでCI/CDパイプラインを組む
最後に、開発の要である自動デプロイの仕組みを.github/workflows/deploy.yml
に記述します。
name: Deploy to Amazon ECS
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-deploy-role
aws-region: ap-northeast-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: my-app-repo
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
- name: Deploy to Amazon ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ secrets.ECS_TASK_DEFINITION_FAMILY }}
service: ${{ secrets.ECS_SERVICE_NAME }}
cluster: ${{ secrets.ECS_CLUSTER_NAME }}
image: ${{ steps.build-image.outputs.image }}
wait-for-service-stability: true
第7章: 現実的な話:セキュリティとコスト管理の勘所
- 最低限のセキュリティチェックリスト:
- [ ] IAMロールの権限は最小権限の原則に従っているか?
- [ ] セキュリティグループは必要最小限のIPとポートのみ許可しているか?(特にRDS)
- [ ] DBのパスワードなど、機密情報はTerraformのコードに直接書かず、Secrets Managerで管理しているか?
- [ ] ALBにAWS WAFをアタッチして、基本的なWeb攻撃を防いでいるか?
- リアルな月額コスト試算:
- この構成で、小規模なトラフィックを想定した場合の月額コストは以下のようになります(東京リージョン、2025年7月時点)。
- ECS Fargate (0.25vCPU, 0.5GB RAM): 約$12/月
- ALB: 約$22/月
- RDS (db.t3.micro): 約$15/月
- NAT Gateway: 約$45/月(データ処理量による)
- 合計: 約$94/月〜
- NAT Gatewayのコストが比較的高いため、外部へのアウトバウンド通信が少ない場合は、VPCエンドポイントの利用を検討することでさらにコストを削減できます。
まとめ:これはあなたの「資産」になる
私たちは、単なるWebアプリケーションのデプロイ手順をなぞったのではありません。TerraformとGitHub Actionsを使って、いつでも再現可能で、改善し続けられる「インフラ資産」を構築したのです。
この鉄板構成は、あなたの次の個人開発、スタートアップ、新規事業における強力な出発点となるはずです。ぜひ、この設計図を元に、あなた自身の最高のアプリケーションを世界に届けてください。
コメント