AWS App Runner promised to make container deployments frictionless. No infrastructure to manage, automatic scaling, built-in load balancing. But monitoring App Runner services reveals gaps fast. CloudWatch’s default metrics are too coarse to diagnose real problems. You see CPU spikes but not which API call caused them. You see memory usage aggregated across all instances but not which container is leaking memory. And cold starts the delay when App Runner provisions a new instance after traffic drops to zero are invisible in default CloudWatch dashboards.
As more teams adopt managed container services like App Runner, the monitoring challenge shifts from managing infrastructure to understanding application behavior inside a black box runtime.
This guide walks through setting up comprehensive monitoring for AWS App Runner services covering CloudWatch metrics, cold start detection, application level tracing, and how to correlate infrastructure signals with actual request performance.
Prerequisites
Before starting, ensure you have:
- An AWS App Runner service deployed and running in production or staging
- AWS CLI installed and configured with credentials that have CloudWatch read access and App Runner read access
- IAM permissions for CloudWatch Logs, CloudWatch Metrics, and EventBridge (if setting up automated alerting)
- Basic familiarity with CloudWatch dashboards and log groups
- Optional: An APM tool that supports OpenTelemetry or AWS integrations for deeper application tracing (CubeAPM, Datadog, or New Relic)
For teams running multiple services, a dedicated monitoring IAM role with cloudwatch:GetMetricStatistics, apprunner:DescribeService, and logs:FilterLogEvents permissions simplifies access control.
Step 1: Understand What App Runner Exposes in CloudWatch
AWS App Runner publishes metrics to CloudWatch in the AWS/AppRunner namespace. These metrics fall into two categories: instance level and service level. The distinction matters because most production issues require both views to diagnose.
Instance level metrics track individual scaling units. Each instance runs one container. If your service auto scales to 5 instances during peak traffic, you get 5 separate metric streams for CPU and memory. Service level metrics aggregate across all active instances.
Navigate to the CloudWatch console and open Metrics → All metrics → AWS/AppRunner. You will see these core metrics:
Instance metrics:
CPUUtilization— percentage of allocated vCPU used by this instance during one minute periodsMemoryUtilization— percentage of allocated memory used by this instance during one minute periods
Service metrics:
CPUUtilization— aggregated CPU usage across all instancesMemoryUtilization— aggregated memory usage across all instancesRequests— total HTTP requests received by the service2xxStatusResponses,4xxStatusResponses,5xxStatusResponses— count of responses by status code categoryRequestLatency— time in milliseconds to process HTTP requestsActiveInstances— number of instances currently processing requestsConcurrency— approximate number of concurrent requests being handled
The gap: CloudWatch does not track cold start duration, time to first byte after scaling from zero, or per endpoint latency. These require application level instrumentation.
Step 2: Create a CloudWatch Dashboard for App Runner Metrics
A single dashboard with the right metrics saves hours during incidents. Most teams realize too late that switching between CloudWatch metric pages during a production issue wastes cognitive load.
Open the CloudWatch console and navigate to Dashboards → Create dashboard. Name it after your service, for example app-runner-api-production.
Add the following widgets:
Widget 1: Active Instances (line graph)
- Metric:
AWS/AppRunner→ActiveInstances - Statistic: Average
- Period: 1 minute
- Why: Shows scaling behavior. If this stays at your minimum instance count during traffic spikes, your service is underprovisioned.
Widget 2: Request Count (line graph)
- Metric:
AWS/AppRunner→Requests - Statistic: Sum
- Period: 1 minute
- Why: Confirms traffic volume. Correlate with ActiveInstances to see if scaling lags behind load.
Widget 3: Request Latency (line graph)
- Metric:
AWS/AppRunner→RequestLatency - Statistics: Average, p50, p99
- Period: 1 minute
- Why: Average latency hides outliers. p99 shows what the slowest 1% of users experience.
Widget 4: Error Rate (stacked area)
- Metrics:
4xxStatusResponses,5xxStatusResponses - Statistic: Sum
- Period: 1 minute
- Why: Immediate visibility into client errors versus server errors.
Widget 5: CPU and Memory Utilization (dual axis line graph)
- Metrics:
CPUUtilization,MemoryUtilization(service level) - Statistic: Average
- Period: 1 minute
- Why: Resource saturation often precedes performance degradation.
Save the dashboard. Bookmark the URL. During incidents, load this dashboard first before opening individual metric pages.
Step 3: Detect Cold Starts Using ActiveInstances Metric
Cold starts happen when App Runner scales your service down to zero instances after a period of no traffic, then provisions a new instance when the next request arrives. The delay between request arrival and instance readiness is the cold start duration.
CloudWatch does not publish a ColdStartDuration metric. But you can infer cold starts by watching the ActiveInstances metric drop to zero and then spike back up.
Create a CloudWatch alarm to detect when your service scales to zero:
aws cloudwatch put-metric-alarm \
--alarm-name app-runner-scaled-to-zero \
--alarm-description "Alert when App Runner service has zero active instances" \
--metric-name ActiveInstances \
--namespace AWS/AppRunner \
--statistic Average \
--period 60 \
--threshold 0 \
--comparison-operator LessThanOrEqualToThreshold \
--evaluation-periods 2 \
--dimensions Name=ServiceName,Value=your-service-name
Replace your-service-name with your App Runner service name from the console.
This alarm fires when ActiveInstances stays at zero for two consecutive minutes. If your service receives even sporadic traffic, this means App Runner scaled down completely.
To measure cold start impact on user experience, correlate the alarm timestamp with your application logs. Search for the first request timestamp after the alarm fired. The gap between alarm timestamp and first successful request log is your cold start window.
For production services that cannot tolerate cold starts, set your minimum instance count to 1 in App Runner service settings. This keeps one instance warm at all times but adds baseline cost.
Step 4: Enable Application Logs and Send Them to CloudWatch Logs
App Runner automatically creates two CloudWatch log groups for every service:
/aws/apprunner/{service-name}/{service-id}/application— your application’s stdout and stderr/aws/apprunner/{service-name}/{service-id}/service— App Runner lifecycle events (deployments, scaling, health checks)
Navigate to CloudWatch Logs → Log groups and confirm both exist. If not, your App Runner service may have logging disabled in its service settings.
Application logs are where you find request level details that CloudWatch metrics miss. For example:
- Which API endpoint is slow
- Database query durations
- External API call latencies
- Specific error messages and stack traces
Most container applications log to stdout by default, which App Runner captures automatically. If your application writes logs to a file instead, App Runner will not capture them. Reconfigure your app to log to stdout.
Example Python Flask app logging configuration:
import logging
import sys
logging.basicConfig(
stream=sys.stdout,
level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s'
)
Example Node.js Express app:
const morgan = require('morgan');
app.use(morgan('combined'));
Once logs flow to CloudWatch, use CloudWatch Logs Insights to query them. Open CloudWatch Logs → Logs Insights, select your application log group, and run:
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 100
This surfaces the 100 most recent error messages. Adjust the filter pattern to match your app’s log format.
Step 5: Instrument Your Application with OpenTelemetry for Deep Visibility
CloudWatch metrics tell you something is wrong. Application logs tell you what the error message says. Neither tells you which code path caused the problem or how a slow request propagated across services.
Distributed tracing fills that gap. By instrumenting your App Runner application with OpenTelemetry, you get span level visibility into every function call, database query, and external HTTP request.
Install the OpenTelemetry SDK for your language. Example for Python:
pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-flask opentelemetry-exporter-otlp
Add instrumentation to your application:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
trace.set_tracer_provider(TracerProvider())
otlp_exporter = OTLPSpanExporter(endpoint="your-otlp-endpoint:4317")
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(otlp_exporter))
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
Replace your-otlp-endpoint with your observability backend. For teams using infrastructure monitoring platforms that support OpenTelemetry natively, this connects your App Runner traces directly to the same system tracking your EC2 instances, RDS databases, and Kubernetes clusters.
CubeAPM runs inside your VPC and accepts OpenTelemetry traces without sending data outside your cloud. Deploy CubeAPM in the same region as your App Runner service to avoid cross region egress costs. Configure your App Runner service’s environment variables to point at the CubeAPM collector endpoint.
Datadog and New Relic also support OpenTelemetry but require sending telemetry to their SaaS endpoints, which incurs AWS data transfer costs at approximately $0.09 per GB for cross region traffic.
Once traces flow, you can:
- See exact latency breakdown for each database query in a slow request
- Identify which external API call timed out
- Trace errors back to the specific line of code that threw an exception
- Correlate App Runner scaling events with request performance changes
Step 6: Set Up Alerts for Critical Metrics
Dashboards require someone to watch them. Alerts notify you when thresholds break.
Create CloudWatch alarms for these scenarios:
High error rate:
aws cloudwatch put-metric-alarm \
--alarm-name app-runner-high-5xx-rate \
--metric-name 5xxStatusResponses \
--namespace AWS/AppRunner \
--statistic Sum \
--period 300 \
--threshold 10 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 1 \
--dimensions Name=ServiceName,Value=your-service-name
This fires if your service returns more than 10 server errors in any 5 minute window.
High latency:
aws cloudwatch put-metric-alarm \
--alarm-name app-runner-high-latency \
--metric-name RequestLatency \
--namespace AWS/AppRunner \
--statistic Average \
--period 60 \
--threshold 1000 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--dimensions Name=ServiceName,Value=your-service-name
This fires if average request latency exceeds 1000 milliseconds for two consecutive minutes.
Resource saturation:
aws cloudwatch put-metric-alarm \
--alarm-name app-runner-high-cpu \
--metric-name CPUUtilization \
--namespace AWS/AppRunner \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--dimensions Name=ServiceName,Value=your-service-name
This fires if CPU utilization stays above 80% for two consecutive 5 minute periods, signaling your instances are undersized or your service should scale out.
Route these alarms to an SNS topic connected to Slack, PagerDuty, or email.
Step 7: Monitor Cold Start Impact on Real User Experience
Cold starts affect real users differently depending on when they occur. A cold start during low traffic at 3 AM delays one user by a few seconds. A cold start during peak traffic when App Runner is scaling up delays dozens of users.
To measure cold start impact accurately, track time to first byte for requests that arrive after a scaling event.
If you instrumented your application with OpenTelemetry in Step 5, add a custom span attribute to mark the first request after instance startup:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
@app.before_request
def mark_first_request():
if not hasattr(app, 'started'):
app.started = True
span = trace.get_current_span()
span.set_attribute("app.cold_start", True)
Query your APM tool for spans tagged app.cold_start=true and compare their latency distribution against normal requests. If cold start requests have p99 latency 3x higher than normal, and you see cold starts happening during peak traffic, increase your minimum instance count.
For teams using AWS monitoring tools that do not support custom span attributes, log a structured message on application startup and search CloudWatch Logs Insights:
fields @timestamp, @message
| filter @message like /cold_start=true/
| stats count() by bin(5m)
This shows how many cold starts occurred in each 5 minute window over the selected time range.
Troubleshooting Common Issues
Issue: ActiveInstances metric shows zero but requests are succeeding
This happens when CloudWatch metric delivery lags behind real time. App Runner publishes metrics to CloudWatch with up to 60 seconds delay. If you see zero active instances but your application logs show recent requests, wait 2 minutes and refresh the metric graph.
Issue: High CPU utilization but low request count
Your application may have a background job, memory leak, or inefficient code path consuming CPU even when idle. Check application logs for scheduled tasks. Profile your application using language specific tools (e.g., py-spy for Python, Node.js built-in profiler) to find CPU hotspots.
Issue: Request latency spikes correlate with scaling events
App Runner provisions new instances when traffic increases, but new containers take time to warm up (load application code, establish database connections, populate caches). During this warm up window, requests route to instances that are not fully ready. Solutions: increase minimum instance count to keep instances warm, optimize application startup time, or implement health check endpoints that block traffic until the container is fully initialized.
Issue: 5xx errors spike then disappear after a few seconds
This pattern often indicates a dependency failure database timeout, external API unreachable, or memory exhaustion. Check application logs for error messages during the spike window. Correlate with infrastructure metrics (RDS CPU, ElastiCache memory, external API status pages).
Issue: Cannot find App Runner service logs in CloudWatch
Verify logging is enabled in App Runner service settings. Navigate to App Runner console → Services → your service → Configuration → Observability. Ensure “Application logs” is set to “CloudWatch Logs”. If it was disabled, enable it and redeploy the service.
Issue: OpenTelemetry traces not appearing in APM tool
Check three things: network connectivity from App Runner to your collector endpoint (use VPC endpoints if your collector is in a private subnet), correct OTLP exporter endpoint URL in application environment variables, and IAM permissions if your collector requires AWS credentials for ingestion.
Conclusion
Monitoring AWS App Runner services requires combining CloudWatch’s infrastructure metrics with application level tracing and structured logging. CloudWatch alone shows symptoms (high CPU, error spikes) but not causes. Application instrumentation with OpenTelemetry surfaces the exact query, API call, or code path responsible.
The monitoring strategy that works: create a CloudWatch dashboard with ActiveInstances, Requests, RequestLatency, and error counts. Set alarms for error rate, latency, and resource saturation. Instrument your application with OpenTelemetry to trace request flows and correlate with infrastructure signals. Track cold starts by monitoring ActiveInstances drops to zero and measuring first request latency after scaling events.
For teams running multiple App Runner services alongside EC2, ECS, or Kubernetes workloads, using an observability platform that correlates all signals in one place (traces, logs, metrics, events) reduces context switching during incidents. CubeAPM runs inside your VPC and ingests telemetry from App Runner, EC2, RDS, and Lambda without data leaving your infrastructure, making it easier to meet compliance requirements and avoid cross region data transfer costs.
Disclaimer: The information in this article reflects the latest details available at the time of publication and may change as technologies and products evolve. Features, pricing, and plan limits can change over time. Always verify the latest information directly with the vendor before making purchasing or deployment decisions.
Frequently Asked Questions
What metrics does AWS App Runner expose in CloudWatch?
App Runner publishes CPU utilization, memory utilization, request count, request latency, HTTP status code counts (2xx, 4xx, 5xx), active instances, and concurrency to CloudWatch in the AWS/AppRunner namespace.
How do I detect cold starts in AWS App Runner?
Monitor the ActiveInstances metric. When it drops to zero and then spikes back up, a cold start occurred. Measure the time gap between zero instances and the first successful request log to quantify cold start duration.
Can I use OpenTelemetry with AWS App Runner?
Yes. Instrument your application with the OpenTelemetry SDK for your language and configure the OTLP exporter to send traces to any OpenTelemetry compatible backend, including CubeAPM, Datadog, or New Relic.
How do I reduce cold start impact in App Runner?
Set your minimum instance count to 1 or higher in App Runner service configuration. This keeps at least one container warm at all times, eliminating cold starts but adding baseline compute cost.
Where are App Runner logs stored?
App Runner automatically sends application logs (stdout/stderr) and service logs (deployment events, scaling events) to CloudWatch Logs. Log groups are created automatically in the format /aws/apprunner/{service-name}/{service-id}/application and /aws/apprunner/{service-name}/{service-id}/service.
What is the difference between instance level and service level metrics?
Instance level metrics track individual scaling units. Service level metrics aggregate across all active instances. For example, if 3 instances are running, you get 3 separate CPUUtilization streams at instance level and one aggregated CPUUtilization metric at service level.
How do I troubleshoot high latency in App Runner services?
Start with CloudWatch RequestLatency metric to confirm the spike. Check application logs for slow queries or external API timeouts during the spike window. If you instrumented with OpenTelemetry, trace individual slow requests to see exact latency breakdown across database calls, HTTP requests, and function execution.





