关于 aws-production-deploy
为需要在 AWS 上部署全栈应用的开发者提供生产部署指南,涵盖容器、网络、CI/CD 与监控配置,并提醒常见运维风险。
主要功能
- 01配置 ECR、ECS Fargate 与网络
- 02搭建 GitHub Actions 部署流水线
- 03设置 ALB、SSL 与 Redis 缓存
- 04配置 CloudWatch 日志和告警
- 05提供 Docker 配置与运维检查项
适用场景
- 01将前后端容器发布到 AWS
- 02为 ECS 服务配置持续部署
- 03为生产环境添加监控与缓存
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ aiagentwithdhruv/ skills --skill aws-production-deploy安装后在 Claude Code 中输入/aws-production-deploy确认已加载。
详细介绍
AWS 生产部署栈
目标
在 AWS 上将任意全栈应用部署到生产环境,并配置完善的 CI/CD、监控、缓存和负载均衡。完成设置后,无需手动部署。
何时使用此技能
- 将 Next.js + FastAPI(或任意前端 + 后端)部署到生产环境
- 设置 Docker → ECR → ECS Fargate 流水线
- 为 AWS 配置 GitHub Actions CI/CD
- 添加 CloudWatch 日志记录 + Grafana 仪表板
- 设置 ElastiCache(Redis)用于缓存/会话
- 配置带 SSL 的 Application Load Balancer
- 任何需要生产级 AWS 托管的项目
架构模式
git push → GitHub Actions → Docker Build → ECR Push → ECS Fargate Deploy
↓
ALB (HTTPS + SSL)
↓
ECS Tasks (Containers)
↓ ↓ ↓
Database ElastiCache CloudWatch
↓
Grafana
AWS CLI 设置步骤
前提条件
# Install AWS CLI (macOS)
brew install awscli
# Configure credentials
aws configure
# AWS Access Key ID: <from IAM>
# AWS Secret Access Key: <from IAM>
# Default region: ap-south-1 (or your region)
# Default output: json
# Verify
aws sts get-caller-identity
步骤 1:创建 ECR 存储库
# Create repos for frontend and backend images
aws ecr create-repository --repository-name <app>-frontend --image-scanning-configuration scanOnPush=true
aws ecr create-repository --repository-name <app>-backend --image-scanning-configuration scanOnPush=true
# Login to ECR
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
步骤 2:创建 VPC 和网络
# Create VPC
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=<app>-vpc}]'
# Create public subnets (for ALB) — need 2 AZs minimum
aws ec2 create-subnet --vpc-id <vpc-id> --cidr-block 10.0.1.0/24 --availability-zone <region>a --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=<app>-public-1}]'
aws ec2 create-subnet --vpc-id <vpc-id> --cidr-block 10.0.2.0/24 --availability-zone <region>b --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=<app>-public-2}]'
# Create private subnets (for ECS + ElastiCache)
aws ec2 create-subnet --vpc-id <vpc-id> --cidr-block 10.0.3.0/24 --availability-zone <region>a --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=<app>-private-1}]'
aws ec2 create-subnet --vpc-id <vpc-id> --cidr-block 10.0.4.0/24 --availability-zone <region>b --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=<app>-private-2}]'
# Create Internet Gateway (for ALB)
aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=<app>-igw}]'
aws ec2 attach-internet-gateway --internet-gateway-id <igw-id> --vpc-id <vpc-id>
# Create NAT Gateway (for ECS Fargate to pull images + reach internet)
aws ec2 allocate-address --domain vpc # Get Elastic IP
aws ec2 create-nat-gateway --subnet-id <public-subnet-1-id> --allocation-id <eip-alloc-id>
# Route tables — public subnets → IGW, private subnets → NAT
aws ec2 create-route-table --vpc-id <vpc-id> # Public RT
aws ec2 create-route --route-table-id <public-rt-id> --destination-cidr-block 0.0.0.0/0 --gateway-id <igw-id>
aws ec2 associate-route-table --route-table-id <public-rt-id> --subnet-id <public-subnet-1-id>
aws ec2 associate-route-table --route-table-id <public-rt-id> --subnet-id <public-subnet-2-id>
aws ec2 create-route-table --vpc-id <vpc-id> # Private RT
aws ec2 create-route --route-table-id <private-rt-id> --destination-cidr-block 0.0.0.0/0 --nat-gateway-id <nat-gw-id>
aws ec2 associate-route-table --route-table-id <private-rt-id> --subnet-id <private-subnet-1-id>
aws ec2 associate-route-table --route-table-id <private-rt-id> --subnet-id <private-subnet-2-id>
步骤 3:安全组
# ALB security group — allow HTTPS from internet
aws ec2 create-security-group --group-name <app>-alb-sg --description "ALB SG" --vpc-id <vpc-id>
aws ec2 authorize-security-group-ingress --group-id <alb-sg-id> --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id <alb-sg-id> --protocol tcp --port 80 --cidr 0.0.0.0/0
# ECS security group — allow from ALB only
aws ec2 create-security-group --group-name <app>-ecs-sg --description "ECS SG" --vpc-id <vpc-id>
aws ec2 authorize-security-group-ingress --group-id <ecs-sg-id> --protocol tcp --port 3000 --source-group <alb-sg-id>
aws ec2 authorize-security-group-ingress --group-id <ecs-sg-id> --protocol tcp --port 8000 --source-group <alb-sg-id>
# ElastiCache security group — allow from ECS only
aws ec2 create-security-group --group-name <app>-redis-sg --description "Redis SG" --vpc-id <vpc-id>
aws ec2 authorize-security-group-ingress --group-id <redis-sg-id> --protocol tcp --port 6379 --source-group <ecs-sg-id>
步骤 4:ElastiCache(Redis)
# Create subnet group
aws elasticache create-cache-subnet-group \
--cache-subnet-group-name <app>-redis-subnet \
--cache-subnet-group-description "Redis subnets" \
--subnet-ids <private-subnet-1-id> <private-subnet-2-id>
# Create Redis cluster
aws elasticache create-cache-cluster \
--cache-cluster-id <app>-redis \
--cache-node-type cache.t3.micro \
--engine redis \
--num-cache-nodes 1 \
--cache-subnet-group-name <app>-redis-subnet \
--security-group-ids <redis-sg-id>
步骤 5:Application Load Balancer
# Create ALB
aws elbv2 create-load-balancer \
--name <app>-alb \
--subnets <public-subnet-1-id> <public-subnet-2-id> \
--security-groups <alb-sg-id> \
--scheme internet-facing \
--type application
# Create target groups
aws elbv2 create-target-group --name <app>-frontend-tg --protocol HTTP --port 3000 --vpc-id <vpc-id> --target-type ip --health-check-path /
aws elbv2 create-target-group --name <app>-backend-tg --protocol HTTP --port 8000 --vpc-id <vpc-id> --target-type ip --health-check-path /health
# SSL Certificate (via ACM)
aws acm request-certificate --domain-name <yourdomain.com> --validation-method DNS --subject-alternative-names "*.<yourdomain.com>"
# → Validate DNS via Route53 or your DNS provider
# HTTPS Listener with path-based routing
aws elbv2 create-listener \
--load-balancer-arn <alb-arn> \
--protocol HTTPS --port 443 \
--certificates CertificateArn=<cert-arn> \
--default-actions Type=forward,TargetGroupArn=<frontend-tg-arn>
# HTTP → HTTPS redirect
aws elbv2 create-listener \
--load-balancer-arn <alb-arn> \
--protocol HTTP --port 80 \
--default-actions Type=redirect,RedirectConfig='{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'
# Route /api/* to backend
aws elbv2 create-rule \
--listener-arn <https-listener-arn> \
--conditions Field=path-pattern,Values='/api/*' \
--priority 1 \
--actions Type=forward,TargetGroupArn=<backend-tg-arn>
步骤 6:ECS 集群 + 任务定义
# Create cluster
aws ecs create-cluster --cluster-name <app>-cluster
# Create CloudWatch log groups FIRST
aws logs create-log-group --log-group-name /ecs/<app>/frontend
aws logs create-log-group --log-group-name /ecs/<app>/backend
# Create ECS Task Execution Role (if not exists)
aws iam create-role --role-name ecsTaskExecutionRole --assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{"Effect": "Allow", "Principal": {"Service": "ecs-tasks.amazonaws.com"}, "Action": "sts:AssumeRole"}]
}'
aws iam attach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
任务定义 JSON(infra/task-definition.json):
{
"family": "<app>",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::<account>:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::<account>:role/ecsTaskRole",
"containerDefinitions": [
{
"name": "frontend",
"image": "<account>.dkr.ecr.<region>.amazonaws.com/<app>-frontend:latest",
"portMappings": [{"containerPort": 3000, "protocol": "tcp"}],
"environment": [
{"name": "NEXT_PUBLIC_API_URL", "value": "https://<domain>/api"}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/<app>/frontend",
"awslogs-region": "<region>",
"awslogs-stream-prefix": "ecs"
}
},
"essential": true
},
{
"name": "backend",
"image": "<account>.dkr.ecr.<region>.amazonaws.com/<app>-backend:latest",
"portMappings": [{"containerPort": 8000, "protocol": "tcp"}],
"environment": [
{"name": "MONGODB_URI", "value": "<mongodb-atlas-uri>"},
{"name": "REDIS_URL", "value": "redis://<elasticache-endpoint>:6379"},
{"name": "OPENAI_API_KEY", "value": "<key>"}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/<app>/backend",
"awslogs-region": "<region>",
"awslogs-stream-prefix": "ecs"
}
},
"essential": true
}
]
}
# Register task definition
aws ecs register-task-definition --cli-input-json file://infra/task-definition.json
# Create ECS service
aws ecs create-service \
--cluster <app>-cluster \
--service-name <app>-service \
--task-definition <app> \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[<private-subnet-1>,<private-subnet-2>],securityGroups=[<ecs-sg-id>],assignPublicIp=DISABLED}" \
--load-balancers "targetGroupArn=<frontend-tg-arn>,containerName=frontend,containerPort=3000" "targetGroupArn=<backend-tg-arn>,containerName=backend,containerPort=8000"
步骤 7:GitHub Actions CI/CD
.github/workflows/deploy.yml:
name: Deploy to AWS ECS
on:
push:
branches: [main]
env:
AWS_REGION: ap-south-1
ECR_FRONTEND: <app>-frontend
ECR_BACKEND: <app>-backend
ECS_CLUSTER: <app>-cluster
ECS_SERVICE: <app>-service
TASK_DEFINITION: <app>
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build & push frontend
run: |
docker build -t ${{ steps.ecr.outputs.registry }}/${{ env.ECR_FRONTEND }}:${{ github.sha }} -f frontend/Dockerfile frontend/
docker push ${{ steps.ecr.outputs.registry }}/${{ env.ECR_FRONTEND }}:${{ github.sha }}
- name: Build & push backend
run: |
docker build -t ${{ steps.ecr.outputs.registry }}/${{ env.ECR_BACKEND }}:${{ github.sha }} -f backend/Dockerfile backend/
docker push ${{ steps.ecr.outputs.registry }}/${{ env.ECR_BACKEND }}:${{ github.sha }}
- name: Download current task definition
run: |
aws ecs describe-task-definition --task-definition ${{ env.TASK_DEFINITION }} --query taskDefinition > task-def.json
- name: Update frontend image in task def
id: frontend-task
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-def.json
container-name: frontend
image: ${{ steps.ecr.outputs.registry }}/${{ env.ECR_FRONTEND }}:${{ github.sha }}
- name: Update backend image in task def
id: backend-task
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: ${{ steps.frontend-task.outputs.task-definition }}
container-name: backend
image: ${{ steps.ecr.outputs.registry }}/${{ env.ECR_BACKEND }}:${{ github.sha }}
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.backend-task.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
所需的 GitHub Secrets:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY
步骤 8:CloudWatch 监控
# Create metric alarms
aws cloudwatch put-metric-alarm \
--alarm-name <app>-high-cpu \
--metric-name CPUUtilization \
--namespace AWS/ECS \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--dimensions Name=ClusterName,Value=<app>-cluster Name=ServiceName,Value=<app>-service
aws cloudwatch put-metric-alarm \
--alarm-name <app>-high-memory \
--metric-name MemoryUtilization \
--namespace AWS/ECS \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--dimensions Name=ClusterName,Value=<app>-cluster Name=ServiceName,Value=<app>-service
# Create dashboard
aws cloudwatch put-dashboard --dashboard-name <app>-dashboard --dashboard-body '{
"widgets": [
{"type":"metric","properties":{"metrics":[["AWS/ECS","CPUUtilization","ClusterName","<app>-cluster","ServiceName","<app>-service"]],"period":300,"title":"CPU Utilization"}},
{"type":"metric","properties":{"metrics":[["AWS/ECS","MemoryUtilization","ClusterName","<app>-cluster","ServiceName","<app>-service"]],"period":300,"title":"Memory Utilization"}},
{"type":"metric","properties":{"metrics":[["AWS/ApplicationELB","RequestCount","LoadBalancer","<alb-id>"]],"period":60,"title":"Request Count"}},
{"type":"metric","properties":{"metrics":[["AWS/ApplicationELB","TargetResponseTime","LoadBalancer","<alb-id>"]],"period":60,"title":"Response Time"}},
{"type":"metric","properties":{"metrics":[["AWS/ApplicationELB","HTTPCode_Target_5XX_Count","LoadBalancer","<alb-id>"]],"period":60,"title":"5xx Errors"}}
]
}'
步骤 9:设置 Grafana
# Option A: Self-hosted Grafana on EC2
docker run -d -p 3000:3000 --name grafana grafana/grafana-oss
# Option B: Amazon Managed Grafana (recommended)
aws grafana create-workspace \
--account-access-type CURRENT_ACCOUNT \
--authentication-providers AWS_SSO \
--permission-type SERVICE_MANAGED \
--workspace-name <app>-grafana
Grafana 仪表板配置:
- 数据源:CloudWatch
- 面板:CPU、内存、请求数、响应时间、错误率、活动任务
- 警报规则:CPU > 80%、5xx > 10/分钟、响应时间 > 2 秒
Dockerfiles
前端 Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
后端 Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
docker-compose.yml(本地开发)
version: '3.8'
services:
frontend:
build: ./frontend
ports: ["3000:3000"]
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8000
depends_on: [backend]
backend:
build: ./backend
ports: ["8000:8000"]
environment:
- MONGODB_URI=${MONGODB_URI}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- REDIS_URL=redis://redis:6379
depends_on: [redis]
redis:
image: redis:7-alpine
ports: ["6379:6379"]
容易忽略的事项(至关重要)
-
NAT Gateway — 位于私有子网中的 ECS Fargate 无法拉取 ECR 镜像,除非使用 NAT 或 VPC 端点。可以添加 NAT Gateway(每月 32 美元),也可以创建 ECR VPC 端点(免费,但设置更繁琐)。
-
ECS 任务角色与执行角色 — 执行角色 = ECS Agent 拉取镜像并写入日志。任务角色 = 应用程序的 AWS 权限(S3、SES 等)。两者并不相同。
-
MongoDB Atlas 网络访问 — 在 MongoDB Atlas 中将 NAT Gateway 的弹性 IP 列入白名单,而不是使用 0.0.0.0/0。
-
运行状况检查 — ALB 运行状况检查必须在
/(前端)和/health(后端)返回 200。如果运行状况不佳,ECS 会不断终止并重启任务。 -
密钥管理 — 切勿将密钥放入任务定义 JSON。使用 AWS Secrets Manager 或 SSM Parameter Store,并在任务定义中通过
valueFrom引用。 -
Container Insights — 为 ECS 启用此功能,以便在 CloudWatch 中获取每个容器的指标:
aws ecs update-cluster-settings --cluster <app>-cluster --settings name=containerInsights,value=enabled -
自动扩缩 — 初次部署后添加:
aws application-autoscaling register-scalable-target \ --service-namespace ecs \ --scalable-dimension ecs:service:DesiredCount \ --resource-id service/<app>-cluster/<app>-service \ --min-capacity 1 --max-capacity 10 -
成本控制 — Fargate 价格:每 vCPU/小时 0.04048 美元 + 每 GB/小时 0.004445 美元。一个 0.5 vCPU + 1GB 的任务每月约 30 美元。2 个任务每月约 60 美元。NAT Gateway 每月另加 32 美元。
-
日志保留 — 为 CloudWatch 日志组设置保留期限,否则成本会不断增长:
aws logs put-retention-policy --log-group-name /ecs/<app>/backend --retention-in-days 30 -
滚动部署 — ECS 默认采用滚动更新。将
minimumHealthyPercent=50和maximumPercent=200设为相应值,以便在终止旧任务前启动新任务。
成本估算(ap-south-1)
| 服务 | 规格 | 月度成本 |
|---|---|---|
| ECS Fargate(2 个任务) | 每个 0.5 vCPU + 1GB | ~$30-60 |
| NAT Gateway | 数据处理 | ~$32 + 数据费 |
| ALB | 按小时 + LCU | ~$20-30 |
| ElastiCache | cache.t3.micro | ~$13 |
| CloudWatch | 日志 + 指标 | ~$5-15 |
| ECR | 镜像存储 | ~$1-3 |
| ACM(SSL) | 免费 | $0 |
| 总计 | ~$100-150/月 |
架构
输入项
| 名称 | 类型 | 必填 | 描述 |
|---|---|---|---|
app_name | string | 是 | 应用名称(用于所有 AWS 资源) |
aws_region | string | 是 | AWS 区域(例如,ap-south-1) |
aws_account_id | string | 是 | AWS 账户 ID |
domain | string | 否 | 用于 SSL 的自定义域名 |
frontend_port | number | 否 | 前端端口(默认值:3000) |
backend_port | number | 否 | 后端端口(默认值:8000) |
fargate_cpu | string | 否 | 任务 CPU(默认值:512) |
fargate_memory | string | 否 | 任务内存(默认值:1024) |
desired_count | number | 否 | 任务数量(默认值:2) |
输出项
| 名称 | 类型 | 描述 |
|---|---|---|
alb_dns | string | ALB DNS 名称(公共 URL) |
ecr_frontend_uri | string | 前端 ECR 仓库 URI |
ecr_backend_uri | string | 后端 ECR 仓库 URI |
cluster_name | string | ECS 集群名称 |
cloudwatch_dashboard | string | CloudWatch 控制面板 URL |
凭证
| 名称 | 来源 |
|---|---|
AWS_ACCESS_KEY_ID | IAM 用户 / GitHub Secrets |
AWS_SECRET_ACCESS_KEY | IAM 用户 / GitHub Secrets |
MONGODB_URI | MongoDB Atlas 连接字符串 |
OPENAI_API_KEY | OpenAI 控制面板 |
可组合使用
modal-deploy(替代方案:无服务器函数)design-website(部署前的前端设计)- 任何包含前端和后端容器的项目
费用
生产环境配置约为 100–150 美元/月(按 ap-south-1 区域价格计算)