Build a Docker image using GitHub Action, save it to Amazon ECR, and deploy it to Amazon ECS.
--Point --Assuming production operation, Release Make GitHub Action work when you create). --Enable build cache to speed up Docker builds
--Create a destination ECR --Create an ECR to deploy to --Create an AWS IAM user for use with GitHub Action (requires ECR and ECS permissions)
Proceed with these as created.
Create a workflow like the one below and save it to .github/workflows/ in the repository with a suitable name like deploy-to-ecs.yml.
By pushing the tag to the main branch, this workflow works, the Docker image is pushed to the ECR and deployed to the ECS.
Operate by using [Release] of Github (https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository) I'm assuming that.
.github/workflows/deploy-to-ecs.yml
name: Deploy to ECS 
on:
  push:
    tags:
      - v*
env:
  ECR_REPOSITORY: your-repository-name
  ECS_SERVICE: your-service-name
  ECS_CLUSTER: your-cluster-name
jobs:
  deploy:
    name: Deploy to ECS
    if: github.event.base_ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Configure AWS Credentials #AWS access permission settings
        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: ap-northeast-1
      - name: Login to Amazon ECR #ECR login process
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1
      - name: Set Docker Tag Env #Match Docker Image version to tag
        run: echo "::IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")" >> $GITHUB_ENV
      - name: Build, tag, and push image to Amazon ECR #Docker image build&Push
        env:
          DOCKER_BUILDKIT: 1
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          docker build --cache-from=$ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg BUILDKIT_INLINE_CACHE=1 -f Dockerfile -t $ECR_REPOSITORY .
          docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
      - name: Render Amazon ECS task definition for app container #ECS task definition file rendering of app container
        id: render-app-container
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: .aws/ecs/task-definition.json
          container-name: app
          image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
      - name: Deploy to Amazon ECS service #ECS service deployment
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.render-app-container.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: false
The workflow operation will be explained in detail from the top.
name: Deploy to ECS 
I have decided on the name of the workflow. This name will be displayed when you check the workflow in the list from the repository of Github or the Action tab. When creating multiple workflows in a verification or production environment, it is recommended to rename them so that they are easy to distinguish.
on:
  push:
    tags:
      - v*
It works when tags starting with v such as v1.0.0 are pushed, and Release It is assumed that this workflow will work by creating (/ github / administration-a-repository / managing-releases-in-a-repository).
Also, this time in jobs so that it will not work if the tag is pushed to another branch
if: github.event.base_ref == 'refs/heads/main'
If it is not the main branch like, it will not work.
To make it work when pushed to a specific branch, remove the if description
on:
  push:
    branches:
      - target-branch
Please describe as. (Change target-branch to the branch you want to deploy.)
In order to realize that it works when a tag is pushed to a specific branch by the description method of on: push:
on:
  push:
    tags:
      - v*
     branches:
      - target-branch
If you write like, it will be an or condition (when either the tag or the branch is pushed) instead of the and condition, so specify the target tag with on: push: and target with if The branch of is specified.
env:
  ECR_REPOSITORY: your-repository-name
  ECS_SERVICE: your-service-name
  ECS_CLUSTER: your-cluster-name
I am assigning it to an environment variable to reduce the description of the value that is used many times in job.
Please change your-〇〇〇-name to the one in your environment
jobs:
  deploy:
    name: Deploy to ECS
    if: github.event.base_ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
Check out to the target branch in the environment of ubuntu-latest and start executing the job.
(As I mentioned earlier, I wrote the if line so that it will be executed only in the main branch when the tag is pushed. It is not necessary if the operating condition is changed to push in the branch.)
- name: Configure AWS Credentials #AWS access permission settings
    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: ap-northeast-1
I am logged in to the AWS account that has created the ECR to push and the ECS to deploy to.
Deploy using Github's Secret (https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets) IAM access key and secret key AWS_ACCESS_KEY_ID Please register with and AWS_SECRET_ACCESS_KEY.
 - name: Login to Amazon ECR #ECR login process
    id: login-ecr
    uses: aws-actions/amazon-ecr-login@v1
Log in to the ECR of the AWS account you logged in with in step 5.
  - name: Set Docker Tag Env #Match Docker Image version to tag
    run: echo "::IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")" >> $GITHUB_ENV
