A secure-by-default, fully managed container/source-to-URL compute service — deploy an ECR image or build straight from a GitHub repo, with derived VPC egress, X-Ray tracing on by default, and no servers to patch. Built for the AWS provider v6.x.
- 🚀 Deploys AWS App Runner — a fully managed service that builds and runs a web application or API from either a container image (ECR/ECR Public) or a GitHub source repository, with automatic TLS, scaling, and a public HTTPS endpoint out of the box.
- 🔒 Derives
egress_typeautomatically — wiring a VPC connector (created here or supplied externally) flips the service to private VPC egress; omitting one leaves the AWS default public-internet egress. No separate toggle to forget. - 🕵️ X-Ray observability ON by default — every service gets an attached
aws_apprunner_observability_configurationunless explicitly opted out, giving a regulated FI's compute an audit trail from day one. - 🔑 Supports a customer-managed KMS key for the source-repo copy and service logs App Runner maintains internally (AWS-managed key by default).
- 🌐 Composable child resources — auto scaling configurations, VPC connectors, VPC Ingress Connections (private ingress), custom domain associations, and GitHub connections — all
for_eachover stable-keyed maps. - 🏷️ Universal
tagson every taggable child resource, withtags_allsurfaced for governance/cost allocation.
💡 Why it matters: App Runner is the fastest path from a container or a GitHub branch to a running, TLS-terminated, auto-scaled HTTPS endpoint — without provisioning a single EC2 instance, ALB, or ECS service. For, that speed can't come at the cost of an accidentally public database dependency or an untraceable production service — this module makes the secure path the default path.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
subgraph Foundations
IAM[terraform-aws-iam-role]
KMS[terraform-aws-kms]
VPC[terraform-aws-vpc]
SG[terraform-aws-security-group]
end
subgraph Supporting
ECR[terraform-aws-ecr]
SM[terraform-aws-secrets-manager]
SSM[terraform-aws-ssm]
VPCE[terraform-aws-vpc-endpoint]
R53[terraform-aws-route53-zone]
end
APPR[["terraform-aws-apprunner"]]
subgraph Serverless family - Phase 7
LAMBDA[terraform-aws-lambda]
SFN[terraform-aws-sfn]
end
IAM -->|access_role_arn / instance_role_arn| APPR
KMS -->|kms_key_arn| APPR
VPC -->|subnets| APPR
SG -->|security_groups| APPR
ECR -->|image_identifier| APPR
SM -->|runtime_environment_secrets| APPR
SSM -->|runtime_environment_secrets| APPR
VPCE -->|vpc_endpoint_id| APPR
APPR -->|service_url CNAME target| R53
APPR -.siblings.-> LAMBDA
APPR -.siblings.-> SFN
style APPR fill:#FF9900,color:#fff
Rendered and validated via the Mermaid Chart MCP (
validate_and_render_mermaid_diagram) during authoring.
flowchart TB
SVC[["aws_apprunner_service.this (keystone)"]]
CONN[aws_apprunner_connection]
ASV[aws_apprunner_auto_scaling_configuration_version]
OBS[aws_apprunner_observability_configuration]
VPCC[aws_apprunner_vpc_connector]
CDA[aws_apprunner_custom_domain_association]
VIC[aws_apprunner_vpc_ingress_connection]
DASV[aws_apprunner_default_auto_scaling_configuration_version]
DEP[aws_apprunner_deployment]
CONN -->|connection_arn| SVC
ASV -->|auto_scaling_configuration_arn| SVC
OBS -->|observability_configuration_arn| SVC
VPCC -->|vpc_connector_arn egress| SVC
SVC -->|service_arn| CDA
SVC -->|service_arn| VIC
SVC -->|service_arn| DEP
ASV -->|promote to account default| DASV
style SVC fill:#FF9900,color:#fff
Rendered and validated via the Mermaid Chart MCP (
validate_and_render_mermaid_diagram) during authoring.
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws provider |
>= 6.0, < 7.0 |
No provider {} block ships inside this module — the caller's root module configures the aws provider (region, credentials, default_tags).
| Action | Required for | Notes |
|---|---|---|
apprunner:CreateService, DescribeService, UpdateService, DeleteService, ListServices, TagResource/UntagResource |
Service lifecycle | Core keystone actions |
apprunner:CreateAutoScalingConfiguration, DescribeAutoScalingConfiguration, DeleteAutoScalingConfiguration, ListAutoScalingConfigurations |
auto_scaling_configurations |
Each config revision is immutable |
apprunner:UpdateDefaultAutoScalingConfiguration |
default_auto_scaling_configuration_key |
Account/Region-wide side effect — grant sparingly |
apprunner:CreateConnection, DescribeConnection, DeleteConnection, ListConnections |
connections |
Connection still needs a manual console handshake |
apprunner:AssociateCustomDomain, DescribeCustomDomains, DisassociateCustomDomain |
custom_domain_associations |
— |
apprunner:CreateVpcConnector, DescribeVpcConnector, DeleteVpcConnector, ListVpcConnectors |
vpc_connectors |
— |
apprunner:CreateVpcIngressConnection, DescribeVpcIngressConnection, UpdateVpcIngressConnection, DeleteVpcIngressConnection |
vpc_ingress_connections |
— |
apprunner:CreateObservabilityConfiguration, DescribeObservabilityConfiguration, DeleteObservabilityConfiguration |
observability_configuration (ON by default) |
— |
apprunner:StartDeployment |
trigger_deployment |
— |
iam:PassRole on the access-role and instance-role ARNs |
authentication_configuration.access_role_arn, instance_configuration.instance_role_arn |
Scope to the specific role ARNs, never iam:PassRole * |
kms:CreateGrant, kms:DescribeKey |
encryption_configuration.kms_key_arn |
Only when a customer-managed CMK is supplied |
ec2:DescribeNetworkInterfaces, DescribeSubnets, DescribeSecurityGroups |
vpc_connectors read-back |
ENI provisioning itself is performed by App Runner's own service-linked role |
App Runner auto-creates
AWSServiceRoleForAppRunneron first use — the executing principal should haveiam:CreateServiceLinkedRolescoped toapprunner.amazonaws.comfor a clean first apply.
- Service-linked role —
AWSServiceRoleForAppRunneris created automatically on first use in an account/Region; no manual step required beyond the IAM permission above. - ECR private repositories need an access role. Set
source_configuration.authentication_configuration.access_role_arnto a role trustingbuild.apprunner.amazonaws.comwith ECR pull permissions. ECR Public repositories need none. - GitHub
code_repositorysources need a completed connection handshake.aws_apprunner_connectioncreates the connection object inPENDING_HANDSHAKE; a human must approve it once in the AWS Console (App Runner → GitHub connections) before it can back a service. Terraform cannot automate this interactive OAuth step. - VPC egress is opt-in. Without a
vpc_connectorsentry wired throughnetwork_configuration, the service can only reach the public internet — not a private RDS/ElastiCache/internal endpoint. - Region availability — App Runner is not available in every AWS Region; confirm availability in the target Region. No us-east-1 global-resource coupling (unlike CloudFront/ACM/WAFv2).
- Quotas — 60 services per account/Region (soft), 100 vCPUs / 100 running instances per account/Region (soft), 100 VPC connectors per account/Region, 5 custom domains per service (soft).
terraform-aws-apprunner/
├── providers.tf
├── variables.tf
├── main.tf
├── outputs.tf
├── README.md
└── SCOPE.md
module "apprunner" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
instance_configuration = {
instance_role_arn = module.apprunner_instance_role.arn
}
tags = {
Environment = "prod"
Application = "loan-api"
}
}| Input | Type | Source module |
|---|---|---|
source_configuration.image_repository.image_identifier |
string (ECR URI) |
terraform-aws-ecr |
source_configuration.authentication_configuration.access_role_arn |
string (IAM ARN) |
terraform-aws-iam-role |
instance_configuration.instance_role_arn |
string (IAM ARN) |
terraform-aws-iam-role |
vpc_connectors[*].subnets |
list(string) |
terraform-aws-vpc |
vpc_connectors[*].security_groups |
list(string) |
terraform-aws-security-group |
vpc_ingress_connections[*].vpc_endpoint_id |
string |
terraform-aws-vpc-endpoint |
encryption_configuration.kms_key_arn |
string (ARN) |
terraform-aws-kms |
*.runtime_environment_secrets values |
string (ARN) |
terraform-aws-secrets-manager / terraform-aws-ssm |
| Output | Description | Consumed by |
|---|---|---|
id |
Service id | Root-module cross-references |
arn |
Service ARN | IAM policies, this module's own vpc_ingress_connections/custom_domain_associations |
service_url |
Default HTTPS subdomain | Route 53 CNAME target |
egress_type |
Resolved DEFAULT/VPC egress mode | Network audit tooling |
vpc_connector_arns |
Map name → ARN | Sibling module calls sharing a connector |
custom_domain_certificate_validation_records / custom_domain_dns_targets |
DNS records to create | Route 53 automation |
connection_arns / connection_statuses |
GitHub connection state | Runbook gating on AVAILABLE |
tags_all |
Merged tags | Governance/cost allocation |
1 · Minimal — ECR image-based service
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
}2 · GitHub source-repository (code_repository) service
module "apprunner_connection" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-marketing-site"
connections = {
github = { provider_type = "GITHUB" }
}
source_configuration = {
code_repository = {
repository_url = "https://github.com/microsoftexpert/marketing-site"
source_code_version = {
value = "main"
}
code_configuration = {
configuration_source = "REPOSITORY" # reads apprunner.yaml
}
}
authentication_configuration = {
connection_arn = module.apprunner_connection.connection_arns["github"]
}
}
}
# NOTE: complete the OAuth handshake once in the AWS Console (App Runner ->
# GitHub connections) before this service can deploy successfully.3 · VPC-connected service reaching a private RDS instance
module "apprunner_backoffice" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-backoffice"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
vpc_connectors = {
private = {
subnets = module.vpc.private_subnet_ids
security_groups = [module.apprunner_sg.id]
}
}
# egress_type is derived to "VPC" automatically because vpc_connector_key is set.
network_configuration = {
egress_configuration = {
vpc_connector_key = "private"
}
}
instance_configuration = {
instance_role_arn = module.apprunner_instance_role.arn
}
}
# module.apprunner_sg would allow egress to module.rds's security group on 5432/3306.4 · Custom domain association
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
custom_domain_associations = {
api = {
domain_name = "api.farmcreditcasey.example"
enable_www_subdomain = false
}
}
}
# After apply, create the CNAME records from
# module.apprunner_api.custom_domain_certificate_validation_records["api"]
# plus a CNAME from api.farmcreditcasey.example to
# module.apprunner_api.custom_domain_dns_targets["api"].5 · Private ingress via VPC endpoint (no public access)
module "apprunner_internal" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-internal-tool"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
network_configuration = {
ingress_configuration = {
is_publicly_accessible = false
}
}
vpc_ingress_connections = {
internal = {
vpc_id = module.vpc.id
vpc_endpoint_id = module.apprunner_vpc_endpoint.id
}
}
}6 · X-Ray observability — explicit naming
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
observability_configuration = {
name = "casey-loan-api-xray"
}
}
# observability_configuration.enabled defaults to true — this example only
# customizes the resource's name; tracing is already ON without this block.7 · Secure-by-default opt-out — disable X-Ray tracing
module "apprunner_lowrisk" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-public-docs"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
# Discouraged for regulated workloads — documented exception required.
observability_configuration = {
enabled = false
}
}8 · Customer-managed KMS key for source/log encryption
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
encryption_configuration = {
kms_key_arn = module.kms.arn
}
}9 · Auto scaling configuration — custom concurrency/instance bounds
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
auto_scaling_configurations = {
api-scaling = {
max_concurrency = 50
min_size = 2
max_size = 10
}
}
auto_scaling_configuration_key = "api-scaling"
}10 · Environment variables and secrets from Secrets Manager
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
image_configuration = {
port = "8080"
runtime_environment_variables = {
LOG_LEVEL = "info"
}
runtime_environment_secrets = {
DATABASE_URL = module.secrets_manager.arn
}
}
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
}11 · `for_each` pattern — one service per environment
locals {
apprunner_services = {
dev = { image_tag = "dev-latest" }
test = { image_tag = "test-latest" }
prod = { image_tag = "latest" }
}
}
module "apprunner" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
for_each = local.apprunner_services
service_name = "casey-loan-api-${each.key}"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:${each.value.image_tag}"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
tags = {
Environment = each.key
}
}12 · Manual deployment trigger (redeploy the same image tag)
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
trigger_deployment = true
}13 · Import an existing App Runner service
import {
to = module.apprunner_api.aws_apprunner_service.this
id = "arn:aws:apprunner:us-east-2:123456789012:service/casey-loan-api/abc123def456"
}14 · Promote a custom auto scaling configuration to the account default (opt-in)
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
auto_scaling_configurations = {
org-standard = {
min_size = 1
max_size = 25
}
}
# Account/Region-wide side effect: every future service with no explicit
# auto_scaling_configuration_arn inherits this configuration.
default_auto_scaling_configuration_key = "org-standard"
}15 · End-to-end composition — ECR + VPC + RDS + App Runner
module "vpc" {
source = "git::https://github.com/microsoftexpert/terraform-aws-vpc?ref=v1.0.0"
#...
}
module "ecr" {
source = "git::https://github.com/microsoftexpert/terraform-aws-ecr?ref=v1.0.0"
#...
}
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
#...
}
module "rds" {
source = "git::https://github.com/microsoftexpert/terraform-aws-rds?ref=v1.0.0"
identifier = "casey-loan-db"
vpc_security_group_ids = [module.rds_sg.id]
db_subnet_group_name = module.vpc.database_subnet_group_name
kms_key_arn = module.kms.arn
#...
}
module "apprunner_ecr_access_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
# trust policy: build.apprunner.amazonaws.com
# inline policy: ecr:GetDownloadUrlForLayer / BatchGetImage / GetAuthorizationToken on module.ecr.arn
}
module "apprunner_instance_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
# trust policy: tasks.apprunner.amazonaws.com
}
module "apprunner_sg" {
source = "git::https://github.com/microsoftexpert/terraform-aws-security-group?ref=v1.0.0"
vpc_id = module.vpc.id
egress_rules = {
to_rds = {
referenced_security_group_id = module.rds_sg.id
from_port = 5432
to_port = 5432
ip_protocol = "tcp"
}
}
}
module "apprunner_api" {
source = "git::https://github.com/microsoftexpert/terraform-aws-apprunner?ref=v1.0.0"
service_name = "casey-loan-api"
source_configuration = {
image_repository = {
image_identifier = "${module.ecr.repository_url}:latest"
image_repository_type = "ECR"
image_configuration = {
runtime_environment_secrets = {
DATABASE_URL = module.rds.master_user_secret_arn
}
}
}
authentication_configuration = {
access_role_arn = module.apprunner_ecr_access_role.arn
}
}
instance_configuration = {
instance_role_arn = module.apprunner_instance_role.arn
}
vpc_connectors = {
private = {
subnets = module.vpc.private_subnet_ids
security_groups = [module.apprunner_sg.id]
}
}
network_configuration = {
egress_configuration = {
vpc_connector_key = "private"
}
}
encryption_configuration = {
kms_key_arn = module.kms.arn
}
tags = {
Environment = "prod"
Application = "loan-api"
}
}Core
service_name— service name (FORCE-NEW)source_configuration— exactly one ofimage_repositoryorcode_repository, plusauthentication_configurationandauto_deployments_enabled
Compute
instance_configuration—cpu(default"1024"),memory(default"2048"),instance_role_arn
Networking
network_configuration—ip_address_type,ingress_configuration.is_publicly_accessible,egress_configuration.vpc_connector_arn/vpc_connector_key(derivesegress_type)vpc_connectors— map of private-egress VPC connectors to createvpc_ingress_connections— map of private-ingress PrivateLink connections
Health & Reliability
health_check_configuration— protocol/path/interval/timeout/thresholds
Encryption
encryption_configuration—kms_key_arn(FORCE-NEW; null = AWS-managed key)
Scaling
auto_scaling_configurations— map of named scaling profiles to createauto_scaling_configuration_arn/auto_scaling_configuration_key— which one the service usesdefault_auto_scaling_configuration_key— promote one to the account/Region default (opt-in)
Observability
observability_configuration—enabled(default true),name,trace_configuration.vendor
Domains & Source
custom_domain_associations— map of custom domainsconnections— map of GitHub source-repository connections
Operational
trigger_deployment— force a redeploytags,timeouts
id,arn— primary identifiersservice_name,service_id,service_url,statusauto_scaling_configuration_arns,service_auto_scaling_configuration_arnvpc_connector_arns,egress_typevpc_ingress_connection_arns,vpc_ingress_connection_domain_namescustom_domain_association_ids,custom_domain_certificate_validation_records,custom_domain_dns_targetsconnection_arns,connection_statusesobservability_configuration_arn—nullwhen observability is disabledtags_all
No output is sensitive = true — this module emits no secrets; runtime_environment_secrets values are ARNs pointing at Secrets Manager/SSM, not the secret values themselves.
- ARN format:
arn:aws:apprunner:<region>:<account>:service/<service_name>/<service_id>. Child resources (VPC connector, auto scaling configuration, observability configuration) share the patternarn:aws:apprunner:<region>:<account>:<type>/<name>/<id>/<revision>. service_nameandencryption_configurationare FORCE-NEW. Changing either destroys and recreates the service, generating a newservice_id/service_urland breaking any DNS CNAMEs pointed at the old URL.- No
timeoutsblock on the service itself — a real provider gotcha for this module; onlyaws_apprunner_deploymentsupports a create timeout. - Auto scaling configurations and observability configurations are immutable, versioned resources. Every field is force-new; a change registers a new revision under the same name, and the old revision goes
INACTIVE(reclaimed by AWS after a delay, not immediately deleted). egress_type↔vpc_connector_arncoupling is handled for you: this module derivesegress_typefrom whether a connector is wired, closing the common "connector created but never actually used" misconfiguration.tags↔tags_all↔ providerdefault_tags— standard interaction; resource tags win on key conflict.aws_apprunner_default_auto_scaling_configuration_versionandaws_apprunner_deploymentare not taggable.- Eventual consistency / async validation — custom domain association validation is DNS-based and asynchronous;
certificate_validation_records/dns_targetare available immediately, but the domain doesn't reachACTIVEuntil the caller creates the DNS records and App Runner observes them. - Destroy ordering —
vpc_ingress_connectionsandcustom_domain_associationsreference the service by ARN; Terraform's implicit dependency graph destroys them before the service. Avpc_connectorsentry still attached to a running service cannot be deleted — detach (switch egress back toDEFAULTor another connector) first. - No us-east-1 global-resource requirement — App Runner is a regional service; unlike CloudFront/ACM/WAFv2, there is no global-resource coupling to document here.
- Egress network path defaults to public (
DEFAULT) unless a VPC connector is wired — but the moment one is wired (viavpc_connector_arnorvpc_connector_key), the module automatically switches to"VPC"egress. Opt-out: simply don't wire a connector. - Encryption at rest defaults to the AWS-managed key (
encryption_configuration = null). Opt-out (toward stronger posture): supplyencryption_configuration.kms_key_arnfor an auditable, revocable CMK. - X-Ray observability defaults ON (
observability_configuration.enabled = true). Opt-out (discouraged): setobservability_configuration.enabled = false. - Public reachability defaults to
true(App Runner's core purpose). Opt-out toward a private posture: setnetwork_configuration.ingress_configuration.is_publicly_accessible = falseand add avpc_ingress_connectionsentry. - VPC connector security groups default to the VPC's default security group (allows all outbound) when
security_groupsis omitted. Opt-out toward least privilege: always supply an explicitsecurity_groupslist for PII-adjacent workloads. - Account-wide default scaling profile is never touched unless explicitly requested via
default_auto_scaling_configuration_key— a side effect this module refuses to trigger silently.
cd terraform-aws-apprunner
terraform init -backend=false
terraform validate
terraform fmt -check
⚠️ Pin the module source with?ref=v1.0.0— never a branch — for reproducible applies.
terraform plan/apply additionally require valid AWS credentials (profile, SSO session, or OIDC role) and a configured Region with App Runner availability.
terraform plan
terraform apply
terraform outputterraform init -backend=false/validate/fmt -check— pure static validation, no AWS credentials required.tflint(AWS ruleset) andcheckovvia the sharedvalidate_all_modules.ps1harness.- Live testing (a human, out of band): apply in a non-production account with a least-privilege role, confirm
service_urlresponds over HTTPS, confirmstatus = RUNNING, and (ifvpc_connectorsis used) confirm connectivity to the private dependency from inside the service.
$ terraform output
arn = "arn:aws:apprunner:us-east-2:123456789012:service/casey-loan-api/6b1f2e6b3c9a4d9e8f0a1b2c3d4e5f60"
egress_type = "VPC"
id = "arn:aws:apprunner:us-east-2:123456789012:service/casey-loan-api/6b1f2e6b3c9a4d9e8f0a1b2c3d4e5f60"
service_id = "6b1f2e6b3c9a4d9e8f0a1b2c3d4e5f60"
service_name = "casey-loan-api"
service_url = "abc123def456.us-east-2.awsapprunner.com"
status = "RUNNING"
observability_configuration_arn = "arn:aws:apprunner:us-east-2:123456789012:observabilityconfiguration/casey-loan-api-observability/1/..."
- Tag drift from
default_tagsoverlap — if a key exists in both the provider'sdefault_tagsand this module'stags,tags_allreflects the resource-tag value (it wins); avoid duplicating the same key in both places. - Credential-chain failures —
terraform plan/applyfail withNoCredentialProvidersor similar when noAWS_PROFILE/SSO session/OIDC role is active; this module never accepts credentials as variables, so fix the chain at the shell/CI level. - Service stuck in
CREATE_FAILED— almost always a missing/incorrectauthentication_configuration.access_role_arn(private ECR) or an unapprovedaws_apprunner_connectionhandshake (GitHub). Checkapprunner:DescribeServiceevents in the console. - Service can't reach a private RDS/ElastiCache endpoint — confirm
network_configuration.egress_configurationactually resolves to"VPC"(check theegress_typeoutput); avpc_connectorsentry that's defined but never referenced byvpc_connector_arn/vpc_connector_keyleaves the service on public egress. - IAM
AccessDeniedonapprunner:CreateService/etc. — the executing role is missing an action from the Required IAM Permissions table above; add the specific action, don't broaden toapprunner:*. iam:PassRoledenied —access_role_arn/instance_role_arnrequire the Terraform principal to haveiam:PassRolescoped to those exact role ARNs.- Destroy hangs on a
vpc_connectorsentry — the connector is still referenced by a running service's egress configuration; switch the service toDEFAULTegress (or another connector) before removing the entry. - Custom domain stuck
PENDING_CERTIFICATE_DNS_VALIDATION— the caller hasn't yet created the CNAME records fromcustom_domain_certificate_validation_records/custom_domain_dns_targets; App Runner polls DNS asynchronously and there is no Terraform-side wait for it. - GitHub connection stuck
PENDING_HANDSHAKE— a human must approve it once in the AWS Console; no Terraform action will complete this.
- Terraform AWS provider resource reference: App Runner Service, Auto Scaling Configuration Version, Connection, Custom Domain Association, Default Auto Scaling Configuration Version, Deployment, Observability Configuration, VPC Connector, VPC Ingress Connection
- AWS App Runner Developer Guide — source configuration, networking, and security concepts
- AWS App Runner service quotas reference
🧡 "Infrastructure as Code should be standardized, consistent, and secure."