Skip to content

How to Set Up CI/CD in GitHub Actions

Key idea:

GitHub Actions — built-in CI/CD in GitHub (free 2000 min/month for private repos, unlimited for public). Workflow = YAML file in .github/workflows/. Triggers: push, pull_request, schedule (cron), manual. Runners: ubuntu-latest, macos-latest, windows-latest. Deploy via SSH + rsync, docker push, Vercel/Netlify integrations. Secrets stored in repo settings.

Below: step-by-step, working examples, common pitfalls, FAQ.

Step-by-Step Setup

  1. Create .github/workflows/ci.yml
  2. Define trigger: on: push: branches: [main]
  3. Define jobs with runs-on + steps (checkout, setup-node, npm install, test)
  4. For deploy: secrets (SSH_KEY, DEPLOY_HOST) in repo Settings → Secrets
  5. Deploy step: appleboy/ssh-action or rsync + SSH
  6. Commit + push — workflow runs automatically
  7. Results: Actions tab in GitHub, logs per step, artifacts

Working Examples

ScenarioConfig
Simple Node.js CIname: CI on: push: { branches: [main] } pull_request: 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 via SSHdeploy: needs: test runs-on: ubuntu-latest steps: - uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: deploy key: ${{ secrets.SSH_PRIVATE_KEY }} script: cd /var/www && git pull && npm install --production && pm2 reload all
Matrix build (multi Node version)strategy: matrix: node: [18, 20, 22] steps: - uses: actions/setup-node@v4 with: { node-version: ${{ matrix.node }} }
Docker build + push- uses: docker/build-push-action@v5 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
Scheduled workflow (cron)on: schedule: - cron: '0 2 * * *' # daily at 2 AM UTC

Common Pitfalls

  • Secrets printed in logs — use ::add-mask:: or sensitive steps with if: env check
  • Free tier — 2000 min for private repos. Build 10 min × 200 runs = limit
  • Cache matters: actions/cache@v4 with proper key — otherwise npm install each time 1 min
  • Ubuntu runners get upgraded — pin specific version (ubuntu-22.04, not ubuntu-latest for prod)
  • Deploy step runs on every push — add if: github.ref == 'refs/heads/main'

Learn more

Frequently Asked Questions

Is free tier enough?

For solo project — yes. Team 5+ — sometimes need Teams $4/user/mo or self-hosted runners.

Why self-hosted runners?

For private network access, unlimited minutes, GPU/ARM runners. Downside — maintenance + security (compromised runner = RCE in workflow).

Secrets vs env vars?

Secrets — encrypted, not visible in logs. Env vars — plain, visible. For tokens/passwords — always secrets.

GitHub Actions vs Jenkins/GitLab CI?

GitHub Actions: free for GH repos, huge actions marketplace, YAML. Jenkins: self-host, flexible but maintenance. GitLab CI: tight GitLab integration. For GitHub — Actions default.