The tag pushed so that the image tag of Docker has the same value as the tag of git is taken out in a format like v1.0.0 and assigned to the environment variable.
If you do not use tags in the workflow operating conditions,
run: echo "::IMAGE_TAG=${{ github.sha }}" >> $GITHUB_ENV
I think it's a good idea to use a commit hash to make the image tag unique.
  - name: Build, tag, and push image to Amazon ECR #Docker image build&Push
    env:
      DOCKER_BUILDKIT: 1
      ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
    run: |
      docker build --cache-from=$ECR_REGISTRY/$ECR_REPOSITORY:latest --build-arg BUILDKIT_INLINE_CACHE=1 -f Dockerfile -t $ECR_REPOSITORY .
      docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
      docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
      docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
      docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
When building a Docker image locally, the build speeds up using the previously built image, but GitHub Action does not retain the previous build result, so it takes time to build each time.
Therefore, here, Docker's docker build --cache-from is used to speed up the build.
What we are doing is building a docker image, pushing the tag saved in the environment variable IMAGE_TAG, and the value changes dynamically for each workflow operation, so the value at the time of the previous build is used. Since it is difficult to get it, I also push the latest tag and use the latest tag for caching. By doing this, the cache is used while being consistent with the release tag.
(DOCKER_BUILDKIT, BUILDKIT_INLINE_CACHE is described because it is necessary to use --cache-from.)
Note: If you're using Docker's multi-stage build, you can't cache this method because the Docker image doesn't include the entire build process. In that case, I think you should refer to Articles like this.
- name: Render Amazon ECS task definition for app container #ECS task definition file rendering of app container
    id: render-app-container
    uses: aws-actions/amazon-ecs-render-task-definition@v1
    with:
      task-definition: .aws/ecs/task-definition.json
      container-name: app
      image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
To execute ECS, a task definition is required, and it is necessary to describe the repository and tag information of the Docker image to be used there. Therefore, when updating DockerImage, it is also necessary to update the task definition, and that process is being performed.
今回はレポジトリへタスク定義ファイル.aws/ecs/task-definition.jsonを作成してあり、そのファイルを呼び出し、Dockerイメージの情報を更新しています。
Reference example of task-definition.json)
{
  "containerDefinitions": [
      "portMappings": [
        {
          "hostPort": 80,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "image": "your-image-name",
      "name": "app"
    }
  ],
  "cpu": "256",
  "executionRoleArn": "your-role-name",
  "family": "your-family",
  "memory": "512",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "networkMode": "awsvpc"
}
Reading from s3 seems to be possible, so please manage each task definition you like.
- name: Deploy to Amazon ECS service #ECS service deployment
    uses: aws-actions/amazon-ecs-deploy-task-definition@v1
    with:
      task-definition: ${{ steps.render-app-container.outputs.task-definition }}
      service: ${{ env.ECS_SERVICE }}
      cluster: ${{ env.ECS_CLUSTER }}
      wait-for-service-stability: false
It is deployed to ECS based on the task definition created in 9.
By setting wait-for-service-stability to true, it is possible to wait for the completion of execution until the deployment is completed and notify the completion of deployment, but the execution time of Github Actions will increase. There is a possibility that it will exceed the execution frame.
It is also possible to hook the completion of deployment using AWS Lambda and notify it, so it is recommended to do so in an environment with many deployments.
If the above flow works correctly, the deployment is complete.
- name: Run Migrate #Run Migration
    env:
      CLUSTER_ARN: your_cluster_arn
      ECS_SUBNER_FIRST: your_subnet_first
      ECS_SUBNER_SECOND: your_subner_second
      ECS_SECURITY_GROUP: your_security_group
    run: |
      aws ecs run-task --launch-type FARGATE --cluster $ECS_CLUSTER --task-definition ${{ steps.put-migrate-task.outputs.render-app-container }} --network-configuration "awsvpcConfiguration={subnets=[$ECS_SUBNER_FIRST, $ECS_SUBNER_SECOND],securityGroups=[$ECS_SECURITY_GROUP],assignPublicIp=ENABLED}" > run-task.log
      TASK_ARN=$(jq -r '.tasks[0].taskArn' run-task.log)
      aws ecs wait tasks-stopped --cluster $CLUSTER_ARN --tasks $TASK_ARN
I wanted to migrate the DB before deploying this time, so I added such a job to the ECS in step 10 before deploying and executed the task alone. By doing this, it is also possible to execute a single task within Github Actions.
When deploying to ECS, I think there are options such as combining AWS CodeBuild and AWS CodePipeline, but it is also possible to easily deploy using GitHub Action.
I have referred to the following article very much.
-Deploy an app from GitHub Actions to Amazon ECS
Recommended Posts