CI/CD in 30 Minutes: Deploying to a VM Using GitHub Actions

DevOps & Cloud Engineer — building scalable, automated, and intelligent systems. Developer of sorts | Automator | Innovator
When I first heard the term CI/CD, it sounded like something only large companies use with complicated pipelines and enterprise tools. But CI/CD is simply this:
Whenever you push code, your app automatically builds, tests, and deploys.
No manual SSH into servers.
No git pull every time you change something.
In this post, we’ll set up a very clean CI/CD workflow using:
GitHub Actions (free)
A Linux VM on any cloud (can be DigitalOcean, AWS EC2, Proxmox VM, anything)
A simple application running on the VM
1. The Setup
You should already have:
A VM with Linux (Ubuntu recommended)
Your app running (Node, Python, doesn’t matter)
SSH access working
Let’s assume your app directory on the server is:
/var/www/myapp
and your app runs using something like:
systemctl restart myapp
For the above, we often setup our service as a systemd service, perhaps, we will talk about deploying an app with systemd in a different article!
2. Generate SSH Key for GitHub Action
On your local machine (not the server):
ssh-keygen -t ed25519 -C "github-deploy-key"
This creates:
~/.ssh/id_ed25519 (private key)
~/.ssh/id_ed25519.pub (public key)
Copy the public key to your VM:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server_ip
Test:
ssh user@server_ip
3. Add Private Key to GitHub Secrets
Go to your GitHub repo → Settings → Secrets → Actions → New Secret
| Secret Name | Value |
SSH_PRIVATE_KEY | contents of id_ed25519 |
SERVER_HOST | your server IP |
SERVER_USER | your server username |
4. Add CI/CD Workflow
Create file:
.github/workflows/deploy.yml
name: Deploy to VM
on:
push:
branches: ["main"]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
- name: Deploy to server
run: |
ssh -o StrictHostKeyChecking=no ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} "
cd /var/www/myapp &&
git pull &&
npm install --production &&
pm2 restart myapp
"
Replace
npm installyour app’s commands if needed.
5. Push to Main → Deployment Happens
Now try:
git add .
git commit -m "Test CI/CD"
git push origin main
Open GitHub → Actions Tab → You’ll see deployment logs in real time.
Once it says: Success, refresh your app, it’s live.
This is continuous deployment in its simplest, cleanest form.
Why This Matters!
This setup:
Removes the “dev to server” friction
Makes deployments repeatable and reliable
Forces clean code structure
Helps you work like teams do in production
Even if you’re a solo developer today: your future teammates will thank you.
Closing Thoughts
You don’t need Kubernetes, Helm, ArgoCD, or GitOps to start doing DevOps.
Sometimes, a 40-line GitHub Action is all you need.






