AWS App Runner, Past The One-Click Deploy
A deep, engineer-level walkthrough of how App Runner's two-role security model, concurrency-based autoscaling, and deployment cutover actually work underneath the "point it at a repo" tutorials.
If you already know that App Runner takes a container image or a source repo and gives you a running, load-balanced HTTPS endpoint, this article isn’t going to re-walk that. We’re going into why App Runner needs two separate IAM roles instead of one, how its autoscaling actually measures load, what really happens to your old version during a deployment cutover, and how to give a “no VPC required” service real private networking without breaking the abstraction that makes it attractive in the first place.
1Advanced Service Concepts
Skipping “what is App Runner” — this is the layer where its abstractions start requiring real architectural decisions.
App Runner’s pitch is deploying a container or source repo without touching a load balancer, target group, or Auto Scaling group directly. The advanced work is understanding the handful of concepts App Runner still asks you to reason about explicitly, because they don’t disappear — they just move.
Two Distinct IAM Roles
An access role lets the App Runner build service pull a private container image or source code; an instance role is assumed by the running application itself to call other AWS services — conflating the two is a common source of confusing permission errors.
VPC Connectors and Private Ingress
By default, App Runner services have public ingress and egress with no VPC attachment at all; a VPC Connector lets outbound traffic reach private resources like an RDS instance, while private ingress (via VPC endpoints) restricts inbound access away from the public internet entirely.
Concurrency-Based Scaling Configuration
Scaling decisions are driven by a configured max-concurrent-requests-per-instance value, not CPU or memory utilization — a setting tuned wrong either over-provisions instances unnecessarily or queues requests behind an artificially low ceiling.
Source-Based vs. Image-Based Deployment
Source-based deployment has AWS build your code using a managed builder inferred from the repo; image-based deployment runs a container image you’ve already built — each has a different rebuild trigger model and a different degree of build-process control.
Automatic vs. Manual Deployment Triggers
A service can be configured to automatically deploy on every new commit or image push, or to require an explicit deployment trigger — a decision with real consequences for how a bad commit propagates to production.
Provisioned vs. Active Instance Billing
App Runner distinguishes between instances that are provisioned but idle and instances actively handling requests, billing them differently — a detail that materially affects cost modeling for spiky traffic patterns.
Think of a valet parking service outside a restaurant. You hand over your car (your code or image) and never touch the parking garage’s floor plan, elevators, or ticketing system (load balancer, target groups, Auto Scaling group) — that’s fully handled. But you still choose how many valets should be on duty during a rush (concurrency setting), whether the valet can access your car’s trunk to grab something for you (instance role permissions), and whether the garage entrance is open to any passerby or only to people with a reservation (public vs. private ingress).
What Interviewer May Ask
2Internal Working
What App Runner is actually orchestrating behind that single “deploy” button.
Underneath the abstraction, App Runner provisions and manages a load balancer, a fleet of compute instances running your container, TLS termination and certificate management, and the scaling logic tying them together — all without exposing any of those resources directly in your account for you to configure by hand.
flowchart TB
U[Client Request] --> LB[AWS-Managed Load Balancer + TLS Termination]
LB --> SC{Scaling Controller}
SC --> I1[Instance 1]
SC --> I2[Instance 2]
SC --> I3[Instance N]
I1 -.->|VPC Connector| PRIV[Private VPC Resources]
I2 -.->|VPC Connector| PRIV
BS[Build Service] -->|Access Role| SRC[Source Repo / ECR Image]
BS --> I1
I1 -->|Instance Role| AWS_APIS[Other AWS Services]
The Build Pipeline for Source-Based Services
For source-based deployment, App Runner’s managed build service detects the application’s runtime and framework, compiles or bundles it using a AWS-managed builder, and packages the result into a container image internally — all without you writing a Dockerfile. This convenience trades away fine-grained control over the build environment; teams needing custom build steps or non-standard toolchains generally move to image-based deployment with their own CI pipeline producing the final image.
How the Scaling Controller Actually Decides
Each running instance reports how many concurrent requests it is currently handling. When that count approaches the configured max-concurrency threshold across the fleet, the scaling controller provisions additional instances; when demand drops, it scales back down toward the configured minimum. Because this is a direct concurrency signal rather than an inferred CPU-utilization proxy, tuning the concurrency threshold has a much more immediate and predictable effect on scaling behavior than a typical CPU-based Auto Scaling policy.
3Data Flow & Lifecycle
From a new commit or image push to traffic actually flowing to the new version — and what happens if it doesn’t go well.
Trigger
A new commit (source-based, with automatic deployment enabled) or a new image push to the configured repository triggers a deployment, or a manual deployment is explicitly initiated.
Build (Source-Based Only)
The managed build service compiles the new source using the access role’s pull permissions, producing a new container image internally.
Provisioning New Instances
New instances running the updated image are provisioned alongside — not instead of — the currently running old-version instances.
Health Check Gate
New instances must pass configured health checks before receiving any live traffic — a failing health check at this stage halts the rollout before it ever reaches users.
Traffic Cutover
Once healthy, the load balancer shifts traffic to the new instances; old instances are drained of in-flight requests rather than terminated abruptly.
Automatic Rollback
If the new version fails its health checks during rollout, App Runner automatically rolls back to the last known-good version without requiring manual intervention, minimizing exposure to a broken deployment.
Because old instances stay serving traffic until the new version is verified healthy, a genuinely broken new version never fully replaces a working one — but a new version that passes health checks while still containing a functional bug (one the health check doesn’t test for) will cut over just as confidently as a correct one. Health check design quality directly bounds how much protection this rollout model actually provides.
4Advantages, Disadvantages & Trade-offs
Advantages
- Full load balancer, TLS, and scaling infrastructure provisioned and patched with zero direct management
- Concurrency-based scaling gives a more direct, predictable scaling signal than CPU-based heuristics for many web workloads
- Built-in health-check-gated rollout with automatic rollback reduces the blast radius of a bad deployment by default
- Source-based deployment removes the need to write and maintain a Dockerfile for straightforward applications
Disadvantages & Trade-offs
- No VPC attachment by default means private-resource access requires deliberately adding a VPC Connector, an easy step to forget
- Source-based build customization is limited compared to a fully custom CI pipeline producing your own image
- Less infrastructure-level control than ECS or EKS — teams needing custom networking topologies or sidecar patterns will outgrow it
- Provisioned-but-idle billing behavior requires deliberate cost modeling for workloads with long idle periods
Anti-Pattern
Granting the instance role broad permissions “to be safe,” reasoning that since the service is already simplified, its IAM shouldn’t need much thought either.
Why It Fails
The instance role is assumed by your running application code, which is the exact surface exposed to any request-handling vulnerability. A broad instance role turns any application-layer compromise into a broad AWS account compromise, regardless of how simplified the surrounding infrastructure is.
Better Approach
Scope the instance role to exactly the AWS API calls the running application makes, and keep the access role — used only at build time — separately and even more tightly scoped, since it never needs runtime application permissions at all.
5Performance & Scalability
App Runner’s scaling ceiling and responsiveness are governed almost entirely by two settings: the configured max-concurrency-per-instance value and the minimum/maximum instance count bounds. Getting these right matters more for App Runner performance tuning than almost anything else in the service.
Where Performance Actually Bites
Setting the max-concurrency value too high causes each instance to accept more simultaneous requests than it can actually serve with acceptable latency, degrading response times well before the scaling controller decides more capacity is needed. Setting it too low causes premature, unnecessary scale-out, provisioning far more instances (and cost) than the actual load requires. The correct value comes from load-testing the specific application’s real request-handling capacity, not from a generic default left unexamined.
Cold Start Considerations
Scaling from zero, or from a low minimum instance count, introduces a cold-start delay while a new instance provisions and passes its health check — latency-sensitive services with unpredictable traffic often set a non-zero minimum instance count specifically to keep at least one warm instance always available, trading a small baseline cost for consistently low first-request latency.
6High Availability & Reliability
App Runner distributes running instances across multiple Availability Zones automatically, and its load balancer routes around unhealthy instances without requiring any manual health-management from you — reliability at the infrastructure layer is largely handled.
A shallow or overly permissive health check endpoint — one that returns success even when a critical downstream dependency is failing — defeats the entire health-check-gated deployment model, since App Runner has no way to know the application is actually unhealthy if the health check itself doesn’t say so.
Reliable operation depends on a health check endpoint that genuinely reflects the application’s ability to serve real traffic — checking downstream database connectivity or critical dependency availability, not just returning a static 200 OK regardless of internal state.
The Automatic Rollback Safety Net
Because failed health checks during a deployment trigger automatic rollback to the last known-good version, a genuinely broken deployment is contained quickly — but only for failure modes the health check actually detects. Reliability engineering here means continuously improving what the health check verifies, not just trusting that its existence is sufficient.
7Security
App Runner’s security model is defined by its two roles, its default-public networking posture, and how secrets reach the running application.
Separate and Minimal Access and Instance Roles
The access role should only ever be able to pull the specific image repository or source repo it needs; the instance role should only ever hold the specific runtime permissions the application actually calls.
Deliberate Ingress Configuration
Services handling internal-only traffic should be configured with private ingress via VPC endpoints rather than left on the public-ingress default, which is easy to overlook precisely because App Runner works out of the box without it.
Secrets Manager and Parameter Store Integration
Sensitive configuration values should be referenced from Secrets Manager or Parameter Store rather than set as plain environment variables, keeping secret material out of the service configuration itself.
Source and Image Provenance
For image-based deployment, restricting which ECR repositories and tags the access role can pull from limits the impact of a compromised upstream image source reaching production.
8Monitoring, Logging & Metrics
App Runner ships application logs, build logs, and a defined set of service metrics to CloudWatch automatically, so the monitoring work is mostly about acting on what’s already collected rather than instrumenting collection yourself.
| Signal | Where It Lands | Why It Matters |
|---|---|---|
| Application stdout/stderr logs | CloudWatch Logs, application log group | Standard debugging trail for the running service itself |
| Build logs | CloudWatch Logs, build log group | Diagnoses source-based build failures before a deployment even reaches runtime |
| Request count and latency | CloudWatch metrics | Direct signal for whether the current concurrency setting matches real traffic shape |
| Active vs. provisioned instance count | CloudWatch metrics | Confirms scaling is behaving as configured and reveals cost-relevant idle capacity |
| Deployment status events | App Runner console / EventBridge | Confirms whether a rollout completed, is still gated on health checks, or auto-rolled-back |
Wire deployment status events to a notification channel so a silent automatic rollback doesn’t go unnoticed — the safety net working correctly is still information a team needs, since it means the most recent commit shipped with a real problem.
9Deployment & Cloud
Beyond the initial “connect a repo” setup, mature App Runner usage integrates with existing CI/CD, infrastructure-as-code, and custom domain practices rather than treating the console as the primary deployment interface.
Define the Service as Code
Terraform or CloudFormation defines the App Runner service, its access and instance roles, VPC Connector, and autoscaling configuration as reviewable infrastructure rather than console clicks.
Choose the Deployment Trigger Deliberately
Automatic deployment on every commit suits fast-moving, well-tested services; manual deployment triggers suit services where a deliberate release gate is preferred.
Attach a VPC Connector Where Needed
Any service needing to reach a private RDS instance, ElastiCache cluster, or internal API gets an explicit VPC Connector attached — this is never automatic and must be part of the provisioning definition.
Configure Custom Domains and Certificates
Custom domain mapping with automatically managed TLS certificates replaces the default App Runner-provided domain for production-facing services.
Where App Runner Fits Against ECS Fargate and Lambda
App Runner sits deliberately between Lambda (event-driven, no persistent process) and ECS/Fargate (full container orchestration control) for teams that want a persistently running web service without managing a cluster, task definitions, or a load balancer directly — the trade-off being less infrastructure control in exchange for meaningfully less operational surface area.
10Design Patterns & Anti-patterns
Load-Tested Concurrency Tuning
Max-concurrency-per-instance is set from real load-test data reflecting the application’s actual per-request resource cost, not left at a generic default.
Meaningful Health Checks
Health check endpoints verify real downstream dependency health, giving the automatic rollback mechanism something genuinely useful to act on.
Explicit Private Networking
Services needing private-resource access get a VPC Connector attached deliberately during provisioning, and public-facing services that shouldn’t be public get private ingress configured explicitly rather than left on the open default.
Shared Broad IAM Role
Using one overly permissive role for both build-time access and runtime application needs collapses the least-privilege boundary the two-role model exists to create.
Default Health Check Left Unexamined
Relying on a trivial health check that always returns success regardless of actual application state neutralizes the deployment safety net entirely.
Untuned Concurrency Default
Leaving the concurrency setting at whatever default was clicked through during setup, without validating it against real traffic and latency requirements, produces either wasted spend or degraded latency.
11Best Practices & Common Mistakes
Best Practices
- Keep the access role and instance role separately and minimally scoped to their distinct purposes
- Load-test to determine max-concurrency-per-instance rather than accepting a generic default
- Attach a VPC Connector deliberately whenever private-resource access is required
- Reference secrets from Secrets Manager or Parameter Store instead of plain environment variables
- Wire deployment status and rollback events to a notification channel the team actually watches
Common Mistakes
- Assuming private database connectivity works without realizing a VPC Connector was never attached
- Confusing the access role and instance role and granting the wrong one the permissions actually needed
- Shipping a health check that always returns success, defeating automatic rollback protection
- Leaving public ingress on for a service that should only ever be reached internally
- Not setting a non-zero minimum instance count for latency-sensitive services, then being surprised by cold-start delay
12Real-World & Industry Examples
Startups Standardizing on App Runner for Internal APIs
Small engineering teams without dedicated platform staff commonly adopt App Runner specifically for internal and customer-facing APIs where the operational savings of not managing ECS clusters or Kubernetes directly outweighs the reduced infrastructure control, letting a handful of engineers ship and scale services without a dedicated platform function.
SaaS Vendors Using Source-Based Deployment for Rapid Iteration
Teams iterating quickly on a web application frontend or API commonly use source-based deployment with automatic deployment triggers specifically to remove the CI pipeline step of building and pushing a container image, shortening the loop between a merged pull request and a live change.
Regulated Workloads Adding Private Ingress
Organizations with internal compliance tooling or internal dashboards that must never be internet-reachable configure App Runner’s private ingress specifically to keep the convenience of the managed runtime while satisfying a hard requirement that the service never be reachable from the public internet.
13Frequently Asked Questions
14Summary and Key Takeaways
What to Carry Forward
- App Runner uses two distinct IAM roles — an access role for build-time pulls and an instance role for runtime AWS calls — and conflating them breaks least privilege in both directions.
- Networking is opt-in by design: no VPC attachment and public ingress by default, with VPC Connectors and private ingress added deliberately when private-resource access or restricted reachability is required.
- Scaling decisions are driven by a configured max-concurrency-per-instance value, not CPU or memory — this setting deserves real load-test data, not a generic default.
- Deployments provision new instances alongside old ones, gate traffic cutover behind health checks, and automatically roll back on failure — but that safety net is only as good as what the health check actually verifies.
- Source-based deployment trades build-process control for convenience; image-based deployment is the better fit once custom build steps or non-standard toolchains are involved.
- Provisioned-but-idle and active-instance billing states differ — cost modeling should account for the configured minimum instance count, not assume true zero-cost idling.
- App Runner sits deliberately between Lambda and ECS/Fargate — less infrastructure control than a full container orchestrator, but meaningfully less operational surface than managing one directly.