Aws
AwsIntermediate

AWS App Runner: Simplified Container and Source Code Deployment

DevHub Team
5 min read
App RunnerContainersServerlessDevOps

TL;DR

Learn how to use AWS App Runner to deploy and run containerized web applications and APIs with automatic scaling and load balancing

AWS App Runner: Simplified Container and Source Code Deployment

AWS App Runner is a fully managed service that makes it easy to deploy containerized web applications and APIs at scale. This guide explores its features, use cases, and best practices.

$1

``mermaid

graph TB

subgraph AppRunner["AWS App Runner"]

direction TB

Source["Source Code/Container"]

Build["Build Process"]

Deploy["Deployment"]

Run["Runtime"]

end

subgraph Features["Service Features"]

direction TB

AS["Auto Scaling"]

LB["Load Balancing"]

SSL["SSL/TLS"]

VPC["VPC Integration"]

end

Source --> Build

Build --> Deploy

Deploy --> Run

Run --> Features

classDef aws fill:#FF9900,stroke:#232F3E,color:#232F3E

class AppRunner,Features aws

`

$1

$1

Example Node.js application:

`javascript

// app.js

const express = require('express');

const app = express();

const port = process.env.PORT || 3000;

app.get('/', (req, res) => {

res.json({ message: 'Hello from App Runner!' });

});

app.listen(port, () => {

console.log(Server running on port ${port});

});

`

Configuration file:

`yaml

apprunner.yaml

version: 1.0

runtime: nodejs12

build:

commands:

pre-build:

- npm install

build:

- npm run build

run:

command: node app.js

network:

port: 3000

`

$1

Dockerfile example:

`dockerfile

FROM node:14-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["node", "app.js"]

`

$1

$1

`typescript

import * as apprunner from '@aws-cdk/aws-apprunner-alpha';

import * as ecr from 'aws-cdk-lib/aws-ecr';

// Create App Runner service from container

const service = new apprunner.Service(this, 'Service', {

source: apprunner.Source.fromEcr({

imageConfiguration: {

port: 3000,

},

repository: ecr.Repository.fromRepositoryName(

this,

'Repo',

'my-app-repo'

),

tag: 'latest',

}),

});

`

$1

`yaml

Resources:

AppRunnerService:

Type: AWS::AppRunner::Service

Properties:

ServiceName: my-web-app

SourceConfiguration:

AuthenticationConfiguration:

AccessRoleArn: !GetAtt AppRunnerECRAccessRole.Arn

ImageRepository:

ImageIdentifier: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/my-app:latest

ImageConfiguration:

Port: 3000

InstanceConfiguration:

Cpu: 1024

Memory: 2048

`

$1

`json

{

"AutoScalingConfigurationName": "production-config",

"MaxConcurrency": 100,

"MinSize": 1,

"MaxSize": 25,

"Tags": [

{

"Key": "Environment",

"Value": "Production"

}

]

}

`

$1

$1

`yaml

Resources:

AppRunnerVpcConnector:

Type: AWS::AppRunner::VpcConnector

Properties:

VpcConnectorName: my-vpc-connector

Subnets:

- subnet-123456

- subnet-789012

SecurityGroups:

- sg-123456

`

$1

`json

{

"Version": "2012-10-17",

"Statement": [

{

"Effect": "Allow",

"Action": [

"ecr:GetAuthorizationToken",

"ecr:BatchCheckLayerAvailability",

"ecr:GetDownloadUrlForLayer",

"ecr:BatchGetImage"

],

"Resource": "*"

}

]

}

`

$1

$1

`python

import boto3

import datetime

cloudwatch = boto3.client('cloudwatch')

def get_app_metrics():

response = cloudwatch.get_metric_statistics(

Namespace='AWS/AppRunner',

MetricName='RequestCount',

Dimensions=[

{

'Name': 'ServiceName',

'Value': 'my-web-app'

}

],

StartTime=datetime.datetime.utcnow() - datetime.timedelta(hours=1),

EndTime=datetime.datetime.utcnow(),

Period=60,

Statistics=['Sum']

)

return response

`

$1

`javascript

const AWS = require('aws-sdk');

const cloudwatch = new AWS.CloudWatch();

async function publishCustomMetric(value) {

await cloudwatch.putMetricData({

Namespace: 'MyAppMetrics',

MetricData: [{

MetricName: 'BusinessTransactions',

Value: value,

Unit: 'Count',

Dimensions: [{

Name: 'ServiceName',

Value: 'my-web-app'

}]

}]

}).promise();

}

