GitHub Actions lets you automate testing and deployment directly from your repository, without needing a separately hosted CI/CD server for many use cases. This guide covers building a complete pipeline: test, build, and deploy to your VPS.
Prerequisites
- A GitHub repository containing your application
- SSH access to your VPS
Step 1 — Generate a Deploy-Specific SSH Key
On your local machine (not the VPS):
ssh-keygen -t ed25519 -f deploy_key -C "github-actions-deploy" -N ""
Step 2 — Add the Public Key to Your VPS
cat deploy_key.pub | ssh deploy@YOUR_SERVER_IP "cat >> ~/.ssh/authorized_keys"
Step 3 — Add the Private Key as a GitHub Secret
In your repository: Settings → Secrets and variables → Actions → New repository secret. Name it DEPLOY_KEY, paste the private key content.
Add additional secrets for the server IP and username similarly (SERVER_IP, SERVER_USER).
Step 4 — Create the Workflow File
mkdir -p .github/workflows
nano .github/workflows/deploy.yml
name: Test and Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
- name: Deploy
run: |
ssh -i ~/.ssh/deploy_key ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }} \
"cd /var/www/myapp && git pull origin main && npm install --production && pm2 reload myapp"
Step 5 — Push and Watch It Run
Push a commit to main and check the Actions tab in your repository to watch the pipeline execute.
The needs: Keyword
needs: test ensures the deploy job only runs if the test job succeeds — a critical safety gate preventing broken code from reaching production.
Adding a Build Step (For Compiled/Bundled Apps)
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm install
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
Deploying with Docker Instead
- name: Build and push Docker image
run: |
docker build -t myregistry/myapp:${{ github.sha }} .
docker push myregistry/myapp:${{ github.sha }}
- name: Deploy
run: |
ssh -i ~/.ssh/deploy_key user@server \
"docker pull myregistry/myapp:${{ github.sha }} && docker compose up -d"
Common Errors
"Host key verification failed" — the ssh-keyscan step wasn't run or the known_hosts file wasn't set up correctly; verify Step 4's SSH setup block.
Deployment succeeds but app doesn't update — check that the deploy commands actually reference the correct application path and process manager command.
Best Practices
- Always run tests before deployment, and gate deployment on tests passing
- Use a dedicated deploy key, not your personal SSH key, for automation
- Restrict the deploy key to only the specific server(s) it needs access to
Continue Reading
- How to Set Up a Deployment User with Restricted SSH Access
- CI/CD Pipeline Security Best Practices
- How to Deploy a Node.js Application with PM2 and Nginx
Browse more articles in DevOps & CI/CD.
