CI/CD pipeline: from a push to production

How a pipeline runs in eight jobs, CI versus continuous delivery and deployment, a fast pipeline in 4 minutes instead of 15, six tools compared, four ways to release, two files of code, rules and mistakes.

Stack and technologies Updated

In short

A CI/CD pipeline is the path every code change takes automatically: lint, type checks and tests, then a build, a release to a staging server and then to production, and after that monitoring with a rollback if errors rise. CI — continuous integration — checks every change before it is merged. CD means either continuous delivery, where a release is ready and a person presses the button, or continuous deployment, where everything that passed the checks goes live by itself. A pipeline is described in a file next to the code: on GitHub it is GitHub Actions, on GitLab it is GitLab CI/CD. For a small site it removes manual uploads and forgotten steps; for a team it lets several people ship every day without breaking each other’s work. A good pipeline runs in under ten minutes, stops at the first failed check and can return the previous version in seconds.

How a pipeline runs

Eight jobs from a push to production. Switch the scenario: everything passes, a test fails, or a bad release reaches production.

  1. Push git push to main 0:02
  2. Lint style and bugs 0:38
  3. Types tsc --noEmit 0:51
  4. Tests 214 tests 2:04
  5. Build npm run build 1:12
  6. Staging deploy and check 0:46
  7. Production deploy 0:31
  8. Monitor errors, 10 minutes 10:00
✓ 214 tests passed
✓ staging answers 200
✓ released: v1.42 → production
✓ error rate no higher than before

The release went by itself: under five minutes from push to production, and nobody logged in to the server.

  1. Push git push to main 0:02
  2. Lint style and bugs 0:38
  3. Types tsc --noEmit 0:51
  4. Tests 214 tests 1:47
  5. Build npm run build —
  6. Staging deploy and check —
  7. Production deploy —
  8. Monitor errors, 10 minutes —
✗ checkout.test.ts › discount is not applied to the cart
  expected $27.00, received $30.00
— build and deploy skipped
→ the author got an email

The pipeline stopped at the tests. Production still runs the previous version, and buyers never saw the bug.

  1. Push git push to main 0:02
  2. Lint style and bugs 0:38
  3. Types tsc --noEmit 0:51
  4. Tests 214 tests 2:04
  5. Build npm run build 1:12
  6. Staging deploy and check 0:46
  7. Production deploy 0:31
  8. Monitor errors, 10 minutes 3:20
✓ released: v1.42 → production
✗ error rate 4.1% with a 1% limit
↩ v1.41 restored in 28 seconds
→ the team got a message with the log

Errors rose after the release, so the pipeline restored the previous version by itself. The fix can wait for a calm head.

The value of a pipeline is in the two bad scenarios. A failed test stops the release before anyone sees the bug, and a bad release that slipped through is rolled back by itself, without a night call. Lint, types and tests run at the same time, because they do not depend on each other.

CI, continuous delivery and continuous deployment

Three steps of automation. Each one builds on the previous one.

TermWhat is automatedWho releasesFits
Continuous integration (CI) lint, types, tests and a build on every change a person, by hand any project with more than one developer
Continuous delivery plus a release to staging and a ready release for production a person presses one button shops, services with a release schedule
Continuous deployment everything, up to production and a rollback nobody: what passed goes live teams with good tests and monitoring

A fast pipeline: 15 minutes or 4

The same jobs, launched differently. A slow pipeline gets skipped, so speed is part of reliability.

Whole run14:40

  • dependencies downloaded every time
  • checks wait for each other
  • all tests in one runner
  • build from scratch

Whole run4:00

  • dependencies from cache
  • lint, types and tests at once
  • tests split into three parts
  • build reuses what has not changed

An example Node.js project; the minutes are illustrative, the ratio is typical.

CI/CD tools compared

Free limits from the public pricing pages, October 2026. The tool usually follows where the code lives.

ToolWhere it runsFreeStrength
GitHub Actions GitHub’s cloud or your own server public repositories and own runners; private — 2,000 minutes a month, then from $0.006 a minute thousands of ready actions, everything next to the code
GitLab CI/CD GitLab’s cloud or your own GitLab 400 minutes a month; Premium — 10,000 for $29 per user code, tasks and pipelines in one place, can run on your server
Bitbucket Pipelines Atlassian’s cloud 50 minutes a month; +1,000 minutes for $10 teams already working in Jira
CircleCI CircleCI’s cloud or your own server 30,000 credits a month — about 3,000 minutes, 5 users fast builds, parallel jobs and test splitting
Jenkins only your own server free and open source; you pay for the server and its upkeep full control, large old projects
Vercel, Netlify, Cloudflare Pages their own platforms free plans for small sites a front-end goes live on every push, with a preview for each branch

Four ways to release

The last step of the pipeline decides whether visitors notice a release. Switch the strategy: who sees which version and how fast you can go back.

Downtime
seconds to minutes
Rollback
deploy the old version again
Fits
internal tools, releases at night
Downtime
none
Rollback
roll back the same way, one by one
Fits
apps on several servers
Downtime
none
Rollback
instant — switch back
Fits
sites and shops; on one server, two folders and a link
Downtime
none
Rollback
a bug reaches only a small share of visitors
Fits
large audiences, risky changes