`

$1

$1

`yaml

name: Deploy to App Runner

on:

push:

branches: [ main ]

jobs:

deploy:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v2

- name: Configure AWS credentials

uses: aws-actions/configure-aws-credentials@v1

with:

aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}

aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

aws-region: us-west-2

- name: Login to Amazon ECR

id: login-ecr

uses: aws-actions/amazon-ecr-login@v1

- name: Build and push image

env:

ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}

ECR_REPOSITORY: my-app

IMAGE_TAG: latest

run: |

docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .

docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

- name: Deploy to App Runner

run: |

aws apprunner update-service \

--service-arn ${{ secrets.APPRUNNER_SERVICE_ARN }} \

--source-configuration imageRepository={imageIdentifier=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG}

`

$1

$1

`yaml

Optimal resource configuration

Resources:

AppRunnerService:

Type: AWS::AppRunner::Service

Properties:

InstanceConfiguration:

Cpu: 1 vCPU

Memory: 2 GB

AutoScalingConfigurationArn: !Ref AutoScalingConfig

HealthCheckConfiguration:

Path: /health

Protocol: TCP

Interval: 10

Timeout: 5

HealthyThreshold: 2

UnhealthyThreshold: 3

`

$1

`javascript

const express = require('express');

const app = express();

const cache = require('memory-cache');

app.get('/api/data', (req, res) => {

const key = req.url;

const cachedResponse = cache.get(key);

if (cachedResponse) {

return res.json(cachedResponse);

}

// Generate response

const response = generateResponse();

cache.put(key, response, 300000); // Cache for 5 minutes

res.json(response);

});

`

$1

$1

`json

{

"AutoScalingConfigurationName": "cost-optimized",

"MaxConcurrency": 50,

"MinSize": 1,

"MaxSize": 10,

"Tags": [

{

"Key": "CostCenter",

"Value": "WebApp"

}

]

}

`

$1

`yaml

Resource optimization example

Resources:

OptimizedService:

Type: AWS::AppRunner::Service

Properties:

InstanceConfiguration:

Cpu: "1024" # 1 vCPU

Memory: "2048" # 2 GB

AutoScalingConfigurationArn: !Ref CostOptimizedConfig

ObservabilityConfiguration:

ObservabilityEnabled: true

TraceConfiguration:

Vendor: AWSXRAY

``

$1

$1

  • Use health checks
  • Implement graceful shutdown
  • Handle environment variables
  • Optimize container size
  • $1

  • Enable observability
  • Implement proper logging
  • Use infrastructure as code
  • Regular monitoring and alerts
  • $1

  • Use VPC connectivity
  • Implement least privilege
  • Enable encryption
  • Regular security updates
  • $1

    Common issues and solutions:

    1. Deployment Failures

    - Check build logs

    - Verify source configuration

    - Validate container image

    - Check resource limits

    2. Performance Issues

    - Monitor metrics

    - Check resource utilization

    - Validate auto-scaling

    - Review application logs

    $1

    1. [AWS App Runner Documentation](https://docs.aws.amazon.com/apprunner/)

    2. [App Runner Best Practices](https://docs.aws.amazon.com/apprunner/latest/dg/best-practices.html)

    3. [Pricing Information](https://aws.amazon.com/apprunner/pricing/)

    4. [App Runner Workshop](https://www.apprunnerworkshop.com/)

    5. [Container Security](https://docs.aws.amazon.com/apprunner/latest/dg/security.html)

    6. [Auto Scaling Guide](https://docs.aws.amazon.com/apprunner/latest/dg/manage-autoscaling.html)

  • [AWS Lambda Container Support: A Comprehensive Guide](/posts/aws/lambda-container-guide) - Explore serverless container deployments
  • [AWS ECS vs EKS in 2024: A Comprehensive Comparison](/posts/aws/ecs-vs-eks-2024) - Compare container orchestration services
  • [AWS Graviton3: Next-Generation ARM-based Computing](/posts/aws/graviton3) - Learn about ARM-based computing for containers
  • [Introduction to AWS Fargate: Serverless Container Orchestration](/posts/aws/introduction-to-aws-fargate) - Discover serverless container orchestration
  • Why This Matters

    Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.

    Trade-offs to Consider

    Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.

    When NOT to Use This

    Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.

    Decision Framework

    Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.