Every time you push code, you SSH into your server, pull the latest changes, restart services, and hope nothing breaks. Sound familiar? There's a better way.
GitHub Actions lets you automate your entire deployment pipeline — from running tests to deploying code — every time you push to your repository. No more manual deployments, no more forgotten steps, no more "it works on my machine" problems.
Here's how to set it up from scratch.
What Are GitHub Actions?
GitHub Actions is a CI/CD (Continuous Integration / Continuous Deployment) platform built directly into GitHub. You define workflows in YAML files, and GitHub runs them automatically based on triggers like pushing code, opening pull requests, or on a schedule.
Push to main → Run tests → Build → Deploy to server
The best part? It's free for public repositories and includes 2,000 minutes per month for private repos on the free tier.
Your First Deployment Workflow
Let's build a real workflow that deploys a website to a VPS whenever you push to the main branch.
Step 1: Create the Workflow File
In your repository, create .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 to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/mysite
git pull origin main
npm install --production
npm run build
pm2 restart myapp
Step 2: Add Repository Secrets
Never hardcode credentials. Go to your GitHub repo → Settings → Secrets and variables → Actions, and add:
SERVER_HOST— Your server's IP address (e.g.,203.0.113.50)SERVER_USER— SSH username (e.g.,deploy)SSH_PRIVATE_KEY— Your private SSH key
To generate a deployment key:
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/deploy_key
Add the public key to your server's ~/.ssh/authorized_keys and paste the private key into the GitHub secret.
Step 3: Push and Watch
Commit the workflow file and push to main. Go to the "Actions" tab in your repo to watch the deployment run in real time.
Adding Tests Before Deployment
Deploying untested code is risky. Add a test step that runs before deployment:
name: Test and Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/mysite
git pull origin main
npm install --production
npm run build
pm2 restart myapp
The needs: test directive ensures deployment only happens after all tests pass.
WordPress Deployment Workflow
For WordPress sites, the workflow looks slightly different:
name: Deploy WordPress Theme
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy theme via rsync
uses: burnett01/rsync-deployments@6.0.0
with:
switches: -avzr --delete --exclude='.git' --exclude='.github'
path: wp-content/themes/mytheme/
remote_path: /var/www/mysite/wp-content/themes/mytheme/
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SSH_PRIVATE_KEY }}
Rsync only transfers changed files, making deployments fast and efficient.
Laravel Deployment
Laravel projects need a few extra steps:
name: Deploy Laravel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/mylaravel
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
sudo systemctl reload php8.2-fpm
Deployment Notifications
Get notified when deployments succeed or fail. Add a Slack or Discord notification step:
- name: Notify on success
if: success()
run: |
curl -X POST "${{ secrets.DISCORD_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d '{"content": "✅ Deployment successful: ${{ github.repository }} (${{ github.sha }})"}'
- name: Notify on failure
if: failure()
run: |
curl -X POST "${{ secrets.DISCORD_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d '{"content": "❌ Deployment FAILED: ${{ github.repository }} — check Actions tab"}'
Best Practices
Use Environment Protection Rules
For production deployments, enable environment protection in GitHub:
- Go to Settings → Environments → New environment → "production"
- Add required reviewers (optional — someone must approve before deploy)
- Restrict to
mainbranch only
Keep Secrets Secure
- Never echo secrets in logs
- Rotate SSH keys periodically
- Use deploy-specific keys with limited permissions
- Consider using OIDC for cloud providers instead of long-lived keys
Handle Rollbacks
Add a manual rollback workflow:
name: Rollback
on:
workflow_dispatch:
inputs:
commit_sha:
description: 'Commit SHA to rollback to'
required: true
jobs:
rollback:
runs-on: ubuntu-latest
steps:
- name: Rollback deployment
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/mysite
git fetch origin
git checkout ${{ github.event.inputs.commit_sha }}
npm install --production
npm run build
pm2 restart myapp
Trigger this manually from the Actions tab when you need to revert a bad deploy.
Common Pitfalls to Avoid
🚩 No test step — Always test before deploying. A broken deployment takes your site down.
🚩 Root SSH access — Create a dedicated deploy user with limited permissions.
🚩 No timeout — Add timeout-minutes: 10 to your jobs to prevent hung deployments.
🚩 Deploying on every push — Only deploy from your main branch. Use pull requests for code review.
🚩 No notification on failure — If you don't know a deployment failed, you can't fix it.
Automate Your Deployments with DeployBase
GitHub Actions handles the CI/CD pipeline, but you still need reliable infrastructure to deploy to. At DeployBase, our VPS plans come with full SSH access, pre-installed Git, and the performance your applications need. Starting at $5/month with NVMe SSD storage, automated backups, and 24/7 support.
Pair GitHub Actions with DeployBase hosting and you get a professional deployment pipeline that rivals setups costing ten times as much.
Get your VPS at DeployBase → — reliable hosting for automated deployments.




