Setting up AWS App Runner with Terraform
Learn how to deploy and manage containerized web applications using AWS App Runner and Terraform, including auto-scaling, monitoring, and security best practices
Setting up AWS App Runner with Terraform
AWS App Runner is a fully managed service that makes it easy to deploy containerized web applications and APIs. This guide demonstrates how to set up and manage App Runner using Terraform.
Video Tutorial
Learn more about managing AWS App Runner with Terraform in this comprehensive video tutorial:
Prerequisites
- AWS CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Docker installed locally
- Container image in Amazon ECR or source code in GitHub
Project Structure
terraform-apprunner/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ └── apprunner/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── config/
└── auto_scaling.json
App Runner Configuration
Create modules/apprunner/main.tf
:
# App Runner Service
resource "aws_apprunner_service" "main" {
service_name = "${var.project_name}-service"
source_configuration {
authentication_configuration {
access_role_arn = aws_iam_role.access_role.arn
}
image_repository {
image_configuration {
port = var.container_port
runtime_environment_variables = {
ENV = var.environment
PORT = var.container_port
}
start_command = var.start_command
}
image_identifier = "${var.ecr_repository_url}:${var.image_tag}"
image_repository_type = "ECR"
}
auto_deployments_enabled = true
}
instance_configuration {
cpu = var.cpu
memory = var.memory
instance_role_arn = aws_iam_role.instance_role.arn
}
health_check_configuration {
healthy_threshold = 3
interval = 5
path = "/health"
protocol = "HTTP"
timeout = 2
unhealthy_threshold = 3
}
auto_scaling_configuration_arn = aws_apprunner_auto_scaling_configuration_version.main.arn
network_configuration {
egress_configuration {
egress_type = "VPC"
vpc_connector_arn = aws_apprunner_vpc_connector.main.arn
}
}
tags = merge(
var.tags,
{
Name = "${var.project_name}-service"
}
)
}
# Auto Scaling Configuration
resource "aws_apprunner_auto_scaling_configuration_version" "main" {
auto_scaling_configuration_name = "${var.project_name}-auto-scaling"
max_concurrency = 100
max_size = 25
min_size = 1
tags = merge(
var.tags,
{
Name = "${var.project_name}-auto-scaling"
}
)
}
# VPC Connector
resource "aws_apprunner_vpc_connector" "main" {
vpc_connector_name = "${var.project_name}-connector"
subnets = var.subnet_ids
security_groups = [aws_security_group.apprunner.id]
tags = merge(
var.tags,
{
Name = "${var.project_name}-connector"
}
)
}
# Security Group
resource "aws_security_group" "apprunner" {
name = "${var.project_name}-apprunner"
description = "Security group for App Runner VPC connector"
vpc_id = var.vpc_id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "${var.project_name}-apprunner"
}
)
}
# IAM Roles
resource "aws_iam_role" "access_role" {
name = "${var.project_name}-access-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "build.apprunner.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "access_role" {
role = aws_iam_role.access_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess"
}
resource "aws_iam_role" "instance_role" {
name = "${var.project_name}-instance-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "tasks.apprunner.amazonaws.com"
}
}
]
})
}
# Custom Domain
resource "aws_apprunner_custom_domain_association" "main" {
domain_name = var.domain_name
service_arn = aws_apprunner_service.main.arn
}
# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "apprunner" {
name = "/aws/apprunner/${var.project_name}"
retention_in_days = 30
tags = merge(
var.tags,
{
Name = "${var.project_name}-logs"
}
)
}
Monitoring and Alerts
- CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "response_time" {
alarm_name = "${var.project_name}-response-time"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "RequestLatency"
namespace = "AWS/AppRunner"
period = "300"
statistic = "Average"
threshold = "1000"
alarm_description = "Response time is too high"
alarm_actions = [aws_sns_topic.alerts.arn]
dimensions = {
ServiceName = aws_apprunner_service.main.service_name
}
}
resource "aws_cloudwatch_metric_alarm" "error_rate" {
alarm_name = "${var.project_name}-error-rate"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "HTTP5xxErrorRate"
namespace = "AWS/AppRunner"
period = "300"
statistic = "Average"
threshold = "5"
alarm_description = "Error rate is too high"
alarm_actions = [aws_sns_topic.alerts.arn]
dimensions = {
ServiceName = aws_apprunner_service.main.service_name
}
}
Observability Configuration
- X-Ray Tracing
resource "aws_iam_role_policy_attachment" "xray" {
role = aws_iam_role.instance_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
}
resource "aws_apprunner_service" "with_tracing" {
# ... other configuration ...
observability_configuration {
observability_enabled = true
trace_configuration {
vendor = "AWSXRAY"
}
}
}
- Application Insights
resource "aws_applicationinsights_application" "main" {
resource_group_name = aws_resourcegroups_group.main.name
auto_config_enabled = true
}
resource "aws_resourcegroups_group" "main" {
name = "${var.project_name}-group"
resource_query {
query = jsonencode({
ResourceTypeFilters = ["AWS::AppRunner::Service"]
TagFilters = [
{
Key = "Project"
Values = [var.project_name]
}
]
})
}
}
Integration Examples
- GitHub Source
resource "aws_apprunner_service" "github" {
service_name = "${var.project_name}-github"
source_configuration {
authentication_configuration {
connection_arn = aws_apprunner_connection.github.arn
}
code_repository {
code_configuration {
code_configuration_values {
build_command = "npm install"
port = "3000"
runtime = "NODEJS_16"
start_command = "npm start"
}
configuration_source = "API"
}
repository_url = var.github_repository_url
source_code_version {
type = "BRANCH"
value = "main"
}
}
}
}
- Private ECR
resource "aws_ecr_repository" "main" {
name = var.project_name
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
resource "aws_ecr_lifecycle_policy" "main" {
repository = aws_ecr_repository.main.name
policy = jsonencode({
rules = [
{
rulePriority = 1
description = "Keep last 30 images"
selection = {
tagStatus = "any"
countType = "imageCountMoreThan"
countNumber = 30
}
action = {
type = "expire"
}
}
]
})
}
Best Practices
-
Performance Optimization
- Configure auto-scaling
- Optimize container size
- Use health checks
- Monitor response times
-
High Availability
- Enable auto-deployments
- Configure health checks
- Use multiple instances
- Monitor availability
-
Security
- Use VPC connectivity
- Implement IAM roles
- Enable encryption
- Regular updates
-
Cost Optimization
- Use appropriate instance size
- Implement auto-scaling
Conclusion
You’ve learned how to set up and manage AWS App Runner using Terraform. This setup provides:
- Containerized application deployment
- Auto-scaling capabilities
- VPC connectivity
- Monitoring and observability
Remember to:
- Monitor application health
- Implement security best practices
- Optimize performance
- Maintain container images
Advanced Features
- Custom Runtime Configuration
resource "aws_apprunner_service" "custom" {
# ... other configuration ...
source_configuration {
image_repository {
image_configuration {
runtime_environment_secrets = {
API_KEY = "arn:aws:secretsmanager:region:account:secret:api-key"
}
runtime_environment_variables = {
NODE_ENV = "production"
REGION = data.aws_region.current.name
}
}
}
}
}
- Private Service
resource "aws_apprunner_vpc_ingress_connection" "main" {
name = "${var.project_name}-ingress"
service_arn = aws_apprunner_service.main.arn
ingress_vpc_configuration {
vpc_id = var.vpc_id
vpc_endpoint_id = aws_vpc_endpoint.apprunner.id
}
}