Install
$ agentstack add skill-san-npm-skills-ws-aws-production-deploy ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
AWS Production Deploy
Production-grade AWS infrastructure patterns. Not hello-world — real modules you'd ship to production with VPC isolation, ECS Fargate, RDS, CloudFront, and full CI/CD.
Architecture Overview
┌─────────────┐
│ Route 53 │
└──────┬──────┘
│
┌──────▼──────┐
│ CloudFront │──── S3 (static assets)
└──────┬──────┘
│
┌──────▼──────┐
│ ALB │ (public subnets)
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌────▼───┐ ┌────▼───┐ ┌────▼───┐
│ECS Task│ │ECS Task│ │ECS Task│ (private subnets)
└────┬───┘ └────┬───┘ └────┬───┘
│ │ │
└────────────┼────────────┘
│
┌──────▼──────┐
│ RDS │ (isolated subnets)
│ Primary + │
│ Read Replica│
└─────────────┘
1. VPC with Proper Network Isolation — Terraform
Most tutorials give you a flat VPC. Production needs three tiers: public (ALB only), private (compute), isolated (database). NAT Gateway per AZ for HA.
# modules/vpc/main.tf
variable "project" { type = string }
variable "environment" { type = string }
variable "vpc_cidr" { default = "10.0.0.0/16" }
variable "az_count" { default = 3 }
data "aws_availability_zones" "available" {
state = "available"
}
locals {
azs = slice(data.aws_availability_zones.available.names, 0, var.az_count)
public_cidrs = [for i in range(var.az_count) : cidrsubnet(var.vpc_cidr, 4, i)]
private_cidrs = [for i in range(var.az_count) : cidrsubnet(var.vpc_cidr, 4, i + var.az_count)]
isolated_cidrs = [for i in range(var.az_count) : cidrsubnet(var.vpc_cidr, 4, i + var.az_count * 2)]
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "${var.project}-${var.environment}", Environment = var.environment }
}
# VPC Flow Logs — mandatory for debugging and compliance
resource "aws_flow_log" "main" {
vpc_id = aws_vpc.main.id
traffic_type = "ALL"
log_destination_type = "cloud-watch-logs"
log_destination = aws_cloudwatch_log_group.flow_logs.arn
iam_role_arn = aws_iam_role.flow_logs.arn
}
resource "aws_cloudwatch_log_group" "flow_logs" {
name = "/vpc/flow-logs/${var.project}-${var.environment}"
retention_in_days = 30
}
resource "aws_iam_role" "flow_logs" {
name = "${var.project}-${var.environment}-flow-logs"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole", Effect = "Allow"
Principal = { Service = "vpc-flow-logs.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy" "flow_logs" {
role = aws_iam_role.flow_logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents","logs:DescribeLogGroups","logs:DescribeLogStreams"]
Resource = "*"
}]
})
}
# Public subnets — ALB lives here
resource "aws_subnet" "public" {
count = var.az_count
vpc_id = aws_vpc.main.id
cidr_block = local.public_cidrs[count.index]
availability_zone = local.azs[count.index]
map_public_ip_on_launch = true
tags = { Name = "${var.project}-${var.environment}-public-${local.azs[count.index]}" }
}
# Private subnets — ECS tasks, NAT for outbound
resource "aws_subnet" "private" {
count = var.az_count
vpc_id = aws_vpc.main.id
cidr_block = local.private_cidrs[count.index]
availability_zone = local.azs[count.index]
tags = { Name = "${var.project}-${var.environment}-private-${local.azs[count.index]}" }
}
# Isolated subnets — RDS, ElastiCache. NO internet access.
resource "aws_subnet" "isolated" {
count = var.az_count
vpc_id = aws_vpc.main.id
cidr_block = local.isolated_cidrs[count.index]
availability_zone = local.azs[count.index]
tags = { Name = "${var.project}-${var.environment}-isolated-${local.azs[count.index]}" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
}
# One NAT per AZ for production HA (cross-AZ NAT is a single point of failure
# AND incurs cross-AZ data charges). Single NAT for dev cuts the per-NAT hourly
# fee — roughly one gateway's hourly + data cost; verify current NAT Gateway
# pricing for your region at https://aws.amazon.com/vpc/pricing/.
resource "aws_eip" "nat" {
count = var.environment == "production" ? var.az_count : 1
domain = "vpc"
}
resource "aws_nat_gateway" "main" {
count = var.environment == "production" ? var.az_count : 1
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route { cidr_block = "0.0.0.0/0"; gateway_id = aws_internet_gateway.main.id }
}
resource "aws_route_table_association" "public" {
count = var.az_count
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
count = var.environment == "production" ? var.az_count : 1
vpc_id = aws_vpc.main.id
route { cidr_block = "0.0.0.0/0"; nat_gateway_id = aws_nat_gateway.main[count.index].id }
}
resource "aws_route_table_association" "private" {
count = var.az_count
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[var.environment == "production" ? count.index : 0].id
}
# Isolated — no internet route at all
resource "aws_route_table" "isolated" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table_association" "isolated" {
count = var.az_count
subnet_id = aws_subnet.isolated[count.index].id
route_table_id = aws_route_table.isolated.id
}
output "vpc_id" { value = aws_vpc.main.id }
output "public_subnet_ids" { value = aws_subnet.public[*].id }
output "private_subnet_ids" { value = aws_subnet.private[*].id }
output "isolated_subnet_ids" { value = aws_subnet.isolated[*].id }
2. ECS Fargate with Auto-Scaling
# modules/ecs/main.tf
variable "project" { type = string }
variable "environment" { type = string }
variable "vpc_id" { type = string }
variable "private_subnet_ids" { type = list(string) }
variable "public_subnet_ids" { type = list(string) }
variable "container_image" { type = string }
variable "container_port" { default = 3000 }
variable "cpu" { default = 512 }
variable "memory" { default = 1024 }
variable "desired_count" { default = 2 }
variable "min_count" { default = 2 }
variable "max_count" { default = 10 }
variable "health_check_path" { default = "/health" }
variable "secrets_arn" { type = string }
variable "certificate_arn" { type = string }
resource "aws_ecs_cluster" "main" {
name = "${var.project}-${var.environment}"
setting { name = "containerInsights"; value = "enabled" }
}
resource "aws_cloudwatch_log_group" "app" {
name = "/ecs/${var.project}-${var.environment}/app"
retention_in_days = 30
}
# Task execution role — pulls images, writes logs, reads secrets
resource "aws_iam_role" "task_execution" {
name = "${var.project}-${var.environment}-task-exec"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Action = "sts:AssumeRole", Effect = "Allow", Principal = { Service = "ecs-tasks.amazonaws.com" } }]
})
}
resource "aws_iam_role_policy_attachment" "task_execution" {
role = aws_iam_role.task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_iam_role_policy" "task_execution_secrets" {
role = aws_iam_role.task_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Action = ["secretsmanager:GetSecretValue"], Resource = [var.secrets_arn] }]
})
}
# Task role — what YOUR CODE runs as. Least privilege.
resource "aws_iam_role" "task" {
name = "${var.project}-${var.environment}-task"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Action = "sts:AssumeRole", Effect = "Allow", Principal = { Service = "ecs-tasks.amazonaws.com" } }]
})
}
resource "aws_iam_role_policy" "task" {
role = aws_iam_role.task.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{ Effect = "Allow", Action = ["s3:GetObject","s3:PutObject"], Resource = ["arn:aws:s3:::${var.project}-${var.environment}-uploads/*"] },
{ Effect = "Allow", Action = ["xray:PutTraceSegments","xray:PutTelemetryRecords"], Resource = ["*"] },
# Required for ECS Exec (enable_execute_command below). Without these four
# SSM Messages actions on the TASK role, `aws ecs execute-command` fails with
# "execute command failed because execute command was not enabled".
{ Effect = "Allow",
Action = ["ssmmessages:CreateControlChannel","ssmmessages:CreateDataChannel","ssmmessages:OpenControlChannel","ssmmessages:OpenDataChannel"],
Resource = ["*"] }
]
})
}
data "aws_region" "current" {}
resource "aws_ecs_task_definition" "app" {
family = "${var.project}-${var.environment}"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.cpu
memory = var.memory
execution_role_arn = aws_iam_role.task_execution.arn
task_role_arn = aws_iam_role.task.arn
container_definitions = jsonencode([
{
name = "app"
image = var.container_image
portMappings = [{ containerPort = var.container_port, protocol = "tcp" }]
secrets = [
{ name = "DATABASE_URL", valueFrom = "${var.secrets_arn}:DATABASE_URL::" },
{ name = "REDIS_URL", valueFrom = "${var.secrets_arn}:REDIS_URL::" }
]
environment = [
{ name = "NODE_ENV", value = var.environment },
{ name = "PORT", value = tostring(var.container_port) }
]
logConfiguration = {
logDriver = "awslogs"
options = { "awslogs-group" = aws_cloudwatch_log_group.app.name, "awslogs-region" = data.aws_region.current.name, "awslogs-stream-prefix" = "app" }
}
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:${var.container_port}/health || exit 1"]
interval = 30, timeout = 5, retries = 3, startPeriod = 60
}
},
{
name = "xray-daemon", image = "amazon/aws-xray-daemon:latest"
cpu = 32, memory = 64, essential = false
portMappings = [{ containerPort = 2000, protocol = "udp" }]
logConfiguration = { logDriver = "awslogs", options = { "awslogs-group" = aws_cloudwatch_log_group.app.name, "awslogs-region" = data.aws_region.current.name, "awslogs-stream-prefix" = "xray" } }
}
])
}
# Security groups
resource "aws_security_group" "alb" {
name_prefix = "${var.project}-${var.environment}-alb-"
vpc_id = var.vpc_id
ingress { from_port = 443; to_port = 443; protocol = "tcp"; cidr_blocks = ["0.0.0.0/0"] }
ingress { from_port = 80; to_port = 80; protocol = "tcp"; cidr_blocks = ["0.0.0.0/0"] }
egress { from_port = 0; to_port = 0; protocol = "-1"; cidr_blocks = ["0.0.0.0/0"] }
lifecycle { create_before_destroy = true }
}
resource "aws_security_group" "ecs" {
name_prefix = "${var.project}-${var.environment}-ecs-"
vpc_id = var.vpc_id
ingress { from_port = var.container_port; to_port = var.container_port; protocol = "tcp"; security_groups = [aws_security_group.alb.id] }
egress { from_port = 0; to_port = 0; protocol = "-1"; cidr_blocks = ["0.0.0.0/0"] }
lifecycle { create_before_destroy = true }
}
# ALB
resource "aws_lb" "main" {
name = "${var.project}-${var.environment}"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
enable_deletion_protection = var.environment == "production"
drop_invalid_header_fields = true
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = var.certificate_arn
default_action { type = "forward"; target_group_arn = aws_lb_target_group.blue.arn }
lifecycle { ignore_changes = [default_action] }
}
resource "aws_lb_listener" "http_redirect" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action { type = "redirect"; redirect { port = "443"; protocol = "HTTPS"; status_code = "HTTP_301" } }
}
# --- Two target groups for CodeDeploy blue/green ---
# CodeDeploy swaps the production listener between these two groups. Both must
# exist up front; the running service is attached to exactly one at a time and
# CodeDeploy shifts traffic to the other on each deploy.
resource "aws_lb_target_group" "blue" {
name_prefix = "blue-"
port = var.container_port
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
deregistration_delay = 30
health_check { path = var.health_check_path; healthy_threshold = 2; unhealthy_threshold = 3; timeout = 5; interval = 15; matcher = "200" }
lifecycle { create_before_destroy = true }
}
resource "aws_lb_target_group" "green" {
name_prefix = "green-"
port = var.container_port
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
deregistration_delay = 30
health_check { path = var.health_check_path; healthy_threshold = 2; unhealthy_threshold = 3; timeout = 5; interval = 15; matcher = "200" }
lifecycle { create_before_destroy = true }
}
# Test listener on :8443 — lets CodeDeploy validate the green stack before it
# receives production traffic. Reuse the prod cert or a separate test cert.
resource "aws_lb_listener" "test" {
load_balancer_arn = aws_lb.main.arn
port = 8443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = var.certificate_arn
default_action { type = "forward"; target_group_arn = aws_lb_target_group.green.arn }
lifecycle { ignore_changes = [default_action] }
}
# Allow the test-listener port through the ALB and into the tasks.
resource "aws_security_group_rule" "alb_test_ingress" {
type = "ingress"
security_group_id = aws_security_group.alb.id
from_port = 8443
to_port = 8443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# ECS Service — CodeDeploy-controlled blue/green with auto-rollback.
# NOTE: deployment_controller = CODE_DEPLOY is INCOMPATIBLE with the ECS
# deployment_circuit_breaker / deployment_configuration blocks; rollback is
# configured on the CodeDeploy deployment group instead (see section 2a). If you
# prefer plain ECS rolling deploys, swap to the variant in section 2b — do NOT
# mix the two.
resource "aws_ecs_service" "app" {
name = "${var.project}-${var.environment}"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = var.desired_count
launch_type = "FARGATE"
enable_execute_command = true
deployment_controller { type = "CODE_DEPLOY" }
network_configuration {
subnets = var.private_subnet_ids
security_groups = [aws_security_group.ecs.id]
assign_public_ip = false
}
load_balancer {
targ
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [san-npm](https://github.com/san-npm)
- **Source:** [san-npm/skills-ws](https://github.com/san-npm/skills-ws)
- **License:** MIT
- **Homepage:** https://skills-ws.vercel.app
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.