Spring Boot Gradle build to ECS: A simple Github Actions workflow


Where I started?
I already had a Spring boot microservice that builds with Gradle and has a docker file. I used to manually deploy in ECS. I needed to automate this workflow so that it deploys automatically on commit.
Here is what I already had,
Github repository
Code that is built using Gradle with Dockerfile
ECR container repository to store images
ECS service with task definition already created
Process
We need to have a plan of what we want the Actions workflow to do. Here is the list of things I wanted to do in the workflow.
Step 1: Get triggered on commits to a particular release branch
Step 2: Checkout and build the code
Step 3: Configure AWS ECR container repository access
Step 4: Docker build, tag and push the image to the ECR
Step 5: Re-deploy the ECS service
Create the Actions Workflow
Go to Actions tab in the repository and click on ‘New Workflow’. You can choose to build from scratch or choose one of the templates. There are many options to choose. Since, my service is built using Gradle, I chose a simple Java with Gradle. This will show an editor with a file under .github/workflows. You can choose to name this file anything you want. This is where we write our workflow.
Give a name for this workflow if you choose. Then comes the section where you define when this workflow needs to be triggered.
Step 1: Get triggered on commits to a particular release branch
The on push or pull_request defines that the actions workflow will get triggered on activity in the ‘main’ branch. If you have another release branch, you can mention it instead.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
Step 2: Checkout and build the code
Now comes, what to do after getting triggered. I need the workflow to define a set of steps or what Github calls “jobs”. You can define each step you want to do as a “job”.
The first job is to build the code. For this, I need to define a runner for Github Action. Github Actions provide free runners (with specific limits). There are options to use Github cloud or Self-hosted runners too. In this example, I am using the public runner available from Github.
I choose the ‘ubuntu-latest’ as the platform to run my build on.
Then comes the steps in the job. I need to checkout the code, setup the right Java version and then build with Gradle. I use jdk17 and hence I specify that and choose the distribution as Eclipse Temurin after looking at different distribution options.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: Package
path: build/libs
Finally, the built jar is then uploaded to a specific location (build/libs).
Step 3: Configure AWS ECR container repository access
There are existing actions available to manage login to AWS especially to ECR container repository so that the built Docker images can be pushed. To use this, you need to have secrets defined in your repository. As you know, it is a bad idea to have access and secret keys directly in the repository. These are environment variables for Github Actions runner to read and work on.
Defining repository secret(s)
Go to the repository -> Click on ‘Settings’ tab -> Go to Secrets and variables in the left side navigation menu. Choose ‘Actions’.
Then click on ‘New repository secret’ and add the values.
Once done, you can refer to these secerts directly in the workflow.
- name: Configure 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: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
Step 4: Docker build, tag and push the image to the ECR
The ECR_Registry is the output of the login step performed earlier. The repository name (ECR_REPOSITORY) here is defined a Gihub repository secret (as defined in the step earlier).
The next step executes the docker build and tags the image as latest.
Note: I am tagging the image as latest for the build. It is advisable to use specific release versions in order to have better traceability.
Then finally the image is pushed to the container repository.
- name: Build, tag, and push the image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ secrets.REPO_NAME }}
IMAGE_TAG: latest
run: |
# Build a docker container and push it to ECR
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
echo "Pushing image to ECR..."
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
# echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
Step 5: Re-deploy the ECS service
You can either define this as a separate job or add it as a step as part of the previous job. I chose to do the latter.
Just need to add a step to update the service by forcing deployment. Please note that force deployment typically pulls the images from the repository for FARGATE launch type. For provisioned (with EC2), the image may get cached and that might need additional step(s).
aws ecs update-service --cluster <cluster_name> --service <service_name> --force-new-deployment
It is also safer to logout from the AWS ECR once the run is completed. It is especially important if you choose to run from public runners.
- name: Logout of Amazon ECR
run: docker logout ${{ steps.login-ecr.outputs.registry }}
Completed Workflow
Now, that is done. Let us look at the completed workflow.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: Package
path: build/libs
- name: Configure 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: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push the image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ secrets.REPO_NAME }}
IMAGE_TAG: latest
run: |
# Build a docker container and push it to ECR
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
echo "Pushing image to ECR..."
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
# echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
aws ecs update-service --cluster <cluster_name> --service <service_name> --force-new-deployment
- name: Logout of Amazon ECR
run: docker logout ${{ steps.login-ecr.outputs.registry }}
Validating this is simple. Do any buildable commits to the branch you have set the trigger on (in this case ‘main’) and the actions wofkflow will trigger and execute. The outcome of the build is shown in the ‘Actions’ tab of the repository.
That’s it. Hope this helps.
Subscribe to my newsletter
Read articles from Ramesh Lakshmipathy directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
