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.99/month with NVMe SSD storage, automated backups, and email 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.