Client Area

GitHub Actions CI/CD to DomainIndia VPS — Build, Test, Deploy Automation

ByDomain India Team·DomainIndia Engineering
6 min read24 Apr 20265 views
# GitHub Actions CI/CD to DomainIndia VPS — Build, Test, Deploy Automation
TL;DR
Turn git push into production deploy. GitHub Actions runs your tests, builds artifacts, and deploys to your DomainIndia VPS — all from .github/workflows/*.yml. This guide shows working pipelines for Node.js, PHP, Python, and Docker apps with secrets, zero-downtime deploys, and PR previews.
## Why GitHub Actions - Free 2,000 minutes/month for private repos, unlimited for public - No separate CI service to run + secure - Native integration with PRs, issues, releases - Massive marketplace of pre-built actions ## Anatomy of a workflow `.github/workflows/deploy.yml`: ```yaml name: Deploy on: push: branches: [main] workflow_dispatch: # allow manual trigger from GitHub UI jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: npm ci - run: npm test deploy: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Deploy env: VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }} VPS_HOST: ${{ secrets.VPS_HOST }} run: | ... ``` ## Pattern 1 — SSH deploy (simplest) Rsync code to VPS, restart the service. ### Step 1 — Generate deploy SSH key On your laptop: ```bash ssh-keygen -t ed25519 -C "github-deploy" -f ~/.ssh/github_deploy -N "" # Creates: ~/.ssh/github_deploy (private) + ~/.ssh/github_deploy.pub (public) ``` Add public key to your VPS: ```bash ssh root@your-vps # Create restricted deploy user: useradd -m -s /bin/bash deploy mkdir -p /home/deploy/.ssh # Paste github_deploy.pub into /home/deploy/.ssh/authorized_keys chmod 700 /home/deploy/.ssh chmod 600 /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh ``` Grant specific sudo for service restart (no password): ```bash # /etc/sudoers.d/deploy deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp, /bin/systemctl status myapp ``` ### Step 2 — Add secrets to GitHub Repo → Settings → Secrets and variables → Actions → New repository secret: - `VPS_HOST` = your vps IP or hostname - `VPS_USER` = `deploy` - `VPS_SSH_KEY` = contents of `~/.ssh/github_deploy` (private key) ### Step 3 — Workflow ```yaml name: Deploy to VPS on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup SSH run: | mkdir -p ~/.ssh echo "${{ secrets.VPS_SSH_KEY }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H ${{ secrets.VPS_HOST }} >> ~/.ssh/known_hosts - name: Install deps (locally, build artifact) run: | npm ci npm run build - name: Deploy run: | rsync -avz --delete --exclude='.git' --exclude='node_modules' -e "ssh -i ~/.ssh/id_ed25519" ./ ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }}:/home/deploy/myapp/ - name: Install deps on server + restart run: | ssh -i ~/.ssh/id_ed25519 ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} ' cd /home/deploy/myapp && npm ci --production && sudo systemctl restart myapp ' ``` ## Pattern 2 — Docker deploy Build image in CI, push to registry, pull on VPS. ```yaml name: Build & Deploy Docker on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Login to registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build & push uses: docker/build-push-action@v5 with: context: . push: true tags: | ghcr.io/yourorg/myapp:latest ghcr.io/yourorg/myapp:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max deploy: needs: build runs-on: ubuntu-latest steps: - name: Deploy via SSH env: SSH_KEY: ${{ secrets.VPS_SSH_KEY }} run: | echo "$SSH_KEY" > key && chmod 600 key ssh -o StrictHostKeyChecking=no -i key deploy@${{ secrets.VPS_HOST }} ' docker pull ghcr.io/yourorg/myapp:latest && docker stop myapp || true && docker rm myapp || true && docker run -d --name myapp --restart=always -p 3000:3000 --env-file /home/deploy/.env ghcr.io/yourorg/myapp:latest ' ``` ## Pattern 3 — PHP + Composer deploy ```yaml name: Deploy PHP on: push: { branches: [main] } jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: { php-version: '8.3' } - run: composer install --no-dev --optimize-autoloader - name: Rsync to VPS uses: burnett01/[email protected] with: switches: -avzr --delete --exclude=.env --exclude=storage/logs path: ./ remote_path: /home/deploy/myapp/ remote_host: ${{ secrets.VPS_HOST }} remote_user: deploy remote_key: ${{ secrets.VPS_SSH_KEY }} - name: Laravel post-deploy uses: appleboy/[email protected] with: host: ${{ secrets.VPS_HOST }} username: deploy key: ${{ secrets.VPS_SSH_KEY }} script: | cd /home/deploy/myapp php artisan migrate --force php artisan config:cache php artisan route:cache php artisan view:cache php artisan opcache:clear ``` ## Pattern 4 — Test matrix Run tests on multiple versions / environments: ```yaml jobs: test: strategy: matrix: php: [8.2, 8.3, 8.4] db: [mysql, postgres] runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_PASSWORD: postgres options: --health-cmd pg_isready steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: { php-version: ${{ matrix.php }} } - run: vendor/bin/phpunit env: DB_CONNECTION: ${{ matrix.db }} ``` ## Pattern 5 — PR preview deployments For static sites or lightweight apps — deploy each PR to `pr-123.yourcompany.com`: ```yaml on: pull_request: types: [opened, synchronize] jobs: preview: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci && npm run build - name: Deploy to subdomain run: | # upload to VPS under /var/www/previews/pr-${{ github.event.pull_request.number }} # nginx wildcard: server_name ~^pr-(?d+).yourcompany.com$; # root /var/www/previews/pr-$pr; scp -r dist/* deploy@vps:/var/www/previews/pr-${{ github.event.pull_request.number }}/ - name: Comment on PR uses: marocchino/sticky-pull-request-comment@v2 with: message: "Preview: https://pr-${{ github.event.pull_request.number }}.yourcompany.com" ``` ## Secrets management Never commit secrets. Use: 1. Repository secrets (`${{ secrets.NAME }}`) 2. Environment secrets (scoped per environment like `production`) 3. Organization secrets (shared across repos) For rotation: change secret in GitHub → next workflow run picks it up. No config file to update. ## Required reviews before deploy Repo → Settings → Environments → Create "production": - Required reviewers: 1+ - Wait timer: 0 (or add delay) - Allowed branches: main Workflow: ```yaml jobs: deploy: environment: production # requires approval before running runs-on: ubuntu-latest ``` GitHub pauses the deploy job until a reviewer clicks approve. ## Rollback strategy Tag every release: ```yaml - name: Create release uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: generate_release_notes: true ``` To rollback: `git checkout `, push branch, deploy that. Or have a manual `rollback.yml` workflow that re-deploys a specific tag. ## Common pitfalls ## FAQ
Q GitHub Actions vs GitLab CI vs Jenkins?

Actions — easiest for GitHub-hosted repos. GitLab CI — better for GitLab. Jenkins — self-hosted, powerful, heavy. For most DomainIndia customers using GitHub, Actions wins.

Q How much does it cost?

Public repos: free. Private: 2,000 minutes/month free (Linux), then $0.008/min. Most small teams fit in free tier.

Q Self-hosted runner?

Useful if you need to deploy into a private network or use specific hardware. Install runner on a DomainIndia VPS, register to your repo.

Q Deployment of multiple services?

Use path filters: paths: ['services/frontend/**'] triggers only when frontend changes. Keep monorepo workflows efficient.

Q Deploy to k3s/Kubernetes?

kubectl apply -f k8s/ after building image. Use actions/setup-kubectl. Store kubeconfig as secret.

Automate deploys to your DomainIndia VPS with GitHub Actions. View VPS plans

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket