DEV Community

Orbit Websites
Orbit Websites

Posted on

GitHub Actions Mastery: A Step-by-Step Guide to Fully Automated Deployments

GitHub Actions Mastery: A Step-by-Step Guide to Fully Automated Deployments

Let’s be honest: manually deploying code is a waste of time and a recipe for human error. If you're still SSH-ing into servers or running git pull by hand, you're not just slowing yourself down — you're risking downtime, missed steps, and burnout. GitHub Actions gives you the power to automate the entire deployment pipeline, from test to production, right from your repo. Let’s walk through how to set up a reliable, repeatable, and fully automated deployment workflow.


1. Understand the Core: Workflows, Jobs, and Steps

GitHub Actions uses YAML files in .github/workflows to define workflows. Each workflow can have multiple jobs, and each job runs a series of steps — including running scripts, checking out code, or deploying.

Here’s a minimal example to get your head around the structure:

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.2
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/myapp
            git pull origin main
            npm install
            pm2 restart myapp
Enter fullscreen mode Exit fullscreen mode

This runs on every push to main, checks out your code, and runs a deployment script over SSH. Simple, but effective.


2. Set Up Secrets — Never Hardcode Credentials

Never put passwords, API keys, or SSH keys in your YAML. Use GitHub Secrets instead.

Go to:

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

Add these (example names):

  • HOST = your server IP or domain
  • USERNAME = SSH user (e.g., ubuntu)
  • SSH_KEY = your private deploy key (use a key pair dedicated to CI)

You can generate a deploy key like this:

ssh-keygen -t rsa -b 4096 -C "github-actions@myapp.com" -f ./deploy-key -N ""
Enter fullscreen mode Exit fullscreen mode

Add the public key (deploy-key.pub) to ~/.ssh/authorized_keys on your server.


3. Add Testing Before Deployment

Automated deployment is great — but only if your code works. Always run tests first.

jobs:
  test:
    runs-on: ubuntu-latest
    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: Run tests
        run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      # ... deployment steps
Enter fullscreen mode Exit fullscreen mode

Now deploy only runs if test passes. This stops broken code from reaching production.


4. Use Environments for Safety

GitHub Environments add control and visibility. You can require approvals, set deployment branches, and store environment-specific secrets.

In GitHub:

Settings → Environments → New Environment → Name it production

Then update your workflow:

deploy:
  environment: production
  runs-on: ubuntu-latest
  needs: test
  steps:
    # ... same as before
Enter fullscreen mode Exit fullscreen mode

Now deployments to production show up in the Environments tab, and you can require manual approval:

environment:
  name: production
  url: https://myapp.com
Enter fullscreen mode Exit fullscreen mode

Then in the environment settings, enable Required reviewers. Now every deploy needs a human thumbs-up.


5. Handle Rollbacks (Because Things Break)

Automated deployment should include a rollback plan. One way: tag your deploys and keep previous versions.

Example: use git tags to mark releases.

- name: Tag release
  run: |
    git config user.name "GitHub Actions"
    git config user.email "actions@github.com"
    git tag -a v${{ github.run_number }} -m "Release ${{ github.run_number }}"
    git push origin v${{ github.run_number }}
Enter fullscreen mode Exit fullscreen mode

Then, if something breaks, you can rollback with:

git checkout v123
git push origin main --force
Enter fullscreen mode Exit fullscreen mode

Or better: build a rollback job in your workflow:

rollback:
  runs-on: ubuntu-latest
  needs: deploy
  if: failure()
  steps:
    - name: Rollback to last tag
      uses: appleboy/ssh-action@v1.0.2
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        key: ${{ secrets.SSH_KEY }}
        script: |
          cd /var/www/myapp
          git checkout v$(git describe --tags --abbrev=0 --exclude='*rollback*')
          pm2 restart myapp
Enter fullscreen mode Exit fullscreen mode

This runs only if the deploy fails. Not perfect, but better than nothing.


6. Deploy to Different Stages: Staging First

Never deploy straight to production. Use staging.


yaml
on:
  push:
    branches: [ main, develop ]

jobs:
  test:
    # ... same as

---

☕ **Playful**
Enter fullscreen mode Exit fullscreen mode

Top comments (0)