Full Stack • Java • System Design • Cloud • AI Engineering

Spring Boot with AWS App Runner

Learn how to deploy a Spring Boot application on AWS App Runner step by step using Docker, Amazon ECR, environment variables, health checks, logs, auto deployments, and production best practices.


Introduction

In the previous articles, we deployed Spring Boot applications using:

  • Amazon EC2
  • AWS Elastic Beanstalk
  • AWS ECS Fargate
  • AWS EKS

In this article, we will learn how to deploy a Spring Boot application on AWS App Runner.

AWS App Runner is a managed service for running web applications and APIs from source code or container images. It can handle infrastructure, load balancing, HTTPS endpoint, scaling, deployment, and logs with less setup compared to ECS or EKS.

Important: AWS documentation currently states that AWS App Runner is no longer open to new customers. Existing customers can continue using the service. This article is useful for existing App Runner users and for learning managed container deployment concepts.


What You Will Learn

  • What is AWS App Runner?
  • When to use App Runner
  • App Runner vs EC2 vs ECS vs EKS
  • How to create a Spring Boot REST API
  • How to Dockerize Spring Boot
  • How to push Docker image to Amazon ECR
  • How to deploy ECR image to App Runner
  • How to configure environment variables
  • How to configure health checks
  • How to view logs
  • How to deploy new versions
  • Common errors and fixes
  • Production best practices

What is AWS App Runner?

AWS App Runner is a managed container application service.

It helps developers deploy web applications and APIs without manually managing:

  • EC2 servers
  • Load balancers
  • Auto Scaling groups
  • Container orchestration
  • SSL endpoint setup
  • Deployment pipeline basics

You provide either:

  • Source code repository
  • Container image from ECR or ECR Public

App Runner creates and runs the service for you.


App Runner Architecture

flowchart TD
    DEV[Developer Machine]
    APP[Spring Boot Application]
    JAR[Build JAR]
    IMG[Docker Image]
    ECR[Amazon ECR]
    APR[AWS App Runner Service]
    CW[CloudWatch Logs]
    USER[User Browser]

    DEV --> APP
    APP --> JAR
    JAR --> IMG
    IMG --> ECR
    ECR --> APR
    APR --> CW
    USER --> APR

Production Style Architecture

flowchart TD
    U[Users]
    HTTPS[App Runner HTTPS Endpoint]
    APP[AWS App Runner Service]
    ECR[Amazon ECR]
    SM[Secrets Manager]
    SSM[SSM Parameter Store]
    RDS[(Amazon RDS)]
    CW[CloudWatch Logs]

    U --> HTTPS
    HTTPS --> APP
    ECR --> APP
    APP --> SM
    APP --> SSM
    APP --> RDS
    APP --> CW

App Runner vs Other AWS Deployment Options

Service Best For Server Management Container Required
EC2 Learning VM deployment and full control Yes No
Elastic Beanstalk Managed app deployment Partial Optional
ECS Fargate Production containers No Yes
EKS Kubernetes workloads Partial Yes
App Runner Simple managed web apps and APIs No Optional

Important App Runner Concepts

Concept Meaning
Service Running App Runner application
Source Code repository or container image
ECR Private container image registry
Port Container listening port
Health Check Endpoint used to verify application health
Auto Deployment Redeploy when new image is pushed
Instance Role IAM role used by app to access AWS resources
Access Role IAM role used by App Runner to pull private ECR image

Prerequisites

Before starting, you need:

AWS Account
AWS CLI
Docker Desktop
Java 17 or Java 21
Maven
Spring Boot project
Amazon ECR access
App Runner access
IAM permissions

Verify Java:

java -version

Output:

openjdk version "17.0.x"

Verify Maven:

mvn -version

Output:

Apache Maven 3.x.x

Verify Docker:

docker --version

Output:

Docker version 25.x.x

Verify AWS CLI:

aws --version

Output:

aws-cli/2.x.x

Step 1: Create Spring Boot Application

Create a Spring Boot project with:

Spring Web
Spring Boot Actuator
Java 17
Maven

Project structure:

