Containers

Deploy applications in AWS App Runner with GitHub Actions

Overview

AWS App Runner is a fully managed service that makes it easy for developers to quickly deploy containerized web applications and APIs at scale and with no prior infrastructure experience required. Starting with the source code or a container image, App Runner will automatically build, scale, and secure the web application in the AWS Cloud.

In this post, we will explore how organizations using GitHub as a source code repository can use GitHub Actions to deploy their applications on App Runner.

If you are new to AWS App Runner, check out the getting started resources.

Background

For organizations using GitHub as a source code repository, GitHub Actions provide a way to implement complex orchestration and CI/CD functionality directly in GitHub by initiating a workflow on any GitHub event.

Integrating App Runner with GitHub Action enables customers to provide a self-serve capability for application developers to quickly develop, prototype, and deploy their applications at scale. This will help development teams reduce the dependency on the platform team, which may be small, for deploying, managing, and upgrading these services to support faster innovation.

A GitHub Action is an individual unit of functionality that can be combined with other GitHub Actions to create workflows. These workflows are triggered in response to certain GitHub events, such as pull, push, or commit. Workflows run inside managed environments on GitHub-hosted servers.

In this post, we will be using the following AWS open-sourced GitHub Actions under the github.com/aws-actions repository to demonstrate App Runner deployment:

Prerequisites

  1. A GitHub account: This post assumes you have the required permissions to configure GitHub repositories, create workflows, and configure GitHub secrets.
  2. Create a new GitHub repository and clone it to your local environment. Refer to the Getting Started documentation for more detailed instructions. For this example, create a repository called github-actions-with-app-runner.
  3. Install AWS Command Line Interface (AWS CLI) locally. This is separate from using the AWS CLI in a GitHub Actions runner. If you use AWS Cloud9 as your integrated development environment (IDE), AWS CLI is pre-installed.
  4. node and npm needs to be installed in the local environment. Refer to this documentation for more detailed steps.
  5. An AWS user with access keys, which the GitHub Actions runner uses to deploy the application: Refer to these instructions for creating an AWS access key and secret.
  6. For source-based deployment, the App Runner service requires access to the source image repository. For more details, refer to the documentation.
  7. For image-based deployment, the App Runner service requires access to the source image repository. This is required for Amazon ECR image repositories, but not for Amazon ECR public repositories). For more details around IAM policies and roles, refer to this documentation.

The following are the step-by-step instructions to create a service role and associating AWSAppRunnerServicePolicyForECRAccess policy with the preceding mentioned permissions.

  1. Create trust-policy.json file with trust policy
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": "build.apprunner.amazonaws.com"
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }
  2. Create new role app-runner-service-role with trust-policy.json
    aws iam create-role --role-name app-runner-service-role \
    --assume-role-policy-document file://trust-policy.json
  3. Attach AWSAppRunnerServicePolicyForECRAccess IAM policy to app-runner-service-role IAM role.
    aws iam attach-role-policy \
    --policy-arn arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess \
    --role-name app-runner-service-role
Configuring AWS credentials and source code connection Amazon Resource Name (ARN) in GitHub

The GitHub Actions CI/CD pipeline requires AWS credentials to access your AWS account. The credentials must include AWS Identity and Access Management (IAM) policies that provide access to App Runner and IAM resources.

These credentials are stored as GitHub secrets within your GitHub repository, under Settings > Secrets. For more information, see GitHub Actions secrets.

