CI/CD Setup Guide

February 18, 2026

How to set up CI/CD for your startup: GitHub Actions workflow structure, test automation, staging environments, and zero-downtime deployment strategies.

GitHub Actions Workflow Structure

GitHub Actions is the default CI/CD choice for most startups because it's integrated with the code repository, its free tier includes unlimited minutes for public repositories and 2,000 minutes per month for private repositories, and the YAML configuration lives in the same repository as the code. A workflow file lives at .github/workflows/deploy.yml and runs automatically on git push events. The standard pipeline has five stages run as sequential jobs: lint, test, build, and deploy, with each stage serving as a gate — if linting fails, the tests don't run; if tests fail, the build doesn't start.

The key GitHub Actions concept is the job-within-workflow structure. Each job runs in its own fresh virtual machine and can be configured with different dependencies, environment variables, and conditions. Use needs: [test] to express job dependencies. Use if: github.ref == 'refs/heads/main' to restrict deployment to the main branch. Cache your node_modules between runs using actions/cache with the package-lock.json as the cache key — this reduces a typical Node.js install step from 90 seconds to 10 seconds. A basic workflow for a Next.js app takes about two hours to configure initially and saves hundreds of hours of manual deployment over the following year.

Test Automation Integration

Tests that run locally but not in CI are tests that nobody trusts. The goal is to run every test in the same environment — same Node version, same environment variables, same database state — every time a pull request is opened. GitHub Actions supports this through the services block, which spins up a PostgreSQL or Redis container for the duration of the test run and tears it down afterward. Use a .env.test file committed to the repository (with non-sensitive test credentials) to ensure the test environment is reproducible.

Playwright and Cypress are the two leading end-to-end testing frameworks for web applications. Both run headless in GitHub Actions with a single configuration line: npx playwright test runs Playwright tests in a headless Chromium browser within the CI environment with no additional setup. Playwright has the edge for modern Next.js applications because it natively supports testing server components and API routes in the same test suite. Configure E2E tests to run only on pull requests targeting main, not on feature branch pushes, to avoid running expensive browser tests on every minor commit. The total CI run time for a typical seed-stage application — lint, unit tests, and a subset of E2E tests — should be under five minutes.

Staging Environments

A staging environment is a production-replica where you verify that your deployment works before it affects real users. The minimum staging configuration is a separate database, a separate set of environment variables, and a deploy process that mirrors production. The most common staging mistake is using a different configuration from production — different Node version, different environment variables, different database seed data — which allows bugs to pass staging and appear only in production.

Vercel's preview URL system is the best implementation of staging for frontend applications: every pull request automatically gets its own preview deployment at a URL like my-app-git-feature-branch-orgname.vercel.app, with no additional configuration required. The preview URL uses production environment variables (or a separate preview environment if configured) and is shareable with teammates and stakeholders for review before merge. For backend services, Railway and Render both support environment-specific deploy pipelines where pushes to a staging branch deploy to a staging service automatically. The branch strategy that works for most startups is: feature branches → pull request with Vercel preview, develop branch → staging environment, main branch → production.

Zero-Downtime Deployment

Zero-downtime deployment means users don't experience errors or interrupted requests during a deployment. The two most common strategies are blue-green deployment and rolling deployment. In blue-green deployment, a new version (green) is deployed alongside the current version (blue), traffic is switched from blue to green atomically, and blue is kept alive briefly for instant rollback. In rolling deployment, instances are replaced one at a time while the others continue serving traffic.

Vercel and Railway implement zero-downtime deployment automatically — there's nothing to configure. A new deployment is built in isolation, verified via a health check, and then traffic is switched atomically. For applications on your own servers, implement a health check endpoint at /api/health that returns 200 when the service is ready. Configure your load balancer (Nginx or AWS ALB) to wait for a 200 from the health endpoint before routing traffic to the new instance. Blue-green deployment adds the complication of database schema migrations — never deploy a migration that breaks the previous version's code, because both versions run briefly in parallel. Additive migrations (adding columns, adding tables) are safe; destructive migrations (removing columns, renaming columns) require a multi-step deploy process.

Frequently Asked Questions

How do I set secret environment variables in GitHub Actions? Go to your repository Settings → Secrets and Variables → Actions → New Repository Secret. Secrets are encrypted and only visible to workflow runs, never to repository viewers. Reference them in your workflow file as ${{ secrets.MY_SECRET_NAME }}. For organisation-wide secrets shared across multiple repositories, use Organisation Secrets under the organisation settings.

What is the difference between a CI runner and a self-hosted runner? GitHub-hosted runners are virtual machines managed by GitHub, included in the free tier, and reset between runs. Self-hosted runners are machines you manage yourself — typically a VPS or EC2 instance — that persist between runs and can be faster for large builds because dependencies are already installed. Self-hosted runners make sense when your build takes more than 10 minutes on GitHub-hosted runners or when you need access to resources on a private network, like an internal Kubernetes cluster.

How do I handle database migrations in a CI/CD pipeline? Run migrations automatically as part of the deploy process, before the new application version starts serving traffic. The command goes in the deploy job after the build and before the service restart. Use a migration tool with a lock mechanism — Flyway, Liquibase, or Prisma Migrate — to prevent simultaneous migrations from two deploy processes running in parallel. Always take a database snapshot before a migration that modifies existing data; automated backups don't substitute for a pre-migration snapshot with a known rollback point.

Should every commit deploy to production? Continuous deployment — where every commit to main automatically deploys — is the most efficient model when you have good test coverage and fast CI. If your test suite catches regressions reliably, every green commit should go to production. If your test coverage is incomplete, a manual approval gate before production deployment reduces risk at the cost of deployment velocity. Start with manual approval, build coverage, and move to continuous deployment when the tests catch 90%+ of regressions before production.

What should a health check endpoint return? At minimum, HTTP 200 with a JSON body confirming the service is running: {"status": "ok", "timestamp": "2026-02-18T12:00:00Z"}. For a more useful health check, include dependencies: {"status": "ok", "database": "connected", "redis": "connected"}. If the database is unreachable, return 503 Service Unavailable so the load balancer routes traffic away from the unhealthy instance. Keep the health check lightweight — no authentication, no complex queries — so it responds in under 100ms.

Related Turkish Products