AWS_app_runner

In this post, we'll see an overview of AWS App Runner, a new competitor to Google Cloud Run. For this, we'll deploy a "Hello World" application, and I'll let you know my own opinion about this AWS managed service.

Disclaimer: I wrote this article in September 2021. I hope and think that App Runner will drastically evolve in the coming weeks/months! I'll not hesitate to update this article or write a new and updated one 🧑‍💻

What is App Runner?

App Runner is an AWS service released on the 18th of May 2021 that allows you to directly run your code or containerized applications. It automatically builds, deploys applications, and distributes traffic with encryption.
App Runner can scale up or down automatically to meet your traffic needs.

Thanks to this new service, we can focus on code only without worrying about scaling or infrastructure.

For what?

Its principal use cases are web applications and APIs. App Runner is like Heroku, Digital Ocean app platform, or Google Cloud Run.

How does it work?

Pre-requisites

To be able to deploy an App Runner instance, we need:

  • AWS console access
  • AWS CLI installed
  • AWS copilot (AWS official tool for managing containers)

Two deployment modes

Build mode

As the name implies, AWS pulls code from GitHub and builds the application on every change in this mode.

Triggers:

  • Automatic: Every push to a specific branch deploys a new version of your service.
  • Manual: Start each deployment yourself using the App Runner console or AWS CLI.

⚠ Only Python and Node.js are supported in build mode!️

Container mode

This mode consists of deploying docker images from private or public AWS ECR registries (only!).

Triggers:

  • Automatic: App Runner monitors your registry and deploys a new version of your service for each image push.
  • Manual: Start each deployment yourself using the App Runner console or AWS CLI.

Deploy a "Hello World" app 🚀

Create our application's content

We'll create a simple hello world python application using Flask.

First, let's create a hello.py file:

from logging import debug
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "<Hello World!>"

Then, creates a requirements.txt file:

flask==2.0.1

Finally, creates the Dockerfile file:

FROM python:3.9-alpine

WORKDIR /app

COPY . .

RUN pip install -r requirements.txt

ENV FLASK_APP=hello
ENV FLASK_RUN_HOST=0.0.0.0
ENV FLASK_RUN_PORT=8080

EXPOSE 8080

ENTRYPOINT [ "flask", "run" ]

I also got used to creating a .dockerignore file containing at least:

Dockerfile
copilot/

In this way, on build, the Dockerfile file and the future copilot directory will not be uploaded to the docker image.

Ok, we're now ready to run something unique 🚀

Run our application

It's time to run our fantastic HelloWorld application! 🤩

Since AWS Console is user-friendly enough, we'll cover the copilot CLI method.

Copilot uses CloudFormation to provision and manage AWS resources as its backend.

Pre-requisites for container mode

An ECR, or Elastic Container Registry, is an AWS-managed container image registry service. An ECR is a place in which we will store our Docker images.

If you plan to use container mode, you'll need to create an ECR and push your docker image into it.

To create an ECR using the CLI, log in to AWS:

aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account_id>.dkr.ecr.<region>.amazonaws.com

Then, create the repository:

aws ecr create-repository \
    --repository-name hello/web \
    --image-scanning-configuration scanOnPush=true \
    --region <region>

Where:

  • hello will be our application's name
  • web will be our service name

Now, you need to build your application. From your application's directory:

docker build -t hello-python .

Finally, tag and push your image to your ECR:

docker tag hello-python <account_id>.dkr.ecr.<region>.amazonaws.com/hello:latest
docker push <account_id>.dkr.ecr.<region>.amazonaws.com/hello:latest

After this, you can continue by reading the Build mode section 😉

Build mode

The first thing to do is to create the application and its service endpoint:

copilot init \
    --type "Request-Driven Web Service" \
    --app hello \
    --name web \
    --port 8080 \
    --dockerfile Dockerfile
  • --type is the architecture of the service that we want to run. In this example, we want to run a Request-Driven Web Service, an internet-facing service deployed on AWS App Runner.
  • --app is the application's name
  • --name is the service's name
  • --port is the port exposed in our Dockerfile
  • --dockerfile is the place where is our application's Dockerfile

For container mode, use --image "<account_id>.dkr.ecr..amazonaws.com/hello/web:latest" instead of --dockerfile Dockerfile

