Skip to main content

Command Palette

Search for a command to run...

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

Updated
3 min read
CI/CD in 30 Minutes: Deploying to a VM Using GitHub Actions
S

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 NameValue
SSH_PRIVATE_KEYcontents of id_ed25519
SERVER_HOSTyour server IP
SERVER_USERyour 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 install your 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.

More from this blog

C

CodeOps Studies

39 posts

Simple write-ups on day to day code or devops experiments, tests etc.