Containers
Enabling continuous workflows for AWS App Runner service with persistency using AWS Copilot CLI
We recently launched a new service called AWS App Runner, the simplest way to build and run your containerized stateless web application on AWS. App Runner provisions and manages all the required resources for you to run containers such as build pipelines, load balancers, scaling in and out, and of course, its underlying infrastructure.
While App Runner works as a great abstraction layer for the web tier and gives you the easiest way to deploy and run stateless web applications, they may require external dependencies. For instance, your web application may need other AWS resources for the data tier such as DynamoDB tables and S3 buckets.
What are the options for you to provision and manage those required resources? Creating them using the management console or the AWS CLI? This would work for some users, but you may want to have a way that is better than it for consistent deployments. How about writing CloudFormation or Terraform templates from scratch? Although this will give you high degree of configurability over the resources with a continuous way to manage them, obviously this wouldn’t be an easy job. We’ve heard from some of the App Runner beta users, that they’re seeking ways to manage dependent resources that are as simple as App Runner.
This is where Copilot comes in. Copilot is an open source command line interface created by AWS, and was originally created to make it easy for developers to build, release, and operate production ready containerized workloads on Amazon ECS and AWS Fargate. Copilot also allows you to create external dependencies such as persistent storage for your application with just few commands. Copilot uses CloudFormation behind the scenes, and it takes care of those dependencies for you. The only thing you need to care about is the “architecture” of your containerized application, instead of thinking and managing underlying infrastructures.
As I mentioned earlier, App Runner gives you fully managed build pipelines. It provides seamless “code-to-deploy” workflow for Node.js and Python applications today, but for some users, it’s important to have their own release pipeline instead of the managed pipeline for achieving more granular controls over it, to run unit/integration tests before deploying the containers for instance. Copilot also helps you with this. It allows you to have your own customizable pipelines with just a few commands.
We launched Copilot v1.7.0 with support for App Runner at the same time as the App Runner GA launch. Now you can run your containerized application on App Runner with Copilot, from small PoC apps to multi-region and multi-account workloads in production.
Let’s dive deep into how they work well together. At the end of the following tutorial, you’ll get an architecture like in the diagram below.
In step 1 and 2, we’re going to set up and deploy minimal required resources such as App Runner and DynamoDB to run the sample application. After that, we’ll have a configurable pipeline with AWS CodePipeline in step 3, then try “push-to-deploy” in step 4 to see all elements work together as expected. Beware that the tutorial may incur some charges especially for App Runner usage and DynamoDB table.
Building and deploying Next.js application on App Runner using Copilot CLI
Prerequisites
- Copilot v1.7.1 or later (see the installation guide if needed)
- Docker Desktop (or Docker Engine on Linux environment)
jq
- A forked “hello-app-runner-nodejs” GitHub repository
- You will need your own forked repository to try “push-to-deploy” in the step 4 of this tutorial, so ensure you’re using a forked repo, which you’re allowed to push.
0. Clone forked repo
1. Create Copilot application
If the Copilot binary is missing or is outdated, follow the Copilot doc to install/update.
Provision “test” environment
At this point you’ll be asked if you want to proceed to create “test” environment. Choose “y” (Yes) to proceed.
Note that Copilot first creates an environment to hold shared infrastructure between services in a Copilot application. Although App Runner does not require a VPC or an Amazon Elastic Container Service (Amazon ECS) cluster, Copilot creates these free-of-charge resources by default. This makes it possible to for you have seamless workflows within the same environment across App Runner or Amazon ECS, whichever you chose for the compute.
Check deployed app in your web browser
Open the endpoint of your App Runner service in your preferred web browser. You can find the endpoint url at the end of the previous command’s output (it’s in the form of https://<random string>.<your AWS region>.awsapprunner.com
).
Once you opened it, you’ll see the app failed to load data. This is because there is no DynamoDB table provisioned at this point.
Okay then, let’s set up a DynamoDB table in the next step.
2. Set up DynamoDB table
Before proceeding, it’s helpful to understand that the command copilot storage init
does not actually provision AWS resources, it only creates configuration files in your local workspace, more specifically ./copilot/my-svc/addons/Items.yml
in this case. So you will provision it using the copilot deploy
command in the next step.
You may also realized at the end of the terminal output above, that your application will be able to refer the DynamoDB table name via an environment variable named ITEMS_NAME
that Copilot will inject into your App Runner service automatically.
Then, let’s provision the dependent resources (a DynamoDB table and IAM roles in this case) and update your service to use the new environment variable “ITEMS_NAME.”
Seed initial data into the DynamoDB table
The provisioned DynamoDB table is still empty of course, so let’s seed it by executing the following command.
If you don’t have npm
in your workspace, then just execute ./seed/seed.sh
instead. Also note that the script requires the jq
command to run successfully.
Check deployed app in your web browser (again)
Open the endpoint of your App Runner service in your preferred web browser application, or just reload the web page you’ve opened in the previous step. As we did in the previous step, you can obtain the endpoint url from the terminal output (or in the App Runner management console of course).
Congratulations! Now you’re running the app successfully with the loaded data from the DynamoDB table this time ?
Let’s open the first item: “Getting Started with App Runner using Copilot.”
You’ll see that the first item describes what you’ve just finished through this step-by-step guide so far.
We have created several resources by using Copilot, so let’s take a little bit of a closer look into the key component, the App Runner service, in the management console to get a sense of how the App Runner service looks like before going to the next step.
First, open App Runner in the AWS Management Console in your web browser’s new tab and click the service name “my-app-test-my-svc” to open the details. As you can see, it shows the endpoint you’ve accessed several times as “Default domain,” a direct link to the ECR repository, along with multiple tabs such as logs, metrics, and custom domains.
Let’s have a look at the “Logs” tab and you’ll find multiple sections in there. The first one is “Event log.” This shows the latest lifecycle events of your App Runner service. The next one is “Deployment logs,” which shows separated log stream for each deployment. The last one is “Application logs.” These are the actual logs came from your web application running on App Runner. Let’s click and open “Application logs” here.
The logs are stored in CloudWatch Logs, and the console shows the latest combined logs from instances into a single log stream. In the following screenshot, the first instance has generated some error logs that say “Missing required key ‘TableName’ in params.” This is true because we first deployed the application without a DynamoDB table and the application didn’t have the TableName variable at that time. You’ll also find the new instance has started at the line of “Using webpack 5. Reason: …” These are the logs from the instance we’re currently running on App Runner.
As I mentioned at the beginning of this article, Copilot uses CloudFormation to provision and manage AWS resources as its backend. So let’s take a brief look at the CloudFormation stack in the CloudFormation section of the AWS Management Console. Once you opened CloudFormation, select the “my-app-test-my-svc” stack in the “Stacks” list.
The following page can be opened via the “Template” tab, and this is the actual CloudFormation template Copilot generated from ./copilot/my-svc/manifest.yml
in your local workspace. It has a definition of the AWS::AppRunner::Service CloudFormation resource, and you can find an environment variable named “ITEMS_NAME” in the resource definition.
The value of the “ITEMS_NAME” environment variable above is referencing the output of the other stack which is the nested stack shown as “my-app-test-my-svc-AddonsStack-O2629IALDI8H” in this case as shown in the following screenshot.
The add-on stack and its CloudFormation template were also created by Copilot from ./copilot/my-svc/addons/Items.yml
. It would be also good to take a look at the YAML files to know how Copilot covers and abstracts to build those complex resources with a few questions you answered in the previous steps and the sense of “better by default.”
Okay, let’s get back to the story.
Hope you still have the web page in your web browser with the first item page of the web app. So let’s back to the top page by clicking the “Back to home” link, then open the second item “3. Set-up Release Pipeline” to go to the next step.
3. Set up the release pipeline
As described in the web page you just opened, you’ll create a release pipeline in CodePipeline by using two Copilot commands.
First, you execute copilot pipeline init
command to generate configuration files. The files are similar to manifest.yml
, which we briefly reviewed in the previous step, but it’s for the pipeline this time.
Let’s take a look at the ./copilot/pipeline.yml
file.
We don’t edit this file in this tutorial, but for example, you can uncomment the test_commands
line at the end of the file if you want to add some tests. See the Copilot documentation for the detailed specifications.
Then, execute copilot pipeline update
to create a pipeline in CodePipeline in your AWS account.
Now you need to take manual actions as described in the following terminal output to allow CodePipeline to access your repository.
Open CodeSuite Connections in the console in your web browser’s new tab that is already signed in to your AWS account.
Click the connection name to go to the detailed view.
Click the “Update pending connection” button then you’ll see a popup window titled as “Connect to GitHub.”
If you already have connected GitHub Apps, you’ll see existing GitHub Apps in a dropdown menu once you put the focus into the input next to the magnifier icon.
< I have an existing GitHub App that has access to the repository. >
Select one, and click “Connect” and go to the “Check Copilot’s status” section below.
< I don’t have any existing GitHub App with access to the repository. >
Click the “Install a new app” button then the window loads the GitHub web page to let you select your GitHub namespace that your forked repository belongs to.
Once you selected the GitHub namespace, follow the web page to create or update a GitHub App to allow access to your GitHub repository. Note that you need to choose “hello-app-runner-nodejs” as a selected repository if you choose “Only select repositories” in the “Repository access” section on the GitHub App page.
You’ll be redirected to the AWS Management Console again after clicking the “Save” button in the GitHub page.
Now the input has a numeric value, then click the “Connect” button.
Check Copilot’s status
Go back to the terminal where you executed copilot pipeline update
, it must show that you have successfully created a pipeline as follows.
Now that you have a release pipeline created in CodePipeline, let’s click the “Back to home” link at the left bottom of the web page in the web application.
In the next step, we’re going to try “push-to-deploy” by using the pipeline we’ve just created.
4. Try “push-to-deploy”
Let’s edit something in the repository and push it to invoke the pipeline!
Once you changed the text, save it and execute the following commands in the terminal window.
All set! Go to CodePipeline in the console at https://<Your AWS region>.console.aws.amazon.com/codesuite/codepipeline/pipelines
in your web browser’s new tab and open the pipeline in the list. You’ll see something like below.
Wait a few minutes for the pipeline to be successfully completed all the stages, then click the “Back to home” link at the left bottom of the web page in the web application. It’ll bring you to the top page.
Congratulations ? As you can see, the change we’ve made were successfully deployed through the pipeline!
Now you have a configurable continuous delivery pipeline that can be invoked by git push. We can push additional commits to see how the sample app works.
5. Cleanup
This is the final (and is optional but important) step. You may want to delete all the provisioned resource to avoid unexpected charges.
With Copilot, it’s also easy to wipe out everything you’ve created by this tutorial in your AWS account. To do this, execute the following command.
That’s all!
Resources to learn more
There are more resources to learn about AWS App Runner and AWS Copilot CLI, and we encourage you to check out them!
- Introducing AWS App Runner | Containers
- AWS Copilot Documentation
- AWS App Runner Documentation
- AWS App Runner Workshop
- ✨ AWS App Runner Public Roadmap at GitHub ✨ – See the latest roadmap items and leave your requests and feedback to the App Runner team directly!
Happy building with App Runner and Copilot! ?