The init step will:

  • Create an ECR for our hello app's web service (build mode only)
  • Create the infrastructure to manage services and jobs under application hello
  • Create the copilot directory (in the current directory) and write the manifest for service web at copilot/web/manifest.yml

In the end, copilot should ask you to create a "test" environment. You can skip this step for now.

Now, we need to create a "production" environment. To do so:

copilot env init --profile default --name production --default-config --app hello --prod
  • --profile is your credential's profile to use for deploying the application (~/.aws/credentials)
  • --name is the environment's name
  • --default-config skip prompting and use default environment configuration
  • --app is the application's name
  • --prod indicate that the environment contains production services

This environment creation step will:

  • Create all infrastructure and network resources such as:
    • VPC
    • Public subnets
    • Private subnets
    • Security groups
    • ECS cluster
    • Internet Gateway
    • IAM roles

If everything goes well, you should see something like this:

environment_creation

We can now deploy our application with this last step

copilot svc deploy --app hello --env production --name web

Here we are! 🚀

deployed_service_web

Finally, try accessing your application by accessing the URL provided by copilot:

accessing_url

Automatic deployment

Our application is now running, it's lovely! But how can we automate its deployment on code updates?

Well, there is some approach to do this, among them:

  • Create an AWS CodePipeline with copilot (only Github is supported)
  • Integrate all steps we did previously into Github Action or Gitlab CI/CD

Alright, let's use copilot and AWS CodePipeline!

Generate the pipeline's configuration files:

ccopilot pipeline init \
    --url https://github.com/<username>/<repository>.git \
    --environments "production"

The previous command will generate the following configuration files in your copilot directory:

  • buildspec.yml
  • pipeline.yml

Now, create the pipeline in CodePipeline in your AWS account:

copilot pipeline update

Copilot should ask you to do some manual actions:

✔ Successfully added pipeline resources to your application: hello
⠋ Creating a new pipeline: pipeline-hello-apprunner-python-exemple
ACTION REQUIRED! Go to https://console.aws.amazon.com/codesuite/settings/connections to update the status of connection copilot-Julie-apprunner-python-e from PENDING to AVAILABLE.

From the AWS Console, open the CodeSuite connections and click on the freshly created connection.

Then, click on Update pending connection. It will pop out a window asking you to authorize AWS connector to access your Github account:

update_pending_connection
Authorize_AWS

Click on Install a new app and then select your account and repository. Finally, click Install.

install_AWS

Now, AWS should let you connect:

settings_AWS_tools

Get back to your terminal; the pipeline update process must be done:

pipeline_update

 

Let's test!

Try adding a file to your repository, or edit an existing one:

~$ echo "# Hello World" > README.md
~$ git add .
~$ git commit -m "feat(README.md): add readme file"
~$ git push -u origin main

From the CodePipeline section of the AWS Console, you can see the whole process running:

process_running
code_pipeline_section

By default, CodePipeline will ask for your approval to go live. Just click on Review and then Approve. You're done, congrats! 🚀

My opinion on App Runner

App Runner is a pretty cool product, but I think it is currently not mature enough to be production-ready. The fact that an App Runner instance cannot connect with resources from inside a VPC (RDS, for example) or that we cannot use AWS Secret Manager is quite limiting IMO.

It is not shocking that App Runner is not production-ready since it was released four months before writing this article.

I listed below some good and "bad" points.

Pros

  • Not locked with AWS
  • No request time limits
  • Simpler than AWS ECS and Fargate
  • Custom domain name
  • Can be fully private (VPC endpoint and VPC PrivateLink)

Drawbacks

  • Cannot pull image outside ECR
  • Cannot use Secret Manager
  • Only Python and Node.js supported in build mode
  • No scale to 0 → Have to pay for at least one instance at any time
  • Less fine-grained configuration options than AWS ECS and Fargate (which could be a good point actually)
  • Not possible to use resources from inside a VPC (e.g. App Runner using a private RDS DB)
  • Only GitHub can be used as a source code repository
  • No volume persistence

In any case, I think App Runner is nonetheless an excellent companion for carrying out PoCs.

Useful links

I want to share with you a few links which are helpful to follow: