← Back to DevBytes

Troubleshooting App Runner: Common Issues and Solutions

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:

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:

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:

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:

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:

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.

— Ad —

Google AdSense will appear here after approval

← Back to all articles