Running Containers made easy with AWS App Runner

As a big fan of running containers on AWS ECS Fargate, ECS users get a lot of features out of the box with little to no effort. Integration with other AWS services like Elastic load balancers, AWS Secret Manager, Parameter store, AWS CodeDeploy e.t.c is smooth and seamless. Even though implementing AWS ECS is easier than deploying to and managing an AWS EKS cluster, things can get complex pretty fast, especially without a dedicated Cloud engineer with deep AWS expertise.

AWS App Runner is a fully managed service that simplifies the deployment of containerized applications. It allows developers to build, deploy, and run containerized applications without the need to manage the underlying infrastructure. With App Runner, developers can deploy their applications from source code(GitHub), container images, or even from a container registry (Amazon ECR). App Runner managed platforms include popular platforms like Python, Node.js, Java, .NET, PHP, Ruby, and Go. Non-supported programming languages can go the container registry route. AWS ECS qualifies as an IAAS (infrastructure as a service) while App Runner falls under the PAAS category (platform as a service).

App Runner handles the build and deployment of code changes (CI/CD), scaling of infrastructure, and load-balancing traffic to these containers. Compared to other container orchestration services like ECS, it has a shallow learning curve.

Let's deploy a sample Python app to App Runner with GitHub as the source. The demo app is a simple Python web application served by a Tornado web server. We'll use Terraform to provision the App Runner resources, and specify the build and deployment steps in an apprunner.yaml file located in the root of the repository.

import asyncio
import os
import tornado.web

app_port = os.environ.get("APP_PORT", 8888)
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

async def main():
    app = make_app()
    app.listen(app_port)
    await asyncio.Event().wait()

if __name__ == "__main__":
    asyncio.run(main())

The build and deploy commands are specified in the apprunner.yaml file.

version: 1.0
runtime: python3 
build:
  commands:
    pre-build:
      - echo "this is a pre-build step"
    build:        
      - pip install -r requirements.txt
    post-build:
      - echo "this is a the post-build step"
  env:
    - name: BUILD_STAGE_ENV
      value: "example"
run:
  runtime-version: 3.8.16
  command: python main.py
  network: 
    port: 80
    #this creates an APP_PORT env with a value of 80 assigned.
    env: APP_PORT  
  env:
    - name: RUNTIME_ENV
      value: "example"

The 33 lines of Terraform below creates an App Runner connection and service resource. After the aws_apprunner_connection resource is applied, you must complete the authentication handshake using the App Runner console. The source code repository URL and branch are important components specified in the aws_apprunner_service resource block.

resource "aws_apprunner_connection" "example" {
  connection_name = "example"
  provider_type   = "GITHUB"
}

resource "aws_apprunner_service" "example" {
  service_name = "example"

  source_configuration {
    authentication_configuration {
      connection_arn = aws_apprunner_connection.example.arn
    }
    code_repository {
      code_configuration {
        # App Runner reads configuration values from the apprunner.yaml file in the source code repository 
        configuration_source = "REPOSITORY"
      }
      repository_url = var.repository_url
      source_code_version {
        type  = "BRANCH"
        value = var.branch
      }
    }
  }
  network_configuration {
    ingress_configuration {
      is_publicly_accessible = true
    }
    egress_configuration {
      egress_type       = "DEFAULT"
    }
  }
}

After the APP Runner service reaches the running status, the Default domain becomes reachable. Changes to the master branch will now trigger a new deployment, using the specified build and deploy steps. Application logs are sent to a Cloudwatch log group.

The Default domain is a subdomain of awsapprunner.com, but a custom domain name is also supported. You need to add the provided CNAME records at your DNS provider, to point to the App Runner default domain and for TLS certificate validation.

There are current limitations to running your application on App Runner you should keep in mind. As of the time of this writing, the most requested App Runner features are the support for web sockets, and the ability to scale app runner to zero.

App Runner is a good place to run services that fit its limits. Stateless HTTPS APIs and web applications are good examples of applications that fit into App Runner's use case. I am particularly looking forward to the WebSocket support, this'll make App Runner a great place to host Streamlit apps.

Demo app source code: https://github.com/Faithtosin/aws-app-runner
Terraform source code: https://github.com/Faithtosin/faithtosin.io/tree/master/examples/mar-19-2023

5
Subscribe to my newsletter

Read articles from Faith Tosin Olapade directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Faith Tosin Olapade
Faith Tosin Olapade

Sharing Cloud, DevOps, Linux, Networking, and Python knowledge.