Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services

Blog Posts

Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Amine Elbarry

Amine

5+ years software engineer

~/AI_Chat~/projects~/experience~/blogs~/hire-me~/services
Back to all blogs

How to Set Up CI/CD for a Web Application

Jun 10, 2026•8 min read

CI/CD turns deployment from a nerve-wracking manual ritual into a boring automated one. You push code, and a pipeline installs dependencies, lints, tests, builds, and deploys — the same way, every time. The payoff is speed and safety: bad code fails the build instead of production, and every release is identical. Here's how I set one up, with a complete GitHub Actions file you can drop in.

The pipeline: what runs, in what order

CI (Continuous Integration) and CD (Continuous Delivery/Deployment) are one flow. Continuous Integration is the test-and-build half; Continuous Delivery is the deploy half. Together they look like this:

git push / open PR
      │
      ▼
┌─────────────── CI ───────────────┐
│ 1. Checkout code                 │
│ 2. Install dependencies (npm ci) │
│ 3. Lint                          │
│ 4. Run unit tests                │
│ 5. Build                         │
│ 6. Security scan                 │
│ 7. Package the artifact          │
└──────────────┬───────────────────┘
               ▼
┌─────────────── CD ───────────────┐
│ 8. Deploy to staging             │
│ 9. Integration / e2e / smoke     │
│ 10. (optional) manual approval   │
│ 11. Deploy to production         │
│ 12. Monitor + rollback if needed │
└──────────────────────────────────┘

The key idea: the same artifact you built and tested in CI is the one you promote to production. You never rebuild for prod — that reintroduces the risk you just eliminated.

Choosing a platform

PlatformBest forNotes
GitHub ActionsRepos already on GitHubEasiest start, huge marketplace, generous free tier
GitLab CITeams on GitLabTightly integrated, .gitlab-ci.yml
JenkinsSelf-hosted, full controlPowerful but you maintain it
CircleCIFast builds, good cachingStrong Docker support

For most teams I default to GitHub Actions — the pipeline lives next to the code and there's nothing extra to host. The choice matters less than people think; the concepts (stages, artifacts, secrets, environments) transfer between all of them. Learn the ideas once and you can read any pipeline.

A working GitHub Actions pipeline

This is a real, runnable CI workflow for a Node app. It runs on every push and PR, and only deploys from main.

yaml
1name: CI/CD 2 3on: 4 push: 5 branches: [main] 6 pull_request: 7 branches: [main] 8 9jobs: 10 ci: 11 runs-on: ubuntu-latest 12 steps: 13 - name: Checkout 14 uses: actions/checkout@v4 15 16 - name: Setup Node 17 uses: actions/setup-node@v4 18 with: 19 node-version: 20 20 cache: npm 21 22 - name: Install dependencies 23 run: npm ci 24 25 - name: Lint 26 run: npm run lint 27 28 - name: Run tests 29 run: npm test -- --ci 30 31 - name: Build 32 run: npm run build 33 34 - name: Security audit 35 run: npm audit --audit-level=high 36 37 deploy: 38 needs: ci 39 if: github.ref == 'refs/heads/main' 40 runs-on: ubuntu-latest 41 environment: production 42 steps: 43 - name: Checkout 44 uses: actions/checkout@v4 45 46 - name: Deploy 47 run: ./scripts/deploy.sh 48 env: 49 DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

A few things worth pointing out:

  • npm ci, not npm install — it installs exactly what's in the lockfile, so CI is reproducible.
  • cache: npm — caches dependencies between runs to keep the pipeline fast.
  • needs: ci — deploy only runs if CI passed.
  • secrets.DEPLOY_TOKEN — credentials come from GitHub's secret manager, never the repo.
  • environment: production — lets you require a manual approval before this job runs.

Adding staging and e2e tests

The workflow above deploys straight to production once CI passes, which is fine for a small project. For anything with real users, insert a staging step: deploy the tested artifact to a staging environment, run end-to-end and smoke tests against it, and only then promote to production. In GitHub Actions you model this with separate jobs and environment protection rules, which can also gate the production job behind a required reviewer's approval.

