Logo rcmonteiro
Utter Todo

Day five - Deploy to AWS

Understanding a Comprehensive GitHub Actions Workflow for CI/CD

GitHub Actions allows you to automate your software workflows directly in your GitHub repository. The YAML configuration described here is a robust Continuous Integration (CI) and Continuous Deployment (CD) workflow. Let’s break down each part of this workflow to understand its functionality and purpose.

Workflow Configuration

Workflow Name and Triggers

name: CI

on:
  push:
    branches: ["main"]
  pull_request:
    types: [opened, synchronize]

The workflow is named "CI" and is triggered on two events:

  • Push: When code is pushed to the main branch.
  • Pull Request: When a pull request is opened or synchronized.

Permissions

permissions:
  id-token: write
  contents: write
  issues: write
  pull-requests: write

Permissions are granted for various actions such as writing to contents, issues, and pull requests, ensuring the workflow can interact with the repository and GitHub services effectively.

Jobs Configuration

The workflow consists of a single job named build, which is designed to build, test, and deploy the application.

Job Configuration

jobs:
  build:
    name: Build, Test and Deploy
    timeout-minutes: 15
    runs-on: ubuntu-latest
  • Name: "Build, Test and Deploy"
  • Timeout: 15 minutes
  • Runner: Uses the latest Ubuntu runner

Services

services:
  postgres:
    image: bitnami/postgresql
    ports:
      - 5432:5432
    env:
      POSTGRESQL_USERNAME: docker
      POSTGRESQL_PASSWORD: docker
      POSTGRESQL_DATABASE: utter-todo

Two services are defined:

  • Postgres: A PostgreSQL database.

Steps

Checkout Code

- name: Checkout
  uses: actions/checkout@v4

Checks out the repository code.

Setup Node.js and pnpm

- name: Install Node.js
  uses: actions/setup-node@v4
  with:
    node-version: 20

- uses: pnpm/action-setup@v4
  name: Install pnpm
  with:
    version: 9.1.1
    run_install: false

Installs Node.js and pnpm, a fast package manager.

Setup Cache

- name: Get pnpm store directory
  shell: bash
  run: |
    echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- uses: actions/cache@v4
  name: Setup pnpm cache
  with:
    path: ${{ env.STORE_PATH }}
    key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
    restore-keys: |
      ${{ runner.os }}-pnpm-store-

Configures caching for pnpm dependencies to speed up subsequent runs.

Install Dependencies

- name: Install dependencies
  run: pnpm install

Installs the project dependencies using pnpm.

Build and Test

- name: Turbo Repo Build
  run: pnpm build

- name: Run Domain Unit Tests (Vitest)
  run: | 
    cd apps/api
    pnpm run test
  env:
    JWT_SECRET: secret
    API_PORT: 4000
    DB_URL: postgresql://docker:docker@localhost:5432/utter-todo?schema=public
    DB_TEST_URL: postgresql://docker:docker@localhost:5432/utter-todo?schema=public

- name: Run API e2e tests (Vitest)
  run: |
    cd apps/api
    pnpm drizzle-kit push
    pnpm run test:e2e
  env:          
    JWT_SECRET: secret
    API_PORT: 4000
    DB_URL: postgresql://docker:docker@localhost:5432/utter-todo?schema=public
    DB_TEST_URL: postgresql://docker:docker@localhost:5432/utter-todo?schema=public

- name: Run WEB Unit Tests (Vitest)
  run: |
    cd apps/web
    pnpm run test
  env:
    NODE_ENV: test
    API_URL: http://localhost:4000
    NEXT_PUBLIC_APP_URL: http://localhost:50789
  • Build: Builds the project.
  • Unit Tests: Runs unit tests for the API and web applications using Vitest.
  • End-to-End (e2e) Tests: Runs e2e tests for the API.

Install and Run Playwright for Web Tests

