AWS App Runner, Past The One-Click Deploy

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.

Identity

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.

Networking

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.

Autoscaling

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 Model

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.

Rollout

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.

Compute

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.

Analogy

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

QWhy does App Runner require two separate IAM roles instead of one?
The access role and instance role serve different actors at different times: the access role is assumed by App Runner’s own build infrastructure before your application even exists, to pull a private image or clone a private repo, while the instance role is assumed by your running application code to call AWS services on its own behalf. Merging them would force the build process to inherit whatever runtime permissions your application needs, and vice versa — violating least privilege in both directions.

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]
        
Fig 2.1 — Build-time access role and runtime instance role serve two entirely different stages of the same service

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.

1

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.

2

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.

3

Provisioning New Instances

New instances running the updated image are provisioned alongside — not instead of — the currently running old-version instances.

4

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.

5

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.

6

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.

i
Design Implication

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
ADR-APR-01 Anti-Pattern
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.

Concurrency
DRIVES SCALING DECISIONS, NOT CPU UTILIZATION
Configurable
MIN AND MAX INSTANCE COUNT BOUNDS PER SERVICE
Health-Gated
NEW CAPACITY MUST PASS CHECKS BEFORE SERVING TRAFFIC

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.

!
Reliability Trap

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.

Role Scoping

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.

Networking

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

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.

Build Security

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.

“App Runner removes the load balancer and the Auto Scaling group from your worry list — it does not remove IAM scoping or network exposure from it.”

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.

SignalWhere It LandsWhy It Matters
Application stdout/stderr logsCloudWatch Logs, application log groupStandard debugging trail for the running service itself
Build logsCloudWatch Logs, build log groupDiagnoses source-based build failures before a deployment even reaches runtime
Request count and latencyCloudWatch metricsDirect signal for whether the current concurrency setting matches real traffic shape
Active vs. provisioned instance countCloudWatch metricsConfirms scaling is behaving as configured and reveals cost-relevant idle capacity
Deployment status eventsApp Runner console / EventBridgeConfirms whether a rollout completed, is still gated on health checks, or auto-rolled-back
i
Best Practice

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.

1

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.

2

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.

3

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.

4

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

Pattern

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.

Pattern

Meaningful Health Checks

Health check endpoints verify real downstream dependency health, giving the automatic rollback mechanism something genuinely useful to act on.

Pattern

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.

Anti-pattern

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.

Anti-pattern

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.

Anti-pattern

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.

2
IAM ROLES GOVERN BUILD-TIME AND RUNTIME ACCESS SEPARATELY
Health-Gated
DEPLOYMENTS WITH AUTOMATIC ROLLBACK
VPC-Optional
NETWORKING, ADDED DELIBERATELY WHEN NEEDED

13Frequently Asked Questions

01Can an App Runner service reach a private RDS database without any additional networking configuration?
No — by default, App Runner services have no VPC attachment and cannot reach resources inside a private VPC. A VPC Connector must be explicitly attached to the service to enable that outbound connectivity.
02What happens to in-flight requests on old instances during a deployment cutover?
Old instances are drained rather than terminated immediately — in-flight requests are allowed to complete on the old version while new traffic is routed to the newly-verified healthy instances, avoiding abrupt request failures during rollout.
03Does scaling to zero eliminate all cost for an idle service?
App Runner distinguishes provisioned and active instance states with different billing; even at a low minimum instance count there is typically some baseline cost tied to the minimum configured capacity, so cost modeling should account for the configured minimum rather than assuming true zero-cost idling.
04If a deployment’s health check fails, does App Runner tell you what specifically failed?
The deployment status reflects that a health check failure triggered rollback, and application logs from the failed instances remain available in CloudWatch Logs for diagnosis — the rollback event itself is a signal to investigate, not a diagnosis on its own.
05Is source-based deployment a good fit for applications with complex, custom build steps?
Generally not — the managed builder is designed for straightforward, framework-standard build processes. Applications with custom compilation steps, non-standard toolchains, or multi-stage build requirements are usually better served by image-based deployment, where the team’s own CI pipeline has full control over how the final image is produced.

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.