yaml
1 deploy-staging: 2 needs: ci 3 runs-on: ubuntu-latest 4 environment: staging 5 steps: 6 - uses: actions/checkout@v4 7 - name: Deploy to staging 8 run: ./scripts/deploy.sh staging 9 - name: Run e2e smoke tests 10 run: npm run test:e2e -- --base-url=$STAGING_URL 11 12 deploy-production: 13 needs: deploy-staging # only runs if staging + its tests passed 14 if: github.ref == 'refs/heads/main' 15 runs-on: ubuntu-latest 16 environment: production # can require a manual approval here 17 steps: 18 - uses: actions/checkout@v4 19 - name: Promote to production 20 run: ./scripts/deploy.sh production

The chain ci → deploy-staging → deploy-production means a failure anywhere short-circuits the rest. Nothing reaches production that didn't pass every gate before it.

Deployment strategies

How you release matters as much as that you release. The three common strategies:

StrategyHow it worksTrade-off
RollingReplace instances gradually, a few at a timeSimple; brief mixed-version window
Blue/greenTwo identical environments; switch traffic all at onceInstant rollback; needs double the infra
CanaryRoute a small % of traffic to the new version firstSafest for risky changes; more complex routing

For a payment-critical system like an e-commerce checkout, I lean toward blue/green or canary — being able to roll back instantly is worth the extra infrastructure. For a low-risk internal tool, rolling is fine.

Best practices that actually matter

  • Keep pipelines fast. A pipeline over ~10 minutes stops being run frequently. Cache dependencies, parallelize independent jobs, and only run e2e tests where they add value.
  • Secrets live in the CI secret manager, never in YAML or the repo.
  • Build once, promote the artifact. Don't rebuild per environment.
  • Use Infrastructure as Code. Define infra (Terraform, etc.) in version control so environments are reproducible and reviewable.
  • Fail fast. Put the cheapest, most likely-to-fail checks first (lint, unit tests) so a broken build dies in seconds, not minutes.
  • Always have a rollback path. Monitoring plus a one-command (or one-click) rollback is what makes fast deploys safe.
  • Gate on quality, not just green. A pipeline that passes because you deleted the failing test isn't safety. Require coverage thresholds, keep flaky tests out (quarantine and fix them), and make the security audit a real gate.

What good looks like

A healthy pipeline has a few observable properties. It's fast — most runs finish in a handful of minutes, so people actually wait for it instead of merging around it. It's trustworthy — a green build genuinely means the code is deployable, because the tests are meaningful and not routinely skipped. It's reproducible — the same commit always produces the same artifact and the same result. And it's reversible — when something does slip through (it will), you can roll back in seconds, not scramble through a manual redeploy.

When I set one of these up on a payment-critical system, the goal wasn't zero incidents — that's not realistic. The goal was making the cost of any single deploy small: small changes, shipped often, each easy to reason about and trivial to undo. A pipeline that lets you deploy ten small safe changes a day beats one that makes you batch a month of risky changes into a single terrifying release.

Where this fits

CI/CD automates the deployment you first do by hand — so if you haven't done a manual deploy yet, start with How to Deploy a Full-Stack Application to understand what the pipeline is automating. For the bigger picture of production readiness, see the hub: Deploying, Scaling & Architecting Full-Stack Apps.

Related Posts

Deploying, Scaling & Architecting Full-Stack Apps

A practical map for taking a full-stack app from a working codebase to a production system that ships reliably and scales — deployment, CI/CD, performance, and architecture choices.

How to Deploy a Full-Stack Application

A step-by-step walkthrough for deploying a full-stack app to production — environment prep, choosing hosting, deploying the database, backend, and frontend, connecting them, and going live with a custom domain and HTTPS.

How to Optimize Web Application Performance

A priority-ordered playbook for making web apps fast — measure Core Web Vitals first, then reduce JavaScript, optimize images, fix caching, tune the backend, and cut re-renders. Based on real high-traffic optimization work.