DEV Community

Orbit Websites
Orbit Websites

Posted on

Automate Your Deployments: A Step-by-Step Guide to Using GitHub Actions for Seamless Continuous Deployment

Automate Your Deployments: A Step-by-Step Guide to Using GitHub Actions for Seamless Continuous Deployment

Let’s be honest: manually deploying code is a chore. It’s error-prone, time-consuming, and the exact opposite of what we should be doing as developers. If you’re still pushing to production by hand, you’re wasting time you could spend building features, fixing bugs, or even just going home on time.

GitHub Actions gives you a powerful, free way to automate deployments right from your repo. No extra CI/CD tools, no complex setup—just YAML and a few clicks. In this guide, I’ll walk you through setting up a real-world continuous deployment pipeline using GitHub Actions.


1. Understand the Flow: What Are We Automating?

Before we write any code, let’s define what we want:

  • On every push to the main branch:
    • Run tests
    • Build the app
    • Deploy to a server or platform (we’ll use a VPS via SSH as an example)

This is continuous deployment: merge code → tests run → if they pass, it ships.


2. Set Up Your Project Structure

Assume you have a simple Node.js app (but this applies to any language). Your repo looks like:

my-app/
├── package.json
├── server.js
├── .github/workflows/deploy.yml   # ← This is where the magic happens
Enter fullscreen mode Exit fullscreen mode

The key is the .github/workflows/deploy.yml file. That’s where GitHub Actions lives.


3. Create the GitHub Actions Workflow

Start by creating .github/workflows/deploy.yml. Here’s a minimal, working example:

name: Deploy to Production

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm test

      - name: Build app
        run: npm run build

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.2
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USER }}
          key: ${{ secrets.SSH_KEY }}
          port: 22
          script: |
            cd /var/www/my-app
            git pull origin main
            npm install
            npm run build
            pm2 restart my-app
Enter fullscreen mode Exit fullscreen mode

Let’s break this down:

  • on.push.branches: Trigger on every push to main.
  • actions/checkout: Pulls your code onto the runner.
  • setup-node: Installs Node.js 18.
  • Then we run the usual: install, test, build.
  • Finally, we use appleboy/ssh-action to SSH into our server and run deployment commands.

4. Store Secrets in GitHub

You never hardcode credentials. Instead, go to:

Repo Settings → Secrets and variables → Actions → New repository secret

Add these:

Name Value
SSH_HOST Your server IP or domain
SSH_USER Your server username (e.g., ubuntu)
SSH_KEY Your private SSH key (use -----BEGIN OPENSSH PRIVATE KEY----- format)

Pro tip: Generate a deploy key with limited access. Don’t use your personal SSH key.


5. Handle Failures Gracefully

Your pipeline should fail fast and notify you.

Add a notification step (optional but useful):

      - name: Notify on failure
        if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: 'Deployment failed for ${{ github.ref }}',
              body: 'Check the workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
            })
Enter fullscreen mode Exit fullscreen mode

This creates a GitHub issue if the deploy fails. Simple, effective.


6. Optimize for Real-World Use

Skip deployments on docs-only changes

You don’t need to deploy just because someone updated a README.

on:
  push:
    branches:
      - main
    paths-ignore:
      - '**.md'
      - 'docs/**'
Enter fullscreen mode Exit fullscreen mode

Or, better: use paths to only trigger when code changes:

    paths:
      - 'src/**'
      - 'package.json'
      - 'package-lock.json'
Enter fullscreen mode Exit fullscreen mode

Cache dependencies

Speed up runs by caching node_modules:

      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
Enter fullscreen mode Exit fullscreen mode

7. Deploy to Other Platforms?

This SSH example works for VPS setups (like DigitalOcean, Linode, etc.). But if you’re using platforms like:

  • Vercel / Netlify: They have their own GitHub integrations. Still, you can trigger deploys via API.

- Render / Railway: Use their CLI or API in the workflow.


Playful tone: "Fuel my coding adventures with a virtual coffee (or a real one, if you're feeling generous)! Your support on Ko-fi helps me keep creating free tools and articles: https://ko-fi.com/orbitwebsites"

Top comments (0)