AWS setup
SmartDeploy uses AWS for CodeBuild + ECR image builds, ECS Fargate runtime deployments, an Application Load Balancer (ALB) for HTTPS and custom-domain routing, STS for identity checks, and optionally ACM for TLS certificates on the ALB.
Use this guide together with:
- Custom domains — deployment hostnames and Vercel DNS
- Self-hosting — running SmartDeploy itself on EC2 (swap, SSL, Nginx)
1. Create an IAM user
- Open IAM → Users → Create user.
- Username:
smartdeploy-service(or any name you prefer). - Select Provide user access to the AWS Management Console only if you want console access; it is not required for API access keys.
- Click Next.
2. Attach a custom policy
Create a Customer managed policy with the JSON below, then attach it to the user.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EC2",
"Effect": "Allow",
"Action": [
"ec2:RunInstances",
"ec2:TerminateInstances",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:Describe*",
"ec2:CreateTags",
"ec2:CreateSecurityGroup",
"ec2:DeleteSecurityGroup",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:RevokeSecurityGroupIngress",
"ec2:RevokeSecurityGroupEgress",
"ec2:CreateKeyPair",
"ec2:CreateLaunchTemplate",
"ec2:DeleteLaunchTemplate"
],
"Resource": "*"
},
{
"Sid": "ALB",
"Effect": "Allow",
"Action": [
"elasticloadbalancingv2:*"
],
"Resource": "*"
},
{
"Sid": "SSM",
"Effect": "Allow",
"Action": [
"ssm:SendCommand",
"ssm:GetCommandInvocation",
"ssm:DescribeInstanceInformation"
],
"Resource": "*"
},
{
"Sid": "CodeBuildECR",
"Effect": "Allow",
"Action": [
"codebuild:CreateProject",
"codebuild:UpdateProject",
"codebuild:StartBuild",
"codebuild:BatchGetBuilds",
"codebuild:BatchGetProjects",
"ecr:GetAuthorizationToken",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:CreateRepository",
"ecr:DescribeRepositories",
"ecr:BatchCheckLayerAvailability"
],
"Resource": "*"
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:GetLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
},
{
"Sid": "IAM",
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:CreateRole",
"iam:PassRole",
"iam:AttachRolePolicy",
"iam:CreateInstanceProfile",
"iam:AddRoleToInstanceProfile",
"iam:GetInstanceProfile",
"iam:CreateServiceLinkedRole"
],
"Resource": "*"
},
{
"Sid": "STS",
"Effect": "Allow",
"Action": "sts:GetCallerIdentity",
"Resource": "*"
}
]
}
Why wildcards? The app creates and discovers resources dynamically (security groups, listeners, target groups, CodeBuild projects). You can narrow
Resourceto your account or region if your organization requires it.
3. Access keys and environment variables
3.1 Long-lived access keys (local dev, Docker, non-AWS hosts)
- Open the user → Security credentials → Create access key.
- Use case: Application running outside AWS.
- Copy the Access key ID and Secret access key (shown once).
In .env (see also .env.example):
| Variable | Description |
|---|---|
AWS_ACCESS_KEY_ID | Access key ID |
AWS_SECRET_ACCESS_KEY | Secret access key |
AWS_REGION | Region for EC2, CodeBuild, ECR, ALB (e.g. us-west-2) |
USE_CODEBUILD | true (default) to build images in CodeBuild and push to ECR |
DEPLOYMENT_ACM_CERTIFICATE_ARN | Optional. ACM certificate ARN in the same region as AWS_REGION for ALB HTTPS |
DOCKERHUB_USERNAME / DOCKERHUB_TOKEN | Optional. Reduces Docker Hub anonymous rate limits during CodeBuild pulls |
STATIC_SITE_BUCKET | Optional. S3 bucket for static_build (no container start command) deploys: CodeBuild syncs build output here. |
STATIC_SITE_PUBLIC_BASE_URL | Required when using static S3 deploys. Public site URL (e.g. https://d123.cloudfront.net or your domain). |
STATIC_SITE_KEY_PREFIX | Optional. Key prefix inside the bucket (defaults derived from repo/service). |
STATIC_SITE_CLOUDFRONT_DISTRIBUTION_ID | Optional. When set, CodeBuild runs a CloudFront invalidation after sync. |
When STATIC_SITE_BUCKET and STATIC_SITE_PUBLIC_BASE_URL are set, sd-artifacts scans with deploy_shape: static or static_build (no start command) use CodeBuild → S3 instead of ECR/ECS.
| Variable | Description |
|---|---|
ECS_CLUSTER_NAME | ECS cluster for Fargate services (e.g. smart-deploy-cluster) |
ECS_SUBNET_IDS | Comma-separated subnet IDs (≥2 AZs recommended; used for Fargate and the shared ALB) |
ECS_SECURITY_GROUP_IDS | Comma-separated security group(s) for tasks; Smart Deploy opens ingress from the ALB SG at deploy time |
ECS_EXECUTION_ROLE_ARN | Task execution role (ECR pull + CloudWatch Logs) |
ECS_ASSIGN_PUBLIC_IP | ENABLED for public subnets (default from Terraform); DISABLED for private subnets with NAT |
ECS_LOG_GROUP | Optional. Defaults to /ecs/smartdeploy-railpack |
ECS_TASK_CPU / ECS_TASK_MEMORY | Optional Fargate sizing (defaults 512 / 1024) |
Container deploys (Railpack server, existing_docker) require the ECS variables above. Static deploys require the STATIC_SITE_* variables.
3.2 Platform infrastructure (Terraform)
Instead of creating the S3 bucket, CloudFront distribution, ECS cluster, and Fargate networking by hand, use the Terraform stack:
cd infra/smart-deploy-platform
cp terraform.tfvars.example terraform.tfvars
terraform init && terraform apply
terraform output -raw smart_deploy_env_snippet
Paste the snippet into .env. If you already created the bucket or cluster manually, set create_s3_bucket = false and/or create_ecs_cluster = false in terraform.tfvars (see infra/smart-deploy-platform/README.md).
If the Next.js app and worker run on EC2, attach an IAM role with the same policy instead of embedding keys. Leave AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY unset; the AWS SDK uses the instance metadata role automatically.
- IAM → Roles → Create role → AWS service → EC2.
- Attach the custom policy from §2.
- EC2 → instance → Actions → Security → Modify IAM role → select the role.
Details: Self-hosting.
4. HTTPS on the ALB (ACM)
- Open ACM in the same region as
AWS_REGION. - Request a public certificate (wildcard or hostname you will use).
- Complete DNS validation.
- Set
DEPLOYMENT_ACM_CERTIFICATE_ARNin.env.
SmartDeploy creates an HTTPS (443) listener and can redirect HTTP to HTTPS. More context: Custom domains.
5. Optional: AWS Bedrock (LLM)
To use Bedrock instead of or alongside Gemini, add credentials that include bedrock:InvokeModel (same IAM user or a dedicated user):
AWS_BEDROCK_ACCESS_KEY_ID=AKIA...
AWS_BEDROCK_SECRET_ACCESS_KEY=...
BEDROCK_MODEL_ID=anthropic.claude-opus-4-5-v1:0
Extend the IAM policy with Bedrock invoke permissions if you use a separate key.
Troubleshooting
| Error | Fix |
|---|---|
| ECS or shared-networking permission denied | Confirm the IAM policy includes the required ECS, ELBv2, EC2 networking, and IAM actions. |
ec2:CreateTags denied | Include ec2:CreateTags in the policy (see §2). |
ALB / DEPLOYMENT_ACM_CERTIFICATE_ARN | Certificate must be issued (not pending) and in the same region as the deployment. |
| ECS rollout or task startup issues | Check ECS service events, task logs, and execution-role permissions. |
| CodeBuild “role does not exist” | First deploy creates the CodeBuild service role; verify the IAM block in §2 allows role creation. |
| Docker Hub 429 in CodeBuild | Set DOCKERHUB_USERNAME and DOCKERHUB_TOKEN (read-only token). |
Security tips
- Do not use the AWS account root user for application keys.
- Rotate access keys on a schedule.
- Prefer an EC2 instance role over static keys when self-hosting.
- Enable CloudTrail for API auditing.