- name: Install Playwright Browsers
  run: |
    cd apps/web
    pnpm add -g playwright
    pnpm exec playwright install --with-deps

- name: Run WEB e2e tests (Playwright)
  run: |
    cd apps/web
    npx playwright test
  env:
    NODE_ENV: test
    API_URL: http://localhost:50789/api
    NEXT_PUBLIC_APP_URL: http://localhost:50789

Installs and runs Playwright for e2e testing of the web application.

Semantic Release and Dynamic Tag Creation

- name: Semantic Release
  uses: cycjimmy/semantic-release-action@v4
  env:
    GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

- name: Create tag
  id: create_tag
  run: |
    SHA=$(echo $GITHUB_SHA | head -c7)
    echo "sha=$SHA" >> $GITHUB_OUTPUT
  • Semantic Release: Automates versioning and package publishing.
  • Create tag: Creates a dynamic tag based on the current commit SHA.

AWS Deployment

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: ${{ secrets.ECR_ROLE }}
    aws-region: us-east-2

- name: Login to AWS ECR
  id: login-ecr
  uses: aws-actions/amazon-ecr-login@v2

- name: Build and Push Docker API Image
  id: build-docker-api-image
  env: 
    ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
    TAG: ${{ steps.create_tag.outputs.sha }}
  run: | 
    docker build -f Dockerfile.api -t $ECR_REGISTRY/utter_todo_api:$TAG .
    docker tag $ECR_REGISTRY/utter_todo_api:$TAG $ECR_REGISTRY/utter_todo_api:latest
    docker push --all-tags $ECR_REGISTRY/utter_todo_api
    IMAGE=$(echo $ECR_REGISTRY/utter_todo_api:$TAG)
    echo "image=$IMAGE" >> $GITHUB_OUTPUT

- name: Build and Push Docker WEB Image
  id: build-docker-web-image
  env: 
    ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
    TAG: ${{ steps.create_tag.outputs.sha }}
  run: | 
    docker build -f Dockerfile.web -t $ECR_REGISTRY/utter_todo_web:$TAG .
    docker tag $ECR_REGISTRY/utter_todo_web:$TAG $ECR_REGISTRY/utter_todo_web:latest
    docker

 push --all-tags $ECR_REGISTRY/utter_todo_web
    IMAGE=$(echo $ECR_REGISTRY/utter_todo_web:$TAG)
    echo "image=$IMAGE" >> $GITHUB_OUTPUT

- name: Deploy API to AWS App Runner
  id: deploy-api-app-runner
  uses: awslabs/amazon-app-runner-deploy@main
  with: 
    service: utter_todo_api
    image: ${{ steps.build-docker-api-image.outputs.image }}
    access-role-arn: ${{ secrets.APP_RUNNER_ROLE }}
    region: us-east-2
    cpu: 1
    memory: 2
    port: 3000

- name: Deploy WEB to AWS App Runner
  id: deploy-web-app-runner
  uses: awslabs/amazon-app-runner-deploy@main
  with: 
    service: utter_todo_web
    image: ${{ steps.build-docker-web-image.outputs.image }}
    access-role-arn: ${{ secrets.APP_RUNNER_ROLE }}
    region: us-east-2
    cpu: 1
    memory: 2
    port: 3000

Deploys the Docker images to AWS App Runner for both the API and web applications.

Conclusion

This GitHub Actions workflow is a comprehensive CI/CD pipeline that builds, tests, and deploys your application. It handles dependency management, testing, Docker image creation, and deployment to AWS, ensuring that every change is thoroughly tested and seamlessly deployed. By automating these processes, you can maintain high code quality and reduce the manual effort involved in deployment, allowing you to focus more on developing new features and improving your application.

Contribute to the Project

If you found this post helpful or have suggestions for improvement, feel free to check out the project repository on GitHub. You are welcome to fork the repository and submit a pull request. If you have any questions or want to discuss a topic, please open an issue. We appreciate your contributions!