AWS: Migrating from Elastic Beanstalk to App Runner
Tags: aws, tutorial, docker, node
Published: 2022-08-24 10:40:00

## Background

I’ve been using AWS [Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/) for five years to host my [hobby app](https://github.com/ropekonsti/konsti). The application is dockerized and I have two environments: testing and production. I’ve been running it with `t2.small` instances since anything smaller cannot handle building Docker containers.

I’ve been deploying new releases using [EB CLI](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3.html). I would say `eb deploy prod` and the new version is deployed. Elastic Beanstalk’s [Docker Platform](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_docker.html) would then pick `Dockerfile` from the project root and everything just worked.

I need [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) (ALB) for SSL/HTTPS. ALBs are quite expensive for hobby projects and since I had two environments, I needed two. Later it became possible to [share Application Load Balancer amongst multiple Elastic Beanstalk environments](https://docs.aws.amazon.com/elasticbeanstalk/latest/relnotes/release-2020-09-10-shared-alb.html) so only one was required. Before this, I dabbled with [providing SSL with Let’s Encrypt](https://gist.github.com/tony-gutierrez/198988c34e020af0192bab543d35a62a?permalink_comment_id=3186299#gistcomment-3186299) so I would not need load balancers at all. Cost wise this was effective, but managing it turned out to be too work-intensive and would’ve required a complete rewrite to support Amazon Linux 2. So I was stuck with one ALB which would hog around 30% of operating costs.

Then came the new service AWS [App Runner](https://aws.amazon.com/apprunner/) with the following sales pitch:

>  App Runner builds and deploys the web application automatically, load balances traffic with encryption, and scales to meet your traffic needs

Sounds good to me, let’s give it a try!

## Getting started

The first step was to figure out how to roll out a new App Runner service. Luckily there are plenty of great guides to do this. [I followed this one](https://dev.to/aws-builders/the-easiest-way-to-deploy-services-on-aws-using-app-runner-3pf2).

I want to deploy from my GitHub repository so as instructed I installed [AWS Connector for GitHub](https://docs.aws.amazon.com/dtconsole/latest/userguide/connections-create-github.html) to my GitHub organization. Then everything else was a breeze: choose repository and branch and toggle automatic deployments when the repository changes. No need to run manual `eb deploy` anymore!

The service can be configured via user interface or `apprunner.yaml` file. I’ll continue with the user interface but will most likely change to the config file once everything is working.

Then comes the first interesting part: I have to add commands for building and starting the app. Elastic Beanstalk did not require these, it just picked the `Dockerfile` which had all steps defined.  Seems like I won’t be using my `Dockerfile` but the scripts the `Dockerfile` was using. This means I cannot define the base image or control how the container OS is built. This might be an issue at some point.

I have to choose container build runtime. I happen to use Nodejs 14 which is available, but there is a possibility that you might be using something that’s not. 

Adding environment variables is similar to Elastic Beanstalk, just filling key-value fields. I’ll leave the remaining configs as defaults. Then I hit “Create service” and ~5 minutes later I have a running application! Or almost.

Turns out my build command needed changing but updates were rolled back automatically. How do I debug this? App Runner’s user interface doesn’t show much. Took me a moment to find the link to CloudWatch and from there I was able to find the relevant logs. 

![CloudWatch button location](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6nltg4xverfhkuy0s0wy.png)

Turns out I was only installing prod dependencies but required dev dependencies as well. After fixing this, I had a running app and it only took me a couple of hours!

I’ll have to redo these steps to add a second service for my second environment.

## Custom domain

App Runner provides me with a domain like `foobar.eu-west-1.awsapprunner.com`. The last thing was to add a custom domain like `example.com`.

Adding a custom domain is straightforward. App Runner’s user interface gives me DNS CNAME records for certificate validation and a CNAME record pointing to the App Runner domain. Copy these to DNS settings and wait for DNS settings to propagate.

I have two domains, `example.com` and `test.example.com`. I’m using [Route 53](https://aws.amazon.com/route53/) as a DNS provider. 

Then I found this:

> If you're using Amazon Route 53 as your DNS provider, you can add a subdomain, but support for adding a root domain isn't available at this time.

That’s a bummer. So `example.com` will not work. I moved the test environment with domain `test.example.com` to App Runner but I have to change my DNS provider to get the production domain to work. Hopefully, this will get fixed in the future.

## Conclusion

I wanted to move my dockerized app running in Elastic Beanstalk to App Runner. Getting started with App Runner took me maybe an hour, then another hour fiddling with settings, and a third one to figure out custom domains. Not bad!

Liked:
- The getting started experience was great.
- Configuring auto-deployment on GitHub repository change was a checkbox. No need to add anything to my CI pipeline!

Didn’t like:
- Silently failing deployments. Took me a while to figure out where the relevant logs are. They were in CloudWatch.
- Not being able to use root domain as custom domain when using Route 53. This forces me to change my DNS provider.

Overall the feeling with App Runner is pretty similar to Elastic Beanstalk. It just does a better job at abstracting the infrastructure away and there are no separate costs for running different services like a load balancer. I have a feeling there will be more constraints I haven’t faced yet, but for now I’m happy with the result!