springboot-apprunner-demo
 ┣ src/main/java/com/codewithvenu/apprunner
 ┃ ┣ AppRunnerDemoApplication.java
 ┃ ┗ controller
 ┃   ┗ HelloController.java
 ┣ src/main/resources
 ┃ ┗ application.yml
 ┣ Dockerfile
 ┗ pom.xml

Step 2: Create Main Class

package com.codewithvenu.apprunner;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AppRunnerDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppRunnerDemoApplication.class, args);
    }
}

Step 3: Create REST Controller

Create file:

src/main/java/com/codewithvenu/apprunner/controller/HelloController.java
package com.codewithvenu.apprunner.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.Map;

@RestController
public class HelloController {

    @Value("${spring.application.name}")
    private String appName;

    @Value("${app.environment:local}")
    private String environment;

    @GetMapping("/")
    public Map<String, Object> home() {
        return Map.of(
                "message", "Spring Boot application is running on AWS App Runner",
                "application", appName,
                "environment", environment,
                "timestamp", LocalDateTime.now().toString()
        );
    }

    @GetMapping("/health")
    public Map<String, String> health() {
        return Map.of("status", "UP");
    }

    @GetMapping("/version")
    public Map<String, String> version() {
        return Map.of("version", "1.0.0");
    }
}

Step 4: Configure application.yml

Create file:

src/main/resources/application.yml
server:
  port: ${PORT:8080}

spring:
  application:
    name: springboot-apprunner-demo

app:
  environment: ${APP_ENVIRONMENT:local}

management:
  endpoints:
    web:
      exposure:
        include: health,info

Important:

App Runner uses PORT as a reserved environment variable. Your application should read it, but you should not manually create an environment variable named PORT in App Runner configuration.


Step 5: Create pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.codewithvenu</groupId>
    <artifactId>springboot-apprunner-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-apprunner-demo</name>
    <description>Spring Boot deployment on AWS App Runner</description>

    <properties>
        <java.version>17</java.version>
        <spring.boot.version>3.3.0</spring.boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring.boot.version}</version>
            </plugin>

        </plugins>
    </build>

</project>

Step 6: Test Application Locally

Run:

mvn spring-boot:run

Input:

curl http://localhost:8080/

Output:

{
  "application": "springboot-apprunner-demo",
  "environment": "local",
  "message": "Spring Boot application is running on AWS App Runner",
  "timestamp": "2026-06-26T10:00:00"
}

Health check:

curl http://localhost:8080/health

Output:

{
  "status": "UP"
}

Step 7: Build Spring Boot JAR

Input:

mvn clean package

Output:

BUILD SUCCESS

Generated JAR:

target/springboot-apprunner-demo-0.0.1-SNAPSHOT.jar

Step 8: Create Dockerfile

Create file:

Dockerfile
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

COPY target/springboot-apprunner-demo-0.0.1-SNAPSHOT.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

Step 9: Build Docker Image

Input:

docker build -t springboot-apprunner-demo .

Output:

Successfully built image
Successfully tagged springboot-apprunner-demo:latest

Check image:

docker images

Output:

REPOSITORY                    TAG       IMAGE ID       SIZE
springboot-apprunner-demo     latest    abc123xyz      200MB

Step 10: Run Docker Container Locally

Input:

docker run -p 8080:8080 \
-e APP_ENVIRONMENT=docker-local \
springboot-apprunner-demo

Output:

Tomcat started on port 8080
Started AppRunnerDemoApplication

Test:

curl http://localhost:8080/

Output:

{
  "application": "springboot-apprunner-demo",
  "environment": "docker-local",
  "message": "Spring Boot application is running on AWS App Runner",
  "timestamp": "2026-06-26T10:15:00"
}

Stop container:

docker ps
docker stop <container-id>

Step 11: Configure AWS CLI

Input:

aws configure

Example input:

AWS Access Key ID: your-access-key
AWS Secret Access Key: your-secret-key
Default region name: us-east-1
Default output format: json

Validate identity:

aws sts get-caller-identity

Output:

{
  "UserId": "AIDAEXAMPLE",
  "Account": "123456789012",
  "Arn": "arn:aws:iam::123456789012:user/codewithvenu"
}

Step 12: Create Amazon ECR Repository

Input:

aws ecr create-repository \
  --repository-name springboot-apprunner-demo \
  --image-scanning-configuration scanOnPush=true \
  --region us-east-1

Output:

{
  "repository": {
    "repositoryName": "springboot-apprunner-demo",
    "repositoryUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo"
  }
}

Repository URI:

123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo

Step 13: Login Docker to ECR

Input:

aws ecr get-login-password --region us-east-1 \
| docker login --username AWS \
--password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

Output:

Login Succeeded

Step 14: Tag Docker Image

Input:

docker tag springboot-apprunner-demo:latest \
123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo:latest

Validate:

docker images

Output:

REPOSITORY                                                            TAG
123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo latest

Step 15: Push Image to ECR

Input:

docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo:latest

Output:

latest: digest: sha256:abc123 size: 1572

ECR Push Flow

flowchart LR
    JAR[Spring Boot JAR]
    IMG[Docker Image]
    TAG[Tag Image]
    ECR[Amazon ECR]

    JAR --> IMG
    IMG --> TAG
    TAG --> ECR

Deployment Option 1: Deploy Using AWS Console

Step 16: Open App Runner Console

Go to:

AWS Console
→ App Runner
→ Create service

Step 17: Choose Source

Input:

Source type: Container registry
Provider: Amazon ECR
Repository type: Private
Container image URI: 123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo:latest
Deployment trigger: Automatic or Manual

Recommended for learning:

Deployment trigger: Manual

Recommended for faster development:

Deployment trigger: Automatic

Step 18: Configure Access Role

Because the image is in private ECR, App Runner needs permission to pull the image.

Input:

ECR access role: Create new service role

AWS can create a role with permissions for ECR access.

Role example:

AppRunnerECRAccessRole

Step 19: Configure Service

Input:

Service name: codewithvenu-apprunner-demo
Virtual CPU: 1 vCPU
Memory: 2 GB
Port: 8080

Environment variable:

APP_ENVIRONMENT=apprunner-dev

Health check:

Protocol: HTTP
Path: /health
Interval: 10 seconds
Timeout: 5 seconds
Healthy threshold: 1
Unhealthy threshold: 5

Step 20: Create and Deploy

Click:

Create & deploy

Output:

Service status: Operation in progress
Service status: Running

App Runner provides a default HTTPS URL:

https://example-id.us-east-1.awsapprunner.com

Step 21: Test App Runner URL

Input:

curl https://example-id.us-east-1.awsapprunner.com/

Output:

{
  "application": "springboot-apprunner-demo",
  "environment": "apprunner-dev",
  "message": "Spring Boot application is running on AWS App Runner",
  "timestamp": "2026-06-26T10:45:00"
}

Health check:

curl https://example-id.us-east-1.awsapprunner.com/health

Output:

{
  "status": "UP"
}

Version check:

curl https://example-id.us-east-1.awsapprunner.com/version

Output:

{
  "version": "1.0.0"
}

Deployment Option 2: Deploy Using AWS CLI

Step 22: Create App Runner ECR Access Role

Create trust policy file:

cat > apprunner-trust-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "build.apprunner.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

Create role:

aws iam create-role \
  --role-name AppRunnerECRAccessRole \
  --assume-role-policy-document file://apprunner-trust-policy.json

Attach policy:

aws iam attach-role-policy \
  --role-name AppRunnerECRAccessRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess

Get role ARN:

aws iam get-role \
  --role-name AppRunnerECRAccessRole \
  --query 'Role.Arn' \
  --output text

Output:

arn:aws:iam::123456789012:role/AppRunnerECRAccessRole

Step 23: Create App Runner Service

Input:

