Introduction to Troubleshooting AWS App Runner
AWS App Runner is a fully managed container application service that lets you build, deploy, and run containerized web applications and microservices without managing infrastructure. While App Runner abstracts away much of the operational overhead, developers still encounter issues during deployment, runtime, scaling, and networking. This tutorial walks through the most common App Runner problems and provides practical, tested solutions.
Why Troubleshooting App Runner Matters
Because App Runner is opinionated and managed, you have less visibility into the underlying infrastructure than you would with Amazon ECS or EKS. When something breaks, you need to know exactly where to look — logs, metrics, health checks, and source configuration — to identify root causes quickly. A systematic troubleshooting approach reduces downtime, improves deployment reliability, and helps you avoid repeated misconfigurations.
Common Issue 1: Deployment Failures
Deployment failures are the most frequent issue developers face with App Runner. They typically occur during the build phase (when App Runner builds your image from source) or the deploy phase (when App Runner pulls and starts your image).
Diagnosing Build Failures
When using source-code-based deployments, App Runner builds your container image using its build pipeline. Build failures usually stem from missing dependencies, incorrect Dockerfile syntax, or unsupported base images. To diagnose, navigate to the App Runner console, select your service, and review the deployment logs.
Common build failure causes include:
- Missing or incorrect
Dockerfilein the repository root - Private dependencies that require authentication
- Base images that exceed App Runner's size limits
- Build commands that fail silently in CI but work locally
Here is a minimal, App Runner-compatible Dockerfile that avoids common pitfalls:
# Use a lightweight, officially supported base image
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files first for better layer caching
COPY package*.json ./
# Install production dependencies only
RUN npm ci --only=production
# Copy application source
COPY . .
# Expose the port App Runner expects
EXPOSE 8080
# Use a non-root user for security
USER node
# Start the application
CMD ["node", "server.js"]
Diagnosing Deploy Failures
If the build succeeds but deployment fails, the issue is usually with the container runtime. App Runner performs a health check on your application — if it does not respond within the configured timeout, the deployment rolls back. Check the following:
- Ensure your app listens on the port specified in the App Runner configuration (default is 8080)
- Verify the application binds to
0.0.0.0rather thanlocalhostor127.0.0.1 - Confirm the health check path returns a 200 HTTP status code
The following Node.js example shows correct binding behavior:
const express = require('express');
const app = express();
const PORT = process.env.PORT || 8080;
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy' });
});
app.get('/', (req, res) => {
res.send('App Runner service is running');
});
// Bind to all interfaces — critical for App Runner
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server listening on port ${PORT}`);
});
Common Issue 2: Health Check Failures
App Runner continuously monitors your service through health checks. If health checks fail repeatedly, App Runner marks the service as unhealthy and may restart or replace instances. Misconfigured health checks are a leading cause of unexpected restarts.
Configuring Health Check Parameters
You can configure the health check interval, timeout, healthy threshold, and unhealthy threshold. The defaults are often too aggressive for applications with slow startup times, such as Java Spring Boot services or applications that perform database migrations on boot.
aws apprunner update-service \
--service-arn arn:aws:apprunner:us-east-1:123456789012:service/my-service/abc123 \
--health-check-configuration \
Protocol=HTTP,Path=/health,Interval=20,Timeout=10,HealthyThreshold=3,UnhealthyThreshold=5
Best practices for health check endpoints:
- Return 200 only when the application is truly ready to serve traffic
- Keep the endpoint lightweight — no database queries or external API calls
- Separate liveness from readiness if your framework supports it
- Set the unhealthy threshold high enough to accommodate startup time
Common Issue 3: VPC and Networking Issues
By default, App Runner services are publicly accessible. When you configure a service to run in a VPC — required for accessing private resources like RDS databases or internal APIs — networking issues frequently arise.
Common VPC Configuration Problems
The most common VPC-related issues include:
- Subnets without internet access (no NAT gateway) preventing image pulls from ECR
- Security groups blocking required outbound traffic
- Incorrect route table configurations
- DNS resolution failures for private hosted zones
When configuring a VPC for App Runner, ensure your subnets have a route to a NAT gateway if you need outbound internet access. The following Terraform snippet shows a correct configuration:
resource "aws_apprunner_service" "example" {
service_name = "my-app-runner-service"
source_configuration {
image_repository {
image_configuration {
port = "8080"
}
image_identifier = "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest"
image_repository_type = "ECR"
}
}
network_configuration {
egress_configuration {
egress_type = "VPC"
}
ingress_configuration {
is_publicly_accessible = false
}
ip_configuration {
assign_public_ip = true
subnet_ids = ["subnet-abc123", "subnet-def456"]
security_group_ids = ["sg-abc123"]
}
}
instance_configuration {
cpu = "1024"
memory = "2048"
}
}
Debugging Connectivity from App Runner
If your App Runner service cannot reach a private resource, verify the security group rules attached to the App Runner service allow outbound traffic to the target. Also confirm the target resource's security group allows inbound traffic from the App Runner security group. A quick diagnostic approach is to add a temporary debug endpoint that attempts a TCP connection to your target:
const net = require('net');
app.get('/debug/connectivity', async (req, res) => {
const host = process.env.DB_HOST;
const port = parseInt(process.env.DB_PORT || '5432');
const socket = new net.Socket();
socket.setTimeout(5000);
socket.on('connect', () => {
socket.destroy();
res.json({ host, port, status: 'reachable' });
});
socket.on('error', (err) => {
res.status(500).json({ host, port, status: 'unreachable', error: err.message });
});
socket.on('timeout', () => {
socket.destroy();
res.status(500).json({ host, port, status: 'timeout' });
});
socket.connect(port, host);
});
Remember to remove this debug endpoint before deploying to production.
Common Issue 4: Performance and Latency Problems
App Runner auto-scales based on concurrent requests, but misconfigured scaling settings or undersized instances can cause latency spikes and throttling.
Tuning Auto Scaling Configuration
App Runner scales between a minimum and maximum number of instances based on the number of concurrent requests per instance. The default values may not suit your workload. For latency-sensitive APIs, lower the concurrency threshold so App Runner scales out sooner:
aws apprunner update-service \
--service-arn arn:aws:apprunner:us-east-1:123456789012:service/my-service/abc123 \
--auto-scaling-configuration-arn \
arn:aws:apprunner:us-east-1:123456789012:autoscalingconfiguration/high-throughput/1
To create a custom auto scaling configuration:
aws apprunner create-auto-scaling-configuration \
--auto-scaling-configuration-name "high-throughput" \
--max-concurrency 50 \
--max-size 20 \
--min-size 2
Choosing the Right Instance Size
App Runner offers CPU and memory combinations ranging from 1 vCPU / 2 GB to 4 vCPU / 12 GB. If your application is CPU-bound (image processing, data transformation), increase CPU. If it is memory-bound (caching, large payloads), increase memory. Monitor CPU and memory utilization using CloudWatch metrics:
aws cloudwatch get-metric-statistics \
--namespace AWS/AppRunner \
--metric-name CPUUtilization \
--dimensions Name=ServiceName,Value=my-service \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-02T00:00:00Z \
--period 300 \
--statistics Average,Maximum
Common Issue 5: Environment Variables and Secrets
Incorrectly configured environment variables and secrets are a frequent source of runtime errors. App Runner supports both plaintext environment variables and secrets stored in AWS Secrets Manager or Parameter Store.
Referencing Secrets Correctly
When referencing a secret, the value must follow the ARN format. A common mistake is using the secret name instead of the ARN. Here is the correct format:
aws apprunner create-service \
--service-name my-service \
--source-configuration file://source-config.json \
--instance-configuration file://instance-config.json
The instance-config.json file should look like this:
{
"Cpu": "1024",
"Memory": "2048",
"InstanceRoleArn": "arn:aws:iam::123456789012:role/AppRunnerInstanceRole",
"RuntimeEnvironmentVariables": {
"NODE_ENV": "production",
"LOG_LEVEL": "info"
},
"RuntimeEnvironmentSecrets": {
"DATABASE_URL": "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/db-url-abc123",
"API_KEY": "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/api-key-def456"
}
}
Ensure the instance role has permission to read the secrets:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/*"
]
}
]
}
Common Issue 6: ECR Permissions and Image Pull Failures
When using an ECR-based source, App Runner needs permission to pull the image. If the access role lacks the necessary permissions, the deployment fails with an image pull error.
Setting Up the Correct Access Role
The App Runner access role must include ecr:GetDownloadUrlForLayer, ecr:BatchGetImage, and ecr:GetAuthorizationToken permissions. Use the AWS-managed AppRunnerECRAccessRole policy or create a custom one:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:GetAuthorizationToken"
],
"Resource": "*"
}
]
}
Also verify that your ECR repository policy allows the App Runner access role to pull images, especially if the repository is in a different account.
Common Issue 7: Logging and Observability Gaps
App Runner automatically sends logs to CloudWatch Logs, but many developers struggle to find them or miss important log streams. Understanding the log structure is essential for effective troubleshooting.
Accessing Application Logs
App Runner creates two log groups: one for deployment logs and one for application logs. The application log group follows the pattern /aws/apprunner/<service-name>/<service-id>/application. You can query these logs using CloudWatch Logs Insights:
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 50
Implementing Structured Logging
Structured logging makes it easier to search and filter logs. Use JSON-formatted log output in your application:
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console({
consoleWarnLevels: ['error', 'warn']
})
]
});
// Usage
logger.info('Request received', {
method: req.method,
path: req.path,
requestId: req.headers['x-request-id']
});
logger.error('Database connection failed', {
host: process.env.DB_HOST,
error: err.message,
stack: err.stack
});
Best Practices for App Runner Reliability
Beyond fixing specific issues, following these best practices will help you avoid problems before they occur:
- Use infrastructure as code: Define your App Runner services using Terraform, CloudFormation, or the AWS CDK to ensure reproducible configurations and easy rollbacks.
- Implement graceful shutdown: Handle
SIGTERMsignals to close database connections and finish in-flight requests before the container exits. - Set up CloudWatch alarms: Monitor key metrics like
RequestCount,Latency,2xxResponseCount, and5xxResponseCountto detect issues proactively. - Use deployment checkpoints: App Runner supports manual deployment approvals for source-code-based services — use them for production environments.
- Tag your resources: Apply consistent tags for cost tracking and resource organization.
- Test locally with the same image: Build and test your container image locally before pushing to ECR to catch runtime issues early.
- Keep images small: Use multi-stage builds and minimal base images to reduce build time and improve deployment speed.
Here is an example of a graceful shutdown implementation in Node.js:
const server = app.listen(PORT, '0.0.0.0', () => {
console.log(`Server listening on port ${PORT}`);
});
let isShuttingDown = false;
app.use((req, res, next) => {
if (isShuttingDown) {
res.set('Connection', 'close');
return res.status(503).json({ error: 'Server is shutting down' });
}
next();
});
process.on('SIGTERM', async () => {
console.log('SIGTERM received, starting graceful shutdown');
isShuttingDown = true;
server.close(async () => {
console.log('HTTP server closed');
// Close database connections
await database.close();
process.exit(0);
});
// Force exit after 30 seconds
setTimeout(() => {
console.error('Forcing shutdown after timeout');
process.exit(1);
}, 30000);
});
Conclusion
Troubleshooting AWS App Runner requires a methodical approach that combines log analysis, configuration review, and an understanding of how the service manages deployments, networking, and scaling. By addressing the common issues covered in this tutorial — deployment failures, health check misconfigurations, VPC connectivity problems, performance tuning, secrets management, ECR permissions, and observability gaps — you can significantly reduce downtime and improve the reliability of your App Runner services. The key is to invest in proper configuration upfront, implement robust monitoring and logging, and follow best practices for container design and infrastructure as code. With these strategies in place, App Runner becomes a powerful, low-maintenance platform for running production workloads at scale.