How to setup a simple Continuous Deployment workflow using github actions with AWS
In this you will learn about how to setup github actions with AWS EC2 server.
There are two stages in this deployment workflow, one is the setup of the cloud provider( your server) and another is the workflow logic itself. The logic itself is pretty much the same accross every provider with a little bit of change.
Let’s tackle the server setup first:
AWS
We are gonna need three things from ec2 service: host, user and ssh key. First create a ssh key in your own local machine using ssh-keygen:
ssh-keygen -t ed25519 -C “github-actions” -f ./github_deploy_keyYou can name it anything you like but try to name it after your server and app to differentiate from others. This will produce two key files, one is public key and the other is private key. The public key file will have the file extension .pub.
In your ec2 server, login and add the public key to the ssh authorized keys list in ~/.ssh/authorized_keys file. Add it to the bottom.
Now goto your repository in github > settings > secrets > actions > New repository secrets. Add EC2_HOST with ipv4 address of the ec2 instance(login to aws consle and click on the instance, you will see the ipv4 address), add EC2_SSH_KEY with the generated private key from local machine. Add another one called EC2_USER with your ec2 server user account name(for new server it will be ec2-user most of time. If you want to create a new use sudo adduser username). Never use root user account.
Now we’re done with the server setup.
Workflow Configuration
The logic for deployment workflow is this: Trigger > Pull > Build > Restart.
This is our trigger code. When branches are merged into the main branch the workflow will get trigged.
on:
push:
branches: [ main ]The workflow will automatically start a disposable virtual server on github not your server.
jobs:
deploy:
runs-on: ubuntu-latestThis code will download the repository code in the disposable virtual server. You can have this code in the file or not. For simple setup it’s not needed. When you need it: for running tests and if you have deploy script, you need to checkout the repo to run the script.
- name: Checkout code
uses: actions/checkout@v3And then it will open a secure tunnel to your server using the secrets we have provided in the github actions secrets.
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
port: 22
command_timeout: 30m
script: |command timeout is to wait for the script commands to run for 30 minutes. After that it will timeout. If your server is in nodejs, you’re gonna need the nvm script command. Navigate to your repository in the server and then run git pull, clean insttall and then build the project and then restart your app.
script: |
# 1. Environment Variables for amazon linux
export NVM_DIR=”$HOME/.nvm”
[ -s “$NVM_DIR/nvm.sh” ] && \. “$NVM_DIR/nvm.sh”
# 2. Navigation
cd /var/www/my-app
# 3. Git Pull
git pull origin main
# 4. Install & Build
npm ci
npm run build
npx pm2 restart all
This is most simple workflow if your app is just at the beginning. This workflow doesn’t address some problems like zero downtime, npm install can run out of memory, npm build on the server can slow down your server which can effect users in realtime.
However there is a level up version that uses: Trigger > Build > Push > Restart. In this way you don’t install or build anything on the server. The disposable virtual server from github handles installing, building the code during checkout step. And then it produces an artifact(dist) which can be run directly on the server. In the next article, I will write how to deploy code with pro workflow and zero downtime.



Excellent breakdown of the Github Actions workflow! Your point about running builds directly on the EC2 instance is something many devlopers overlook when setting up their first CI/CD pipline.
What's particularly smart is flagging the memory constraint issue with npm install on smaller instances. In practice, buildingon the GitHub runner and then pushing the artifact eliminates not just memory concerns but also keeps your production server more predictable since you're not competing with user traffic during deploy cycles.
One thing worth considering: even with the simpler pull-build-restart pattern, you can still get decent uptime by using PM2's cluster mode with reload instead of restart, which does a rolling restart of worker processes.