aws apprunner create-service \
  --service-name codewithvenu-apprunner-demo \
  --source-configuration '{
    "AuthenticationConfiguration": {
      "AccessRoleArn": "arn:aws:iam::123456789012:role/AppRunnerECRAccessRole"
    },
    "AutoDeploymentsEnabled": true,
    "ImageRepository": {
      "ImageIdentifier": "123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo:latest",
      "ImageRepositoryType": "ECR",
      "ImageConfiguration": {
        "Port": "8080",
        "RuntimeEnvironmentVariables": {
          "APP_ENVIRONMENT": "apprunner-dev"
        }
      }
    }
  }' \
  --health-check-configuration '{
    "Protocol": "HTTP",
    "Path": "/health",
    "Interval": 10,
    "Timeout": 5,
    "HealthyThreshold": 1,
    "UnhealthyThreshold": 5
  }' \
  --instance-configuration '{
    "Cpu": "1024",
    "Memory": "2048"
  }'

Output:

{
  "Service": {
    "ServiceName": "codewithvenu-apprunner-demo",
    "Status": "OPERATION_IN_PROGRESS",
    "ServiceUrl": "example-id.us-east-1.awsapprunner.com"
  }
}

Step 24: Check Service Status

Input:

aws apprunner list-services

Output:

{
  "ServiceSummaryList": [
    {
      "ServiceName": "codewithvenu-apprunner-demo",
      "Status": "RUNNING",
      "ServiceUrl": "example-id.us-east-1.awsapprunner.com"
    }
  ]
}

Step 25: Test CLI Created Service

Input:

curl https://example-id.us-east-1.awsapprunner.com/

Output:

{
  "application": "springboot-apprunner-demo",
  "environment": "apprunner-dev",
  "message": "Spring Boot application is running on AWS App Runner",
  "timestamp": "2026-06-26T11:00:00"
}

App Runner Deployment Flow

flowchart LR
    BUILD[Build Docker Image]
    PUSH[Push to ECR]
    ROLE[Create ECR Access Role]
    SERVICE[Create App Runner Service]
    URL[Test HTTPS URL]

    BUILD --> PUSH
    PUSH --> ROLE
    ROLE --> SERVICE
    SERVICE --> URL

Step 26: Deploy New Version

Update controller:

@GetMapping("/version")
public Map<String, String> version() {
    return Map.of("version", "1.0.1");
}

Build JAR:

mvn clean package

Build Docker image:

docker build -t springboot-apprunner-demo .

Tag image:

docker tag springboot-apprunner-demo:latest \
123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo:latest

Push image:

docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/springboot-apprunner-demo:latest

If automatic deployment is enabled, App Runner redeploys automatically.

If manual deployment is enabled, start deployment from Console or CLI:

aws apprunner start-deployment \
  --service-arn arn:aws:apprunner:us-east-1:123456789012:service/codewithvenu-apprunner-demo/service-id

Test:

curl https://example-id.us-east-1.awsapprunner.com/version

Output:

{
  "version": "1.0.1"
}

Step 27: Environment Variables

App Runner supports environment variables for application configuration.

Examples:

APP_ENVIRONMENT=prod
SPRING_PROFILES_ACTIVE=prod
LOG_LEVEL=INFO

Spring Boot usage:

spring:
  profiles:
    active: ${SPRING_PROFILES_ACTIVE:dev}

app:
  environment: ${APP_ENVIRONMENT:local}

Do not store sensitive values directly as plain text environment variables.

For secrets, use:

  • AWS Secrets Manager
  • AWS Systems Manager Parameter Store

App Runner can reference sensitive data stored in Secrets Manager and SSM Parameter Store as environment variables.


Step 28: Logs and Monitoring

App Runner sends logs and metrics to CloudWatch.

Console path:

AWS Console
→ App Runner
→ Service
→ Logs

CloudWatch path:

CloudWatch
→ Log groups
→ App Runner logs

Common logs:

Application logs
Deployment logs
Service events

Step 29: Custom Domain

App Runner provides default domain:

https://example-id.us-east-1.awsapprunner.com

For production, configure custom domain:

api.codewithvenu.com

Flow:

flowchart LR
    DOMAIN[Custom Domain]
    DNS[DNS Provider or Route 53]
    APR[App Runner Service]
    APP[Spring Boot API]

    DOMAIN --> DNS
    DNS --> APR
    APR --> APP