Create the following secrets in your GitHub repository:

  1. Create two secrets named AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY and enter the key values. We recommend following IAM best practices for the AWS credentials used in GitHub Actions workflows, including:
    1. Do not store credentials in your repository code. Use GitHub Actions secrets to store credentials and redact credentials from GitHub Actions workflow logs.
    2. Create an individual IAM user with an access key for use in GitHub Actions workflows, preferably one per repository. Do not use the AWS account root user access key.
    3. Grant least privilege to the credentials used in GitHub Actions workflows. Grant only the permissions required to perform the actions in your GitHub Actions workflows. Here is the minimum set of permission needed, make sure to replace AwsAccountNumber with the actual AWS Account number:
      {
          "Version": "2012-10-17",
          "Statement": [
              {
                  "Sid": "VisualEditor0",
                  "Effect": "Allow",
                  "Action": "apprunner:*",
                  "Resource": "*"
              },
              {
                  "Sid": "VisualEditor1",
                  "Effect": "Allow",
                  "Action":[
                      "iam:PassRole",
                      "iam:CreateServiceLinkedRole"
                  ],
                  "Resource": "*"
              },
              {
                  "Sid": "VisualEditor2",
                  "Effect": "Allow",
                  "Action": "sts:AssumeRole",
                  "Resource": "arn:aws:iam::<<AwsAccountNumber>>:role/app-runner-service-role"
              },
              {
                  "Sid": "VisualEditor3",
                  "Effect": "Allow",
                  "Action": [
                      "ecr:GetDownloadUrlForLayer",
                      "ecr:BatchGetImage",
                      "ecr:BatchCheckLayerAvailability",
                      "ecr:PutImage",
                      "ecr:InitiateLayerUpload",
                      "ecr:UploadLayerPart",
                      "ecr:CompleteLayerUpload",
                      "ecr:GetAuthorizationToken"
                  ],
                  "Resource": "*"
              }
          ]
      }
    4. Rotate the credentials used in GitHub Actions workflows regularly.
    5. Monitor the activity of the credentials used in GitHub Actions workflows.
  2. Define AWS Region by creating a secret with name AWS_REGION and entering the Region where the App Runner service needs to be created.
  3. For code-based services, create a secret with name AWS_CONNECTION_SOURCE_ARN and enter in the Amazon Resource Name (ARN) of the source code connector in App Runner.
  4. For image-based services, create a secret with name ROLE_ARN and enter in the ARN of the IAM role that grants access to the source image repository.

Once the secrets are configured, the following image is how it appears in in GitHub.

Note: Usage or ROLE_ARN and AWS_CONNECTION_SOURCE_ARN will vary depending upon the type of App Runner service used (source code based or container image based).

Usage

This GitHub Action supports two types of App Runner services: source code-based and container image-based. We will see an example of how to use these different methods to deploy the sample application in App Runner.

Creating the sample application

  1. Create a package.json inside the root directory file by running the npm init command. Accept the default values.
    package name: (sample-nodejs-service)
    version: (1.0.0)
    description:
    entry point: (index.js)
    test command:
    git repository:
    keywords:
    author:
    license: (ISC)
    About to write to .../github-actions-with-app-runner/sample-nodejs-service/package.json:

    {
        "name": "sample-nodejs-service",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
            "test": "echo \"Error: no test specified\" && exit 1"
        },
        "author": "",
        "license": "ISC"
    }
  2. Add Express dependency by running the following command:npm install expressAfter running the command, package.json will look like the following:
    {
        ... 
        "dependencies": {
            "express": "^4.17.1"
        }
        ...
    }
  3. Add a start script entry to the package.json file:
    "scripts": {
        "start": "node index.js"
    }

    Note: Remove test line from the scripts definition.

  4. Create an index.js file inside the same directory and add the following code:
    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
        res.send('Running on AWS App Runner Service !');
    });
    
    const PORT = process.env.PORT || 8080;
    app.listen(PORT, () => {
        console.log(`Server listening on port ${PORT}...`);
    });
  5. To start the application, run the following command:
    npm start
  6. Open the browser and navigate to http://localhost:8080
    Running on AWS App Runner Service !

Code-based service

App Runner uses “AWS Connector for GitHub” to point to an existing source code repository in GitHub with the suitable runtime. App Runner will build the container image based on the language/platform runtime and starts the service based on the generated image. For supported runtimes, refer to the documentation.

Here is the GitHub Action high-level view of the end-to-end flow:

Notes:

  1. The workflow uses the aws-actions/configure-aws-credentials action to configure the environment for GitHub Actions using the environment variables containing AWS credentials (specified part of GitHub secrets) and your desired Region.
  2. Based on the pipeline trigger (like push, commit, etc.), the workflow will first configure the environment and then create the corresponding App Runner service to deploy the application.

