Deploying Your Rails App to AWS with Pulumi: Part 3

Supreet Singh - May 21, 2023

In the second part of this series, we were looking at all the resources we need to get the application running on AWS. In this post, we'll look at setting up a CD pipeline with Github Actions and ECR.

Introduction

Rails is really starting to embrace containerization these days and as a matter of fact, we'll be getting an official Dockerfile starting with Rails 7.1. For this post, we'll be using Github Actions to build and push our image to ECR and trigger automated deploys to ECS.

Setting up Github Actions

Github Actions is a CI/CD tool that comes with Github. It's free for public repositories and has a generous free tier for private repositories. I thought about using AWS CodePipeline but I wanted to keep things simple and avoid having to deal with another service in AWS. I like Github a lot more than I like AWS.

The intent here is simple:

  • I push my code to the main branch
  • Github Actions builds the image and pushes it to ECR
  • Deploy the task definition to ECS

Let's start by creating a new file in .github/workflows called deploy.yml and add the following:

on:
  push:
    branches: [main]

name: Deploy to Amazon ECS

permissions:
  id-token: write # This is required for requesting the JWT
  contents: read # This is required for actions/checkout

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::123xyzabc456:role/github-role
          aws-region: us-east-2

      - 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: your-ecr-repo-name
          IMAGE_TAG: ${{ github.sha }}
          PORT: 3000
          WEB_CONCURRENCY: 2
        run: |
          # Build a docker container and push it to ECR so that it can be deployed to ECS.
          docker build . --file Dockerfile --tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --tag $ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg PORT=$PORT --build-arg WEB_CONCURRENCY=$WEB_CONCURRENCY
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
          echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

      - name: Download task definition
        id: download-task-definition-json
        run: |
          aws ecs describe-task-definition --task-definition your-task-definition-name --query taskDefinition > task-definition.json

      - name: Deploy Amazon ECS task definition
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: task-definition.json
          service: your-service-name
          cluster: your-cluster-name

Let's break this down a bit.

on:
  push:
    branches: [main]

This is the trigger for the workflow. We want to run this workflow when we push to the main branch.

permissions:
  id-token: write # This is required for requesting the JWT
  contents: read # This is required for actions/checkout

This is required for the actions/checkout step. It's a bit confusing but it's required for the checkout step to work.

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

This is the job that will run when the workflow is triggered. We're calling it deploy and it will run on ubuntu-latest.

steps:
  - name: Checkout
    uses: actions/checkout@v3

This step checks out your repository content into the workflow's runner so that the workflow can access your code.

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v2
  with:
  role-to-assume: arn:aws:iam::123xyzabc456:role/github-role
  aws-region: us-east-2

This step uses an AWS action to configure the AWS credentials that the workflow will use. You need to replace arn:aws:iam::123xyzabc456:role/github-role with the ARN of your IAM role, and us-east-2 with your AWS region.

- name: Login to Amazon ECR
  id: login-ecr
  uses: aws-actions/amazon-ecr-login@v1

This step logs in to your Amazon Elastic Container Registry (ECR) using another AWS action.

- name: Build, tag, and push image to Amazon ECR
  id: build-image
  env:
  ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
  ECR_REPOSITORY: your-ecr-repo-name
  IMAGE_TAG: ${{ github.sha }}
  PORT: 3000
  WEB_CONCURRENCY: 2
  run: |
  # Build a docker container and push it to ECR so that it can be deployed to ECS.
  docker build . --file Dockerfile --tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --tag $ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg PORT=$PORT --build-arg WEB_CONCURRENCY=$WEB_CONCURRENCY
  docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
  echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

This step builds a Docker image from your application code, tags it, and pushes the image to ECR. The image is tagged with both the commit SHA and latest.

- name: Download task definition
  id: download-task-definition-json
  run: |
  aws ecs describe-task-definition --task-definition your-task-definition-name --query taskDefinition > task-definition.json

This step downloads the task definition JSON for your ECS service. You will need to replace your-task-definition-name with your actual ECS task definition name.

- name: Deploy Amazon ECS task definition
  uses: aws-actions/amazon-ecs-deploy-task-definition@v1
  with:
  task-definition: task-definition.json
  service: your-service-name
  cluster: your-cluster-name

Finally, this step deploys the new Docker image to your ECS service. Replace your-service-name and your-cluster-name with the name of your ECS service and the name of your ECS cluster, respectively.

That's it! By using GitHub Actions, you can automate the build and deployment process of your application every time you push changes to the main branch.

Wrapping up Part 3

Hopefully, this helps fill in some blanks for you. If you have any questions, reach out to me on Twitter @supreet321. I'm happy to help.