Step 30: Cleanup Resources

To avoid charges, delete resources after learning.

Delete App Runner service:

aws apprunner delete-service \
  --service-arn arn:aws:apprunner:us-east-1:123456789012:service/codewithvenu-apprunner-demo/service-id

Delete ECR repository:

aws ecr delete-repository \
  --repository-name springboot-apprunner-demo \
  --force \
  --region us-east-1

Delete IAM role policy:

aws iam detach-role-policy \
  --role-name AppRunnerECRAccessRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess

Delete IAM role:

aws iam delete-role \
  --role-name AppRunnerECRAccessRole

Common Errors and Fixes

Error 1: App Runner Cannot Pull ECR Image

Reason:

Missing access role
Wrong ECR URI
ECR repository in wrong region
Image tag does not exist

Fix:

Use AppRunnerECRAccessRole
Verify image URI
Verify image tag
Verify region

Error 2: Service Fails Health Check

Reason:

Wrong health check path
Application failed to start
Wrong container port
Spring Boot not listening on expected port

Fix:

Use /health endpoint
Set container port to 8080
Use server.port=${PORT:8080}
Check CloudWatch logs

Error 3: 500 Error After Deployment

Reason:

Missing environment variable
Database connection failure
Secrets not configured
Application exception

Fix:

Check App Runner logs
Check environment variables
Check Secrets Manager or Parameter Store permissions

Error 4: Deployment Not Updating

Reason:

Auto deployment disabled
Image tag not changed
Image push failed
Manual deployment not started

Fix:

Enable automatic deployment
Use versioned image tags
Run start-deployment
Check ECR image push output

Best Practices

  • Use Docker image deployment from ECR
  • Use versioned image tags like 1.0.1
  • Avoid using only latest in production
  • Use health check endpoint /health
  • Use environment variables for non-sensitive config
  • Use Secrets Manager or SSM Parameter Store for secrets
  • Enable image scanning in ECR
  • Use CloudWatch logs
  • Configure custom domain for production
  • Keep Docker image small
  • Avoid hardcoding AWS access keys
  • Use IAM roles
  • Configure proper CPU and memory
  • Delete unused services to avoid cost

App Runner vs ECS Fargate

Feature App Runner ECS Fargate
Setup complexity Low Medium
Load balancer management Managed You configure
Container support Yes Yes
Networking control Lower Higher
Scaling control Simple Advanced
Best for Simple APIs and web apps Production microservices

App Runner vs Elastic Beanstalk

Feature App Runner Elastic Beanstalk
Deployment style Container or code App platform
Infrastructure visibility Lower Higher
Simplicity High Medium
Containers First-class Supported
Best for Simple managed container apps Traditional app deployments

Interview Questions

What is AWS App Runner?

AWS App Runner is a managed AWS service for running web applications and APIs from source code or container images without managing servers.

What sources can App Runner use?

App Runner can use source code repositories or container images from Amazon ECR and Amazon ECR Public.

Does App Runner manage load balancing?

Yes. App Runner provides a managed endpoint and handles load balancing and scaling for the service.

What is an App Runner access role?

An access role allows App Runner to pull private container images from Amazon ECR.

How do you configure secrets?

Use AWS Secrets Manager or AWS Systems Manager Parameter Store and reference them as environment variables.

What is the common port configuration?

For Spring Boot, use:

server:
  port: ${PORT:8080}

Then configure App Runner container port as 8080.


Summary

In this article, we deployed a Spring Boot application on AWS App Runner.

We covered:

  • Spring Boot REST API creation
  • Dockerfile creation
  • Local Docker testing
  • Amazon ECR repository
  • Docker image push
  • App Runner deployment using console
  • App Runner deployment using CLI
  • Environment variables
  • Health checks
  • Logs
  • New version deployment
  • Custom domain concept
  • Cleanup
  • Best practices

App Runner is useful when you want to deploy web APIs quickly without managing EC2, ECS cluster details, or Kubernetes complexity.


Loading likes...

Comments

Share a question, correction, or practical insight about this article.

Loading approved comments...