How I set up my first GitHub actions workflow for AWS EC2


the problem
apparently, i ran into a problem where my project was hosted on an AWS EC2 instance (which is basically just a VPS, AWS just gave it a fancy name). every time i made a small update or fixed something, i had to SSH into the server and pull the latest changes manually. at first, it felt okay, but doing this over and over became very tiring and boring.
what needed
so i needed a solution that automatically deploys the code to the server whenever i push to a specific branch - in my case, the main
branch (this branch contains only deployment/production-ready codes).
how I thought of automating
since i already knew GitHub actions is a feature that helps automate tasks, i thought maybe it could handle this deployment thing too.. the idea was: if GitHub actions can trigger whenever i push to the production branch, then maybe it could also SSH into my server and run the deployment steps for me.
doing some research
so i digged a little deeper into what GitHub actions actually does. during my research, i found an official blog/documentation written by GitHub itself, which helped a lot (docs.github.com/en/actions). this gave me a good idea about workflows, triggers, jobs, and how to write the scripts.
starting the setup
so first up, i created a .github > workflows
folder into my repository. this is the directory where GitHub looks for workflow configuration files. the files should have the .yml
extension, in my case i created a /.github/workflows/deploy.yml
file.
writing the workflow
basically, the workflow file is a .yml
file where you'd have to write the steps that GitHub’s virtual machine (runner) will take. it's kinda like writing a bash script where commands are listed line-by-line. in this file, i defined when to run the workflow (in my case, on push to main), then added steps like setting up SSH, accessing the server, and pulling the latest code.
in my case it was a nextjs app.
the script looked like this:
name: Deploy to AWS EC2
on:
push:
branches:
- main
jobs:
production-deploy:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to EC2
uses: appleboy/ssh-action@master
env:
PM2_APP_NAME: ${{ secrets.PM2_APP_NAME }}
APP_DIR: ${{ secrets.APP_DIR }}
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_PRIVATE_KEY }}
envs: PM2_APP_NAME, APP_DIR
script: |
cd $APP_DIR
git pull
npm ci
rm -rf .next
npm run build
pm2 reload $PM2_APP_NAME
nothing fancy, just a clear list of instructions:
– it installs dependencies
– builds the app
– connects to my ec2 instance over ssh
– pulls the latest code from git
– installs dependencies again on the server
– builds it again on server
– and finally, reloads the app using pm2
i used appleboy/ssh-action for the ssh part, it’s dead simple and works well for quick deployments like this. these can be found on Actions Marketplace
you’ll notice i didn’t copy the entire build folder from the runner to the server. later on, i actually switched to doing that using rsync
, and it made a noticeable difference. i’ll write a separate blog on that soon, where i’ll also share how i refactored the process and cut down the deployment time from around ~7.5 mins to ~5 mins.
N:B: you also need to store some secrets in your repo’s settings (under Settings > Secrets and variables > Actions).
i added my EC2_HOST
, EC2_PRIVATE_KEY
, APP_DIR
, and PM2_APP_NAME
there.
that way, nothing sensitive ends up in the codebase.
thanks for reading..
to read all of my journey and experiments checkout my blogs here
peace! ✌️
Subscribe to my newsletter
Read articles from Muhsin Azmal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Muhsin Azmal
Muhsin Azmal
The right path is not always the easiest one.