Deploying the sample application

  1. Create a new directory .github/workflows under the root directory. Create a new file pipeline.yml under the .github/workflows.
  2. Edit the pipeline.yml file and add the following:
    name: Deploy to App Runner - Source # Name of the workflow
    on:
      push:
        branches: [ main ] # Trigger workflow on git push to main branch
      workflow_dispatch: # Allow manual invocation of the workflow
    
    jobs:  
      deploy:
        runs-on: ubuntu-latest
        
        steps:            
          - name: Configure AWS credentials
            uses: aws-actions/configure-aws-credentials@v1 # Configure with AWS Credentials
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              aws-region: ${{ secrets.AWS_REGION }}
              
          - name: Deploy to App Runner
            id: deploy-apprunner
            uses: awslabs/amazon-app-runner-deploy@main # Deploy app runner service
            with:
              service: app-runner-git-deploy-service
              source-connection-arn: ${{ secrets.AWS_CONNECTION_SOURCE_ARN }}
              repo: https://github.com/${{ github.repository }}
              branch: ${{ github.ref }}
              runtime: NODEJS_12
              build-command: npm install
              start-command: npm start
              port: 18000
              region: ${{ secrets.AWS_REGION }}
              cpu : 1
              memory : 2
              wait-for-service-stability: true
          
          - name: App Runner output
            run: echo "App runner output ${{ steps.deploy-apprunner.outputs.service-id }}" 
  3. Make sure that the appropriate values are set for GitHub secrets AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION and AWS_CONNECTION_SOURCE_ARN
    The configuration triggers the GitHub Actions CI/CD pipeline when code is pushed to the main branch. You can amend this if you are using another branch. For a full list of supported events, refer to the GitHub documentation page.
  4. Create .gitignore file under git root directory and add node_modules to the list of excluded folders.
  5. Add all the files to your local git repository, commit the changes, and push to GitHub by running the following command from the root directory.
    git add .
    git commit -am "Initial commit with pipline"
    git push

    Note: Once the files are pushed to GitHub on the main branch, this automatically triggers the GitHub Actions workflow as configured in the pipeline.yml file. Pipeline execution will take couple of minutes to complete. Upon completion, the App Runner service will be created in the specified AWS Region successfully.

Testing the application

  1. Log in to the App Runner console, and you should see a new service with the app-runner-git-deploy-service name configured.
  2. Open the browser and navigate to the URL mentioned following the Default domain section in the console page and we should see the service up and running.
  3. Output: Running on AWS App Runner Service!

Image-based service

For and image-based service, the source image can be a public or private container image stored in an image repository. It can be used by App Runner to deploy the application without any build stage.

Here is the GitHub Action high-level view of the end-to-end flow:

Notes:

  1. The workflow uses the aws-actions/configure-aws-credentials action to configure the environment for GitHub Actions using the environment variables containing AWS credentials (specified part of GitHub secrets) and your desired Region.
  2. In the next step, we will build the Docker image and push the generated image to Amazon ECR using the aws-actions/amazon-ecr-login@v1 action.
  3. Once the image is available in Amazon ECR, awslabs/amazon-app-runner-deploy@main will create a new App Runner service and deploy the application inside the container.

Updates to the sample application

