Use Github actions for Trunk Based Development to deploy AWS ECS Service
Published: 2023-08-27 22:34:48

Trunk based development as a branching model is preferred compared to something like Git / GitHub Flow due its simplicity. But creating a CI/CD pipeline is more challenging since we deploy to every environment from the same branch.

## Introduction to Trunk-Based Development

Trunk-based development involves frequent & small code check-in to a repository, typically known as the "trunk". This approach contrasts traditional development models (like Git Flow), which often involve long, isolated development cycles followed by large code merges. In trunk-based development, teams are encouraged to commit their code to the trunk frequently, often multiple times per day. This allows for continuous integration and continuous deployment (CI/CD), as well as easier collaboration and code reviews. You also don't have branches per environment like a development branch that is deploying to a development environment. You integrate all code on the "trunk".


## Creating GitHub Actions Workflows
I have two environment development & Production


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezj3hiiuk2pp6os4tdva.png)
**Prerequisite**

1. Configure Open ID Connect to get short-lived credentials for my AWS account and store the ARN in Action Secret as <AWS_GITHUB_ACTION_ROLE>. To setup follow this https://aws.amazon.com/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/
2. Create Personal Access Token for Git, this is used for creating tags & Release and store the value in Action Secret as <ORG_GH_ACCESS_TOKEN>
3. create directory called deployments in the root folder of git. deployments (this will hold the task definition files required for AWS)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khuepvsh2zqtehrh402n.png)

## GitHub Action Workflow
**dev-deploy-cicd.yml**

We trigger the pipeline on pushes to main (our "trunk"). Remember, we don't have different branches per environment. We deploy to all our environments from this branch.
Below action will be triggered when a PR or Push happens to main branch and does the following

1. Build and push a new docker image to ECR
2. update the task definition with the new image tag
3. Update the services with the latest task definition and redeployment
4. Create Draft Release and Tag it using semantic versioning

```
name: CICD API 

on:
  push:
    branches:
      - main

env:
  AWS_ROLE: ${{ secrets.DEV_AWS_GITHUB_ACTION_ROLE }}
  AWS_REGION:                    # set this to preferred AWS region, e.g. us-west-1
  ECR_REPOSITORY:                  # set this to Amazon ECR repository name
  ECS_SERVICE:                   # set this to Amazon ECS service name
  ECS_CLUSTER:                         # set this to Amazon ECS cluster name
  ECS_TASK_DEFINITION: deployments              # set this to the path Amazon ECS task definition  
  ECS_CLUSTER_ENV:                     # set this to Amazon ECS cluster Environment
  CONTAINER_NAME:                   # containerDefinitions section of your task definition

permissions:
  id-token: write
  contents: read

jobs:
  deploy-develop:
    name: Build & Deploy
    runs-on: ubuntu-latest
    environment: develop

    steps:
    - name: Source Code Checkout
      uses: actions/checkout@v3
      with:
          submodules: recursive
          token: ${{ secrets.ORG_GH_ACCESS_TOKEN }}

    - name: Configure AWS Credentials Using Role
      uses: aws-actions/configure-aws-credentials@v2
      with:
        role-to-assume: ${{ env.AWS_ROLE }}
        aws-region: ${{ env.AWS_REGION }}
        #mask-aws-account-id: 'false'

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
      with:
        mask-password: 'true'

    - name: Generate Short GIT Commit HASH
      id: vars
      run: echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
    
    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ steps.vars.outputs.sha_short }}
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> "$GITHUB_OUTPUT"

    - name: Update Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}/task-definitions-${{ env.ECS_CLUSTER_ENV }}.json
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}

    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}-${{ env.ECS_CLUSTER_ENV}}
        #wait-for-service-stability: true

    - name: Bump version and push tag
      id: semantic
      uses: mathieudutour/github-tag-action@v6.1
      with:
        github_token: ${{ secrets.ORG_GH_ACCESS_TOKEN }}

    - name: Create Release
      uses: softprops/action-gh-release@v1
      #if: startsWith(github.ref, 'refs/tags/v*')
      with:
        token: ${{ secrets.ORG_GH_ACCESS_TOKEN }}
        tag_name: ${{ steps.semantic.outputs.new_tag }}
        draft: true
        prerelease: true
        generate_release_notes: true
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/31q6h32fymfuvbjqrvak.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cketxhfquqfygr37psnp.png)

**prod-release.yml**

Below action will be triggered when a Draft Release was changed to Publish.
name: Production Release

```
on:
  release:
    types: [published]

env:
  AWS_ROLE: ${{ secrets.PROD_AWS_GITHUB_ACTION_ROLE }}
  AWS_REGION:                 # set this to preferred AWS region, e.g. us-west-1
  ECR_REPOSITORY:                  # set this to Amazon ECR repository name
  ECS_SERVICE:                     # set this to Amazon ECS service name
  ECS_CLUSTER:                       # set this to Amazon ECS cluster name
  ECS_TASK_DEFINITION:              # set this to the path Amazon ECS task definition  
  ECS_CLUSTER_ENV:                      # set this to Amazon ECS cluster Environment
  CONTAINER_NAME:                 # containerDefinitions section of your task definition

permissions:
  id-token: write
  contents: read

jobs:
  deploy-production:
    name: Build & Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
    - name: Source Code Checkout
      uses: actions/checkout@v3
      with:
          submodules: recursive
          token: ${{ secrets.ORG_GH_ACCESS_TOKEN }}

    - name: Configure AWS Credentials Using Role
      uses: aws-actions/configure-aws-credentials@v2
      with:
        role-to-assume: ${{ env.AWS_ROLE }}
        aws-region: ${{ env.AWS_REGION }}
        #mask-aws-account-id: 'false'

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
      with:
        mask-password: 'true'

    - name: Generate Short GIT Commit HASH
      id: vars
      run: echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
    
    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ steps.vars.outputs.sha_short }}
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> "$GITHUB_OUTPUT"

    - name: Update Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}/task-definitions-${{ env.ECS_CLUSTER_ENV }}.json
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}

    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}-${{ env.ECS_CLUSTER_ENV}}
        #wait-for-service-stability: true
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hjcasew5p4xj5x8xhbey.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbw513grpueygnta1qci.png)