A site on one server does not need a cluster for a blue-green release. The new version is uploaded into its own folder, and a symbolic link is switched to it in one step: visitors see either the old version or the new one, never half of each. The previous folder stays, so a rollback is the same switch back — the script is below.

What to automate first

  1. 1. Release in one step

    A merge into the main branch puts the site live. No uploads by hand, no “I forgot to copy that file”.

  2. 2. Checks on every pull request

    Lint, types and a build: they catch most silly mistakes before review.

  3. 3. Tests for money and sign-ups

    Not 100% coverage, but the cart, payment, sign-up and the contact form.

  4. 4. A preview before release

    A staging server or a preview for each branch: the client sees the change before visitors do.

  5. 5. A health check and a rollback

    The site answers, errors did not rise — otherwise the previous version comes back by itself.

  6. 6. Dependency updates and scans

    Weekly pull requests with updates that go through the same checks.

A pipeline in code: 2 files

A GitHub Actions workflow for a site on its own server, and the script on the server that switches releases and rolls back.

Checks, build and release

Checks run on every pull request; a release runs only from main and only if every check passed. Keys live in the repository secrets, not in the code.

.github/workflows/deploy.yml
name: CI/CD

on:
  pull_request:
  push:
    branches: [main]

concurrency:                       # a newer push cancels the older run
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm               # dependencies from cache: minutes → seconds
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test
      - run: npm run build
      - uses: actions/upload-artifact@v7
        with:
          name: site
          path: dist/

  deploy:
    needs: checks                  # no release if any check failed
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production        # secrets and an optional manual approval
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: site
          path: dist/
      - name: Upload and switch
        env:
          SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
          KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
          HOST: ${{ vars.DEPLOY_HOST }}
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
          echo "$KNOWN_HOSTS" > ~/.ssh/known_hosts
          RELEASE=$(date -u +%Y%m%d%H%M%S)
          rsync -az dist/ "deploy@$HOST:/srv/site/releases/$RELEASE/"
          ssh "deploy@$HOST" "/srv/site/switch.sh $RELEASE"

Switch and roll back

The link switches in one step, then the script checks the site. If the check fails, the previous release comes back, and the pipeline turns red.

switch.sh
#!/bin/sh
# Switches the site to a new release and returns the old one if the check fails
set -eu
SITE=/srv/site
NEW="$SITE/releases/$1"
OLD=$(readlink -f "$SITE/current" || true)

switch_to() {
  ln -sfn "$1" "$SITE/current.next"
  mv -T "$SITE/current.next" "$SITE/current"   # atomic: old or new
}

switch_to "$NEW"

if ! curl -fsS --max-time 10 https://example.com/health > /dev/null; then
  [ -n "$OLD" ] && switch_to "$OLD"
  echo "Health check failed, back to $(basename "$OLD")" >&2
  exit 1
fi

# keep the last five releases for a quick rollback
ls -1dt "$SITE"/releases/*/ | tail -n +6 | xargs -r rm -rf

7 rules of a good pipeline

  1. 01

    Under ten minutes

    Cache, parallel jobs and split tests. A slow pipeline gets bypassed.

  2. 02

    One build for every environment

    Staging and production get the same files; only the settings differ.

  3. 03

    Secrets outside the code

    Keys in the repository secrets, with the fewest rights needed for the job.

  4. 04

    Stop at the first red check

    A release never goes out “just this once” with a failing test.

  5. 05

    Migrations that the old version survives

    First add the column, release, then remove the old one — so a rollback does not break the database.

  6. 06

    A rollback in one step

    Keep several previous releases and test the rollback before you need it.

  7. 07

    A message only when it matters

    A red pipeline and a rollback reach a person at once; green runs stay in the log.

Common CI/CD mistakes

  1. Flaky tests that everyone restarts

    A red run stops meaning anything, and a real bug passes with it.

  2. A separate build for production

    Staging checked one set of files, production got another.

  3. Keys in the repository

    They stay in the history even after the file is deleted.

  4. Hand fixes on the server

    The next release overwrites them, and nobody remembers why the bug came back.

  5. A migration without a way back

    The code can be rolled back, the deleted column cannot.

  6. Notifications about every green run

    After a week nobody reads them, including the red ones.

Questions about CI/CD

Does a small site need CI/CD?

Yes, in its simplest form: a release from the main branch in one step and a rollback. It takes a few hours to set up and removes manual uploads for good.

What is the difference between CI and CD?

CI checks every change and builds it. CD delivers the result to servers: with a button press in continuous delivery or by itself in continuous deployment.

Which tool should I choose?

The one built into your code hosting: GitHub Actions for GitHub, GitLab CI/CD for GitLab. A separate tool is worth it only for special needs.

Is Docker required?

No. A site can be released as a folder of files with a link switch. Docker helps when there are several services or servers.

How do you release without downtime?

Prepare the new version next to the old one and switch traffic in one step: a link on one server, a load balancer on several.

What does CI/CD cost?

For most small projects the free limits are enough: 2,000 minutes a month on GitHub, 400 on GitLab. The real cost is the setup and the tests.

Online form

Releases
without fear

I set up pipelines for sites and services: checks on every change, a release in one step and a rollback in seconds. Tell me about the project — I answer within one working day.

Or write to [email protected]