CI/CD Fundamentals
Cloud and DevOps questions in a frontend/full-stack interview are rarely deep. They test whether you understand how your code gets from your laptop to production and what can go wrong. This page covers exactly that.
1. What Is CI/CD?
Continuous Integration (CI)
Every push runs an automated pipeline: install, lint, type-check, test, build. The point is to catch breakage within minutes of the commit, not days later during a release.
Continuous Delivery
Every change that passes CI is automatically prepared for release. Deploying to production is a manual button press.
Continuous Deployment
Every change that passes CI goes to production automatically. No button.
The Interview Answer
"CI is automatically verifying every commit. Continuous Delivery means every passing commit is deployable. Continuous Deployment means it actually ships automatically. The distinction between the last two is whether a human presses a button."
Most candidates blur Delivery and Deployment. Getting it right is a cheap, easy win.
2. Why It Matters
| Without CI/CD | With CI/CD |
|---|---|
| "Works on my machine" | Same build steps every time |
| Manual deploy checklists | One command, or zero |
| Big risky releases | Small frequent changes |
| Breakage found in production | Breakage found in the PR |
| Deploys need one specific person | Anyone can ship |
| Rollback is a scramble | Rollback is a click |
The Real Argument
Small, frequent deploys are safer than big rare ones. When you ship 40 changes at once and something breaks, you have 40 suspects. When you ship one, you have one — and reverting costs nothing.
3. Pipeline Stages
A typical pipeline for a Next.js or Node app:
push / PR
│
├── 1. Checkout code
├── 2. Setup Node + restore dependency cache
├── 3. Install dependencies (npm ci)
├── 4. Lint (eslint)
├── 5. Type check (tsc --noEmit)
├── 6. Unit tests
├── 7. Build
├── 8. Integration / E2E tests
├── 9. Security scan (npm audit)
│
└── on merge to main
├── 10. Deploy to staging
├── 11. Smoke tests
└── 12. Deploy to productionOrdering Principle
Fastest and most likely to fail goes first. Lint takes 10 seconds; E2E takes 5 minutes. Failing lint after a full E2E run wastes everyone's time.
npm ci vs npm install
npm install | npm ci | |
|---|---|---|
| Uses the lockfile | As a hint | Exactly |
| Modifies the lockfile | Can | Never |
Existing node_modules | Reuses | Deletes first |
| Speed in CI | Slower | Faster |
Always npm ci in CI. It guarantees the exact dependency tree from the lockfile — otherwise a transitive dependency can silently update between your local run and the pipeline. This is a very common interview question.
4. GitHub Actions
The most common CI system, and the one to know.
# .github/workflows/ci.yml
name: 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
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run buildKey Concepts
| Term | Meaning |
|---|---|
| Workflow | A YAML file in .github/workflows/ |
| Event | What triggers it — push, pull_request, schedule, workflow_dispatch |
| Job | A group of steps on one runner. Jobs run in parallel by default |
| Step | One command or action |
| Action | A reusable step (actions/checkout@v4) |
| Runner | The VM executing the job |
| Secret | Encrypted value from repository settings, never in the YAML |
Jobs With Dependencies
jobs:
test:
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: test # only if test passed
if: github.ref == 'refs/heads/main' # only on main
runs-on: ubuntu-latest
environment: production # can require approval
steps:
- run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}needs creates the dependency; without it jobs run in parallel.
Matrix Builds
strategy:
matrix:
node-version: [20, 22]Runs the job once per value — the standard way to test across versions.
Services (a Real Database in CI)
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-retries 5
ports: ['5432:5432']Integration tests against a real Postgres, not a mock. Mocking the database tests your mock, not your SQL.
5. Secrets Management
The Rules
- Never commit secrets.
.envin.gitignore, always - Store them in the platform: GitHub Secrets, Vercel env vars, Render env groups
- Different secrets per environment — a staging leak must not compromise production
- Rotate on any suspicion, and on staff departure
- Never log them — configure redaction at the logger
If a Secret Is Committed
Removing it in the next commit is not enough — it stays in git history and in every clone and fork.
The correct response:
- Rotate the secret immediately. This is step one, not step three
- Then purge history (
git filter-repoor BFG) and force push - Assume it was scraped — public repos are scanned by bots within minutes
Saying "rotate first, clean history second" is the answer that shows you have actually dealt with this.
Build-Time vs Runtime
Frontend variables (NEXT_PUBLIC_*, VITE_*) are inlined into the bundle at build time. They are permanently public and require a rebuild to change — updating them in a dashboard does nothing to an already-built app.
Backend variables are read at runtime and a restart picks them up.
Never put a secret in a NEXT_PUBLIC_ variable. It ships in the JavaScript bundle, readable by anyone with DevTools. This is the single most common real-world deployment mistake.
6. Environments
| Environment | Purpose |
|---|---|
| Local | Your machine |
| Preview | One per pull request, auto-created and destroyed |
| Staging | Production-like, shared, for final verification |
| Production | Real users |
Preview Deployments
Vercel, Netlify and Render create a unique URL for every PR. Reviewers click a link instead of pulling the branch and running it locally.
This is the highest-value CI/CD feature for frontend teams and worth naming as a favourite.
Environment Parity
Every environment should run the same build artifact with different configuration. Rebuilding per environment means what you tested is not what you shipped.
The NEXT_PUBLIC_ build-time inlining above is exactly why perfect parity is hard for frontend apps — it's a good nuance to raise.
7. Deployment Strategies
| Strategy | How | Trade-off |
|---|---|---|
| Recreate | Stop old, start new | Downtime. Simple |
| Rolling | Replace instances gradually | No downtime; two versions coexist briefly |
| Blue-Green | Two full environments, switch traffic | Instant rollback; costs 2× infrastructure |
| Canary | 5% of traffic to the new version, then increase | Safest; needs good metrics |
| Feature flags | Ship code disabled, enable per user | Decouples deploy from release |
The Nuance Worth Adding
Deploying is not releasing. With feature flags you ship code to production continuously and turn features on separately — so a bad feature is a config toggle away from being off, not a rollback and redeploy.
Rolling Deploys Need Two Things
- Graceful shutdown — handle
SIGTERM, stop accepting new connections, finish in-flight requests - Backwards-compatible database migrations — during a rolling deploy, old and new code run against the same schema at the same time
Backwards-Compatible Migrations
❌ One deploy: ALTER TABLE users RENAME email TO email_address;
Old instances still query `email` → 500s during the rollout
✅ Three deploys:
1. Add email_address, write to both columns
2. Backfill, switch reads to the new column
3. Drop the old columnCalled expand and contract. It is the answer to "how do you deploy a schema change with zero downtime" and it separates people who have done it from people who haven't.
8. Rollback
Every deploy needs a plan for when it goes wrong.
| Method | Speed |
|---|---|
| Platform "promote previous deployment" (Vercel, Render) | Seconds |
| Redeploy the previous git tag | Minutes |
git revert and push | Minutes, plus a full CI run |
| Feature flag off | Instant |
What Does Not Roll Back
Database migrations. Code rolls back in seconds; a dropped column does not come back. This is why destructive migrations should be a separate, later deploy — after the new code has been running successfully.
git revert is preferable to git reset --force on a shared branch: it adds a new commit rather than rewriting history everyone else has pulled.
9. Monitoring After Deploy
A deploy is not done when the pipeline goes green.
| Watch | Tool |
|---|---|
| Error rate | Sentry, Datadog |
| Latency (p95, p99) | Platform metrics, Datadog |
| Uptime | Better Uptime, Pingdom |
| Core Web Vitals | Vercel Speed Insights, Lighthouse CI |
| Logs | Platform logs, Logtail, Datadog |
Health Endpoints
app.get("/health", (req, res) => res.json({ status: "ok" })); // liveness
app.get("/ready", async (req, res) => { // readiness
try {
await db.$queryRaw`SELECT 1`;
res.json({ status: "ready" });
} catch {
res.status(503).json({ status: "not ready" });
}
});Liveness = is the process alive (restart me if not). Readiness = can I serve traffic (stop routing to me if not). Confusing them causes restart loops — a database blip restarts a perfectly healthy process.
10. Common Interview Questions
"Walk me through your deployment process."
"Push to a feature branch. CI runs lint, type check, tests and a build on the PR, and the platform creates a preview deployment on a unique URL. After review and a green pipeline, merge to main. That triggers a production deploy — build, then a rolling release with health checks. I watch the error rate and latency for a few minutes. If something's wrong, I promote the previous deployment, which takes seconds."
"What's the difference between CI and CD?"
CI verifies every commit automatically. Continuous Delivery keeps every passing commit deployable. Continuous Deployment ships it automatically. Delivery vs Deployment = whether a human presses a button.
"How do you handle environment variables?"
Never in git. Stored per environment in the platform. Frontend NEXT_PUBLIC_/VITE_ variables are build-time inlined and permanently public — never secrets, and changing one needs a rebuild. Backend variables are runtime and picked up on restart.
"How do you do zero-downtime deploys?"
Rolling deploys with health checks, graceful shutdown handling SIGTERM, and backwards-compatible migrations using expand-and-contract so old and new code can run against the same schema simultaneously.
"A deploy broke production. What do you do?"
- Roll back first, diagnose second. Stop the bleeding
- Promote the previous deployment, or flip the feature flag off
- Confirm error rates recover
- Reproduce locally or in staging
- Fix, add a test that would have caught it, redeploy
- Blameless postmortem — what in the pipeline let this through?
The "roll back first" instinct is the thing being tested. Candidates who start debugging production while it's down give the wrong answer.
"What's the difference between npm install and npm ci?"
npm ci installs exactly what the lockfile specifies, deletes node_modules first, never modifies the lockfile, and is faster. npm install can resolve to newer versions and update the lockfile. Always ci in a pipeline — otherwise CI can pass on a different dependency tree than the one you tested.
"How do you keep the pipeline fast?"
- Cache dependencies (
cache: npminsetup-node) - Run independent jobs in parallel
- Fail fast — cheapest checks first
- Only run E2E on PRs to main, not every push
- Run tests affected by the change (Turborepo, Nx) in a monorepo
- Cache Docker layers
A slow pipeline gets bypassed, and a bypassed pipeline protects nothing.