We will be using the proceeding sample application to show how to provision an App Runner  image-based service, but we will make some changes.

  1. Create a Dockerfile inside the root directory and add the following lines to the file
    FROM node:12-slim
    WORKDIR /usr/src/app
    COPY package*.json ./
    COPY index.js ./
    RUN npm ci
    COPY . .
    EXPOSE 8080
    CMD [ "node", "index.js" ]
  2. Run the following command to create an Amazon ECR repository
    aws ecr create-repository --repository-name nodejs
  3. Remove the following files/folders before proceeding to next step
    node_modules, .github/workflow/pipeline.yml
  4. Create a file under .github/workflow with the name image-pipeline.yml with the following contents
    name: Deploy to App Runner - Image based # Name of the workflow
    on:
      push:
        branches: [ main ] # Trigger workflow on git push to main branch
      workflow_dispatch: # Allow manual invocation of the workflow
    jobs:  
      deploy:
        runs-on: ubuntu-latest
        
        steps:      
          - name: Checkout
            uses: actions/checkout@v2
            with:
              persist-credentials: false
              
          - name: Configure AWS credentials
            id: aws-credentials
            uses: aws-actions/configure-aws-credentials@v1
            with:
              aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
              aws-region: ${{ secrets.AWS_REGION }}     
    
          - name: Login to Amazon ECR
            id: login-ecr
            uses: aws-actions/amazon-ecr-login@v1        
    
          - name: Build, tag, and push image to Amazon ECR
            id: build-image
            env:
              ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
              ECR_REPOSITORY: nodejs
              IMAGE_TAG: ${{ github.sha }}
            run: |
              docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
              docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
              echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"  
              
          - name: Deploy to App Runner
            id: deploy-apprunner
            uses: awslabs/amazon-app-runner-deploy@main        
            with:
              service: app-runner-image-deploy-service
              image: ${{ steps.build-image.outputs.image }}          
              access-role-arn: ${{ secrets.ROLE_ARN }}
              runtime: NODEJS_12          
              region: ${{ secrets.AWS_REGION }}
              cpu : 1
              memory : 2
              port: 8080
              wait-for-service-stability: true
          
          - name: App Runner output
            run: echo "App runner output ${{ steps.deploy-apprunner.outputs.service-id }}" 
  5. Make sure that the appropriate values are set for GitHub Secrets AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, and ROLE_ARN
    The configuration triggers the GitHub Actions CI/CD pipeline when code is pushed to the main branch.
  6. Add all the files to your local git repository, commit the changes, and push to GitHub. The following is the view of the folder structure:

    Run the following command from the root directory.

    git add .
    git commit -am "Initial commit with image pipline"
    git push

    Once the files are pushed to GitHub on the main branch, this automatically triggers the GitHub Actions workflow as configured in the image-pipeline.yml file. Pipeline execution will take couple of minutes to complete. Upon completion, the App Runner service will be created in the specified AWS Region successfully.

Testing the application

  1. Log in to the App Runner console, and you should see a new service with the app-runner-image-deploy-service name configured.
  2. Open the browser and navigate to URL mentioned following the Default domain section in the console. You should see the service up and running.
  3. Output: Running on AWS App Runner service!

Cleanup

  1. Log in to the App Runner console, select the app-runner-image-deploy-service service, and select Actions → Delete. Repeat the same for app-runner-git-deploy-service service, as well.
  2. Delete the Amazon ECR repository by running the following command:
    aws ecr delete-repository \
        --repository-name nodejs \
        --force
  3. Delete the IAM role by running the following command:
    1. Detach IAM policy
      aws iam detach-role-policy \
      --policy-arn arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess \
      --role-name app-runner-service-role
    2. Delete IAM role
      aws iam delete-role --role-name app-runner-service-role
  4. Delete the existing “GitHub connection” by following the instructions available of the documentation.
  5. Mark AWS access key and AWS secret that we created part of the prerequisites section of the blog as inactive.
    1. Navigating to IAM users console, select the user for this example
    2. Select the Security credentials tab.
    3. Select Mark inactive button next to the appropriate AWS credentials used in this example.
  6. Delete the GitHub repository used in this example:
    1. Navigate to https://github.com/<<username>>/github-actions-with-app-runner/settings. Replace the user name with the GitHub user name of the repository owner
    2. Select Delete this repository and confirm the deletion

Conclusion

GitHub Actions is a GitHub feature that enables you to run a CI/CD pipeline to build, test, and deploy software directly from GitHub. Since App Runner continuously monitors the source code and Amazon ECR image repository, any changes to these artifacts will automatically trigger a new deployment of the latest changes with no manual interruption.

In this post, we used GitHub Actions to deploy the sample application in App Runner as both a source code and an image-based service. The GitHub Action amazon-app-runner-deploy uses the App Runner API to build and deploy the application using the App Runner service with the specified configuration.

References

  1. Getting started with AWS App Runner
  2. GitHub Actions
Nathan Harris

Nathan Harris

Nathan Harris is a Software Development Engineer with AWS App Runner, working on new ways for customers to modernize their web applications on AWS. Nathan is passionate about making AWS a natural home for cloud native customers new and old.

Dmitry Gulin

Dmitry Gulin

Dmitry is a Modernization Architect with AWS Professional Services, working with AWS customers to unlock the power of modern cloud native application design.

Hari Ohm Prasath

Hari Ohm Prasath

Hari is a Senior Software Development Engineer with App Runner and Elastic Beanstalk, working on new ways for customers to modernize their web applications on AWS. Hari loves to code and actively contributes to the open source initiatives. You can find him in Medium, Github & Twitter @hariohmprasath