Building Scalable GO Application With Docker, AWS, and GitHub Actions
Part 3: How to Deploy Go Apps to AWS EC2 with GitHub Actions
Introduction
In the last part, we successfully put the Go application in a Docker container. Now, we need to deploy it so others can access it.
First, we need to upload our Docker application to Docker Hub. We'll start with that and then set up a GitHub workflow action to help us deploy our application to AWS EC2.
You might wonder why we use GitHub Actions. In real-world projects, things change often, and you can't manually deploy the application every time. To automate this, we use a tool like GitHub Actions.
Let's begin with Docker Hub, and then we'll create the workflow file.
Upload Go Application to DockerHub
DockerHub is like GitHub but for Docker images. It's an online place where developers can find and share Docker images.
* You make your custom image on your computer and push it to DockerHub for others to use.
* As developers, we just need to pull these images using the command
docker pull
, and we're ready to go.* Now, let's upload our Go application to DockerHub.Step 1: Login to Dockerhub
First, we need to log into the docker hub in order to upload.
Run the below command.
docker login
This takes you to the browser where you can log in or sign up. Once you do that, you'll see a message confirming your login was successful.
- Once you are logged in, go to
hub.docker.com
- Once you are logged in, go to
Create Repository
- Just like GitHub needs a repository to store code, Docker Hub needs a repository to store Docker images. Go to the
Repositories
tab in the Navbar.
- And create a public repository. I already have one, so I won't create it again.
Push Image
- Once you've created the repository, you can push the image we made using the
docker push
command. But first, we need to tag our local image aslatest
, as shown below.
docker tag anonymous-go dhruvnakum/anonymous-go:latest
- And now let’s push it
docker push dhruvnakum/anonymous-go:latest
Replace
dhruvnakum
with your username, and if you named the repository differently, use that name instead.After you run this, the image will be successfully pushed to the repository we just created.
Now, let's create a workflow to automate deployment.
Creating the GitHub Actions Workflow
- To automate deployment, we will set up a GitHub Actions workflow.
Create the Workflow File
- In the project, create a new file at
.github/workflows/cicd.yml
.
Define the CI/CD Pipeline
name: Deploy Go Application
on:
push:
branches:
- ec2
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v4
- name: Create .env file
run: echo "PORT=${{ secrets.PORT }}" >> .env
- name: Login to docker hub
run: sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build docker image
run: sudo docker build -t dhruvnakum/anonymous-go .
- name: Push image to docker hub
run: sudo docker push dhruvnakum/anonymous-go:latest
deploy:
needs: build
runs-on: self-hosted
steps:
- name: Delete old anonymous-go container
run: sudo docker rm -f anonymous-go
- name: Pull new anonymous-go docker image
run: sudo docker pull dhruvnakum/anonymous-go
- name: Delete old mongo container
run: sudo docker rm -f mongo-demo
- name: Pull new mongo docker image
run: sudo docker pull mongo:8.0
- name: Delete old network
run: sudo docker network rm anonymous-go-nw
- name: Create networks
run: sudo docker network create anonymous-go-nw
- name: Run mongo container
run: sudo docker run --name mongo-demo --network anonymous-go-nw -d mongo:8.0
- name: Run docker container
run: |
sudo docker run --name anonymous-go \
--network anonymous-go-nw \
-p 3000:3000 \
-e MONGODB_URI="mongodb://mongo-demo:27017" \
-e JWT_SECRET="secret" \
-e REDIS_SECRET="secret" \
-e REDIS_ADDR="redis-11068.c261.us-east-1-4.ec2.redns.redis-cloud.com:11068" \
-e REDIS_PASSWORD="DM4iBq2pTYNQkweBZmwqGKyDYrj872M8" \
-e PORT=3000 \
-d dhruvnakum/anonymous-go:latest
Let me explain what's happening here:
First, we named the workflow
Deploy to Go Application
.To be safe, I created a new branch called
ec2
and pushed all the code to it. We want the workflow to run automatically whenever this branch is updated. Here's how we do it:
on:
push:
branches:
- ec2
We have two jobs: Build and Deploy.
In the Build job, we download our project's code, build a Docker image from it, and push it to Docker Hub.
In the Deploy job, we pull the Docker image of our project and the Mongo image from Docker Hub, run them inside one container as we did before, and finally, we run that container.
Build Job
- We want this job to run on the latest Ubuntu operating system.
jobs:
build:
runs-on: ubuntu-latest
- Now we want to download our project’s code from GitHub inside this OS, so we write this:
name: Checkout source code
uses: actions/checkout@v2
- Since we have environment variables in our code, we need to make them available here. To do this, we must add these secrets in
GitHub Action Secrets
. Go to the project's settings, and under secrets and variables, add these secrets.
- Now, to get this from there, we create .env inside this OS and push everything:
- name: Create .env file
run: echo "PORT=${{ secrets.PORT }}" >> .env
- Now that we have all the secrets. We Login to the docker:
- name: Login to docker hub
run: sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- Then we build the docker image of our project:
- name: Build docker image
run: sudo docker build -t dhruvnakum/anonymous-go .
- Finally, we push the image to the docker hub:
- name: Push image to docker hub
run: sudo docker push dhruvnakum/anonymous-go:latest
Deploy Job
In this job, everything we write will run on the AWS EC2 instance provided by GitHub.
We want this job to start after the build job finishes successfully.
deploy:
needs: build
- In this job, we first want to delete all the old containers and pull the latest ones. Once that is done, we run the container using
docker run
command.
deploy:
needs: build
runs-on: self-hosted
steps:
- name: Delete old anonymous-go container
run: sudo docker rm -f anonymous-go
- name: Pull new anonymous-go docker image
run: sudo docker pull dhruvnakum/anonymous-go
- name: Delete old mongo container
run: sudo docker rm -f mongo-demo
- name: Pull new mongo docker image
run: sudo docker pull mongo:8.0
- name: Delete old network
run: sudo docker network rm anonymous-go-nw
- name: Create networks
run: sudo docker network create anonymous-go-nw
- name: Run mongo container
run: sudo docker run --name mongo-demo --network anonymous-go-nw -d mongo:8.0
- name: Run docker container
run: |
sudo docker run --name anonymous-go \
--network anonymous-go-nw \
-p 3000:3000 \
-e MONGODB_URI="mongodb://mongo-demo:27017" \
-e JWT_SECRET="secret" \
-e REDIS_SECRET="secret" \
-e REDIS_ADDR="redis-11068.c261.us-east-1-4.ec2.redns.redis-cloud.com:11068" \
-e REDIS_PASSWORD="DM4iBq2pTYNQkweBZmwqGKyDYrj872M8" \
-e PORT=3000 \
-d dhruvnakum/anonymous-go:latest
Before we move ahead I forgot to change the URL in the main from localhost to
0.0.0.0
. So make sure you have this inside main.go at the end.
log.Fatal(r.Run("0.0.0.0:" + os.Getenv("PORT")))
Setting Up AWS EC2 Instance
Before deploying the project, we need to set up an EC2 instance.
To do this, log in to the AWS Management Console and go to the EC2 dashboard.
Use this link: https://aws.amazon.com/ec2/
It might ask for your card details, but don't worry; they won't charge you unless you exceed the limit, which you won't be charged for for this purpose.
Search for EC2 and click on it.
Now click on the “Launch Instance.”
- Now, give the instance name and select the Ubuntu image
- Also, select Key pair. If you don’t have one. You can create it by clicking on the Create new key pair button.
- Once it is done, click on the Launch Instance button on the right
Since our app is running on port 3000, go to the Security Group settings and allow inbound traffic on port 3000 to make sure the application is accessible.
To do that, click on the Instance ID
- Then click on the Security tab and there click on Security Group
- Click on Edit inbound rules and then add custom TCP with Port range 3000 as shown below and save the rules.
- Now, we are ready to run the instance.
Setting up Docker inside EC2 Instance,
- Our instance is ready. First, we need to make sure Docker is installed. To do this, let's connect to the instance. Click the Connect button after selecting the instance.
- Once you do that you will see a window like this:
To confirm whether docker is there or not, run
docker
command, you will most likely see this:Now, to install everything, run the below commands
sudo apt-get update && sudo apt-get install docker.io -y && sudo systemct| start docker && sudo chmod 666 /var/run/docker.sock && sudo systemcti enable docker && docker --version
- Once it is done, run
docker version
to see if it’s installed correctly.
Register a Self-Hosted GitHub Runner
A self-hosted runner is simply a computer or server that you own (like an AWS EC2 instance) that runs GitHub Actions instead of using GitHub’s default machines.
To set up, go to the project settings, and inside Actions, you will see this runner tab.
Click on `New self-hosted runner:
Select Linux
- And run all the commands you see there inside your EC2.
- Once you run the last step
./run.sh
command, you will see a runner with the status idle :
But there is one problem, that runner does not run on detached mode in our instance, If you kill it by pressing CTRL+C, you wont be able to run it.
To resolve this issue, just run the below command
sudo ./svc.sh install
sudo ./svc.sh start
Now, your running is running in the background, and you can perform other operations inside your instance.
Finally, we are done with it. Now, we can test our application.
Testing Your Application
- To test, commit to the
ec2
branch that we created. Once you do that, Go to the Actions tab, and there you will see bothbuild
deploy
services are running.
- Once it is done, you will see both marked as green ticks, which means that everything went well and the docker is successfully up inside the ec2.
- To test, go to the instance we created and find the
Public IPv4 address
. Copy it, paste it into Postman, and run any endpoint in your application.
- As you can see it’s working now.
- Phew! I know that seems like a lot at first. But once you follow these steps, you'll get comfortable with it.
Wrapping Up
If you've made it to the end of this guide, you truly deserve a big thank you. So, thank you for sticking with it and reading all the way through. I hope you've gained a lot of valuable insights from this single blog post, covering everything from creating a GitHub Workflow to understanding Actions, setting up a Runner, and working with AWS EC2.
Keep diving deeper into these topics and explore other related concepts, as they are essential for cloud development in today's fast-paced tech world. Mastering these skills will undoubtedly enhance your ability to build and deploy applications efficiently.
I look forward to sharing more knowledge with you in the next blog post! Until then, keep learning and experimenting with new technologies.
Reference: https://www.youtube.com/watch?v=sSAWMr_-Co4&t=409s