A cost-optimized, reliable AWS infrastructure setup using Pulumi TypeScript for deploying applications with App Runner and a PostgreSQL database.
This infrastructure follows AWS Well-Architected Framework principles with focus on:
- Cost Optimization: Right-sized instances, auto-scaling, and pay-per-request models
- Reliability: Multi-AZ database, automated backups, comprehensive monitoring
- Operational Excellence: Infrastructure as Code, automated CI/CD, monitoring dashboards
For visuals, see docs/ARCHITECTURE.md.
- App Runner: Main application hosting with GitHub integration and VPC connectivity
- Public ingress enabled; WAF protection attached in production by default
- Private egress to VPC resources via VPC Connector (e.g., RDS)
- RDS PostgreSQL: Managed database with automatic backups
- CloudWatch: Monitoring, alerting, and dashboards
- Cost Management: Budgets, anomaly detection, and cost optimization
flowchart LR
U[Users] -->|HTTPS| CF{CloudFront?}
CF -- yes --> W[WAFv2]
CF -- no --> W
W --> AR[App Runner]
pulumi-iac-aws/ ├── environments/ # Environment-specific configurations │ ├── dev/ # Development environment │ ├── staging/ # Staging environment │ └── prod/ # Production environment ├── modules/ # Reusable infrastructure modules │ ├── networking/ # VPC, subnets, routing │ ├── database/ # RDS PostgreSQL │ ├── compute/ # App Runner services │ └── monitoring/ # CloudWatch, alarms, budgets ├── shared/ # Shared configuration and types ├── .github/workflows/ # CI/CD pipeline └── docs/ # Additional documentation
## Setup Resources
### setup-files
- Houses reusable templates referenced during onboarding (e.g., `deploy.yml` for GitHub Actions, baseline IAM policies).
- None of these templates are consumed directly by Pulumi—copy them into your GitHub repository or IAM workflows and tailor as needed.
### setup-scripts
- Shell utilities that bootstrap IAM users, validate prerequisites, deploy stacks, and configure GitHub Actions secrets.
- Start with `interactive-setup.sh` for a guided flow or run `validate-setup.sh`, `create-iac-user.sh`, and `get-github-vars.sh` individually.
- See `setup-scripts/README.md` for prerequisites, sample commands, and troubleshooting guidance.
## Quick Start
### Prerequisites
1. **AWS Account**: Active AWS account with appropriate permissions
2. **AWS CLI**: [Install and configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
3. **Pulumi**: [Install Pulumi CLI](https://www.pulumi.com/docs/install/)
4. **Node.js**: Version 22 or later
5. **Docker**: For building container images for App Runner
6. **GitHub Repository**: Used by CI/CD to build and push container images
### 1. Setup
```bash
# Clone the repository
git clone <your-repo-url>
cd pulumi-iac-aws
# Install dependencies
npm install
# Build TypeScript
npm run build
# Login to Pulumi (create free account if needed)
pulumi login
# Create stacks for each environment
pulumi stack init dev --cwd environments/dev
pulumi stack init staging --cwd environments/staging
pulumi stack init prod --cwd environments/prodcd environments/dev
# Set required configuration
pulumi config set appName "your-app-name"
pulumi config set githubOrg "your-github-org"
pulumi config set githubRepo "your-service-repo"
pulumi config set --secret dbPassword "your-secure-password"
pulumi config set alertEmail "your-email@example.com"
# Optional configuration
pulumi config set apiBaseUrl "https://api.example.com"cd ../staging
# Set configuration for staging
pulumi config set appName "your-app-name"
pulumi config set githubOrg "your-github-org"
pulumi config set githubRepo "your-service-repo"
pulumi config set --secret dbPassword "your-staging-password"
pulumi config set alertEmail "your-email@example.com"cd ../prod
# Set configuration for production
pulumi config set appName "your-app-name"
pulumi config set githubOrg "your-github-org"
pulumi config set githubRepo "your-service-repo"
pulumi config set --secret dbPassword "your-production-password"
pulumi config set alertEmail "your-email@example.com" # REQUIRED for production
# Production-specific settings
pulumi config set enableNewFeature false
pulumi config set sentryDsn "your-sentry-dsn" # Optionalcd environments/dev
pulumi upcd environments/staging
pulumi upcd environments/prod
pulumi preview # Always preview production changes first
pulumi up # Deploy after review- Database:
db.t3.micro(~$13/month) - App Runner: Scales to zero, pay-per-request
- Storage: 20GB with auto-scaling to 50GB
- Estimated Cost: ~$20–$35/month
- Networking: Single AZ, fck‑nat enabled, no NAT Gateway baseline
- Database:
db.t4g.micro, 20 GB gp3 (auto up to 50 GB) - App Runner: 0.25 vCPU / 0.5 GB, minSize 0, maxSize 2, maxConcurrency ~10
- WAF/CloudFront: Off by default (can be enabled for testing)
- Estimated Cost: ~$30–$60/month (mostly idle)
- Database:
db.t4g.smallMulti-AZ (~$58/month) - App Runner: Optimized concurrency and scaling
- Backups: Automated with lifecycle policies
- Estimated Cost: $80-120/month
- Monthly budgets with 80% and 100% alerts
- Cost anomaly detection
- Resource tagging for cost allocation
Each environment includes dashboards monitoring:
- App Runner: Request count, response time, active instances
- RDS: CPU utilization, connections, storage
- Costs: Estimated charges and budget status
Staging and production include alerts; production uses tighter SLOs:
- SLOs: App Runner p95 response time (>1s), 5xx error rate (>1%)
- Database CPU (>80%)
- Storage space (<2GB free)
-
Private subnets only
-
Security groups with minimal access
-
Encryption at rest enabled
-
Automated backups with point-in-time recovery
-
App Runner runs inside your VPC via a VPC Connector; public ingress, private egress via SGs
-
IAM roles scoped to minimum required actions (logs access limited to function log groups; X-Ray only for App Runner)
-
Runtime secrets via SSM Parameter Store (optional) with IAM limited to specified parameter paths
-
WAFv2 WebACL attached to App Runner (prod by default); optional CloudFront in front for edge protections and caching
- VPC with public/private subnet separation
- Egress via NAT/fck‑nat and/or VPC endpoints for outbound access
- Security groups instead of NACLs for simplicity
.github/workflows/ci.ymlruns TypeScript build and unit tests on PRs and on pushes tomain/master.- Add a separate deploy workflow if you want automated
pulumi upon merges; this repo intentionally keeps deploy manual.
Required secrets for a deploy workflow (if you add one): AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, PULUMI_ACCESS_TOKEN.
| Feature | Development | Staging (lean) | Production |
|---|---|---|---|
| Database Instance | db.t3.micro | db.t4g.micro | db.t4g.small |
| Multi-AZ | No | No | Yes |
| App Runner Min Size | 0 | 0 | 1 |
| App Runner Max Size | 2 | 2 | 5 |
| CPU/Memory | 0.25/0.5 | 0.25/0.5 | 0.5/1 |
| NAT/fck‑nat | fck‑nat | fck‑nat | NAT |
| Backup Retention | 1 day | 1 day | 7 days |
| Monitoring | Basic | Basic | Full |
| Cost Budget | $50/month | $50/month | $200/month |
Edit the App Runner configuration in each environment:
// In environments/{env}/index.ts
const appService = new AppRunnerService(`${appName}-${environment}-app`, {
// Modify these values as needed
maxConcurrency: 25,
maxSize: 5,
minSize: 1,
cpu: "1 vCPU", // Upgrade if needed
memory: "2 GB", // Upgrade if needed
});Edit the RDS configuration in each environment:
// In environments/{env}/index.ts
const dbInstance = new rds.Instance(`${appName}-${environment}-db`, {
instanceClass: "db.t4g.small", // Upgrade if needed
allocatedStorage: 20, // Increase if needed
maxAllocatedStorage: 100, // Increase if needed
backupRetentionPeriod: 7, // Adjust as needed
});- Weekly: Review cost dashboard and optimize resources
- Monthly: Update dependencies and Pulumi version
- Quarterly: Review and test disaster recovery procedures
- Monitor storage usage and adjust auto-scaling limits
- Review query performance with Performance Insights
- Test backup restoration procedures
- Review IAM permissions and remove unused roles
- Monitor security alerts in AWS Security Hub
# Check App Runner logs
aws apprunner list-services
aws apprunner describe-service --service-arn <service-arn>
# Confirm a container image is available in ECR
aws ecr describe-images --repository-name <repository-name>- Check AWS CloudWatch logs for detailed error messages
- Review Pulumi state with
pulumi stack --show-ids - Use AWS CLI to inspect resources directly
- Point-in-time Recovery: Available for last 7 days
- Automated Snapshots: Daily snapshots with 7-day retention
- Cross-region Backups: Optional; not configured by default
- App Runner: Automatically handles instance failures
- Infrastructure: Recreate from Pulumi code
- Database: Upgrade to larger instance class
- App Runner: Increase CPU/memory allocation
- App Runner: Adjust min/max size and concurrency settings
- Database: Scale storage automatically; consider read replicas for read-heavy workloads
- Monitor CloudWatch metrics before scaling decisions
- Use cost calculator to estimate scaling costs
- Consider reserved instances for predictable workloads
- WAF (App Runner): enabled by default in production. To override per environment:
pulumi config set enableWaf true # or false- CloudFront (optional): place a distribution in front of App Runner for edge protections/caching:
pulumi config set enableCloudFront truepulumi config set enableWaf true # or false- CloudFront (optional): place a distribution in front of App Runner for edge protections/caching:
pulumi config set enableCloudFront truepulumi config set enableCloudFront true