The CI/CD Pipeline: Automating Confidence
Manual deployments are a liability. A technical guide to building a robust CI/CD pipeline with GitHub Actions, Vercel Preview Environments, and Playwright.
In amateur teams, deployment is an “Event.”
The lead developer shouts: “Everyone stop merging! I am deploying!”
They SSH into a server. They run git pull. They pray.
If it breaks, they panic.
In professional teams, deployment is a Non-Event. It happens 20 times a day. It is boring. It is invisible. If it breaks, the system automatically rolls back before the user sees it.
This is achieved through Continuous Integration / Continuous Deployment (CI/CD). The Pipeline is a robot that guards your production environment. Its job is simple: Ruthlessly reject bad code.
Why Maison Code Discusses This
At Maison Code Paris, we act as the architectural conscience for our clients. We often inherit “modern” stacks that were built without a foundational understanding of scale. We see simple APIs that take 4 seconds to respond because of N+1 query problems, and “Microservices” that cost $5,000/month in idle cloud fees.
We discuss this topic because it represents a critical pivot point in engineering maturity. Implementing this correctly differentiates a fragile MVP from a resilient, enterprise-grade platform that can handle Black Friday traffic without breaking a sweat.
1. The Philosophy: Main Branch is Holy
The goal of CI/CD is to ensure that the main branch is always deployable.
You never push to main directly.
You adhere to Trunk Based Development (or Short-Lived Feature Branches).
- Dev creates branch
feat/new-checkout. - Dev pushes code.
- CI Pipeline runs. Tests pass.
- Dev opens Pull Request (PR).
- Preview Environment is deployed.
- Peer Review occurs.
- Merge to
main. - CD Pipeline runs. Deploys to Production.
2. Layer 1: Static Analysis (The Grammar Police)
The cheapest bugs to fix are the ones caught before the code runs.
We run this on every git push.
- Linting (ESLint): Catches syntax errors and bad patterns (
no-unused-vars). - Formatting (Prettier): Enforces style. No arguments about tabs vs spaces during code review.
- Type Checking (TypeScript): The most critical check. “You passed a String to a function expecting a Number.”
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run type-check # tsc --noEmit
- run: npm run lint
Cost: 30 seconds. Value: Saves hours of debugging “undefined is not a function”.
3. Layer 2: Unit Tests (The logic Check)
(See Unit Testing).
Does the calculateTax() function return the correct value for a German user?
We use Vitest. It must be fast.
If Unit Tests take >5 minutes, developers will stop running them locally.
4. Layer 3: The Preview Environment (Vercel)
This is the game changer.
When a PR is opened, Vercel automatically deploys that code to a unique URL: https://my-app-git-feat-checkout.vercel.app.
This allows:
- Visual QA: The Designer can click through the checkout to see if the pixels are perfect.
- Product Review: The PM can verify the feature meets requirements.
- Automated E2E Tests: The Pipeline can run a browser against this real URL.
5. Layer 4: End-to-End (E2E) Tests (The User Simulator)
Unit tests are not enough. “The database connection works. The button renders. But clicking the button doesn’t save to the database.” Only Playwright (or Cypress) catches this.
The CI pipeline spins up a headless browser and acts like a user.
- Go to
/product/sneaker. - Click “Add to Cart”.
- Expect “Cart (1)” visible.
e2e:
needs: preview
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Playwright
run: npx playwright test
env:
BASE_URL: ${{ github.event.deployment_status.target_url }}
Blocker: If E2E tests fail, the “Merge” button in GitHub is disabled.
6. Continuous Deployment: The Release
Once code is merged to main, the CD pipeline starts.
For Vercel/Netlify, this is automatic.
For AWS (Docker/ECS), we use GitHub Actions to build the container, push to ECR, and update the Task Definition.
Zero Downtime (Blue/Green)
We never restart a live server.
- Spin up Green (New Version).
- Wait for Health Check (
200 OK). - Switch Load Balancer to Green.
- Kill Blue (Old Version). This ensures that if the new version crashes on boot, no user sees it.
7. The “Friday Deploy” Rule
There is a meme: “Don’t deploy on Friday.” At Maison Code, we deploy on Fridays. If you are afraid to deploy on Friday, it means you do not trust your pipeline. It means you rely on Manual QA or hope. A robust pipeline gives you the confidence to ship anytime.
8. Security Scanning (Shift Left)
Security is often done “at the end” via a Pentest. We move it to the Pull Request.
- Snyk / Dependabot: Scans
package.jsonfor vulnerable dependencies. - Trivy: Scans Docker containers for OS vulnerabilities.
- SonarQube: Scans code for hotspots (e.g., Hardcoded Passwords).
If you commit an AWS Key, the pipeline explodes.
It prevents the secret from ever reaching the
mainbranch history.
9. Cost Management (Infracost)
Developers love to spin up large instances. “I need an X1.Large database for testing.” We use Infracost. It runs in the PR and comments:
“This PR increases monthly bill by $150 (Upgrade to X1.Large).” This makes cost transparent. The CTO can approve or reject the “Financial Change” just like a “Code Change”. It brings FinOps into DevOps.
10. Managing Secrets (Env Vars)
Never commit .env files.
We use Vercel Env Management or AWS Secrets Manager.
The CI pipeline injects these at build time.
For Open Source checks (like npm audit), we ensure secrets are not logged to the console.
9. GitOps (ArgoCD): The Holy Grail
For our Kubernetes clusters, we use GitOps.
The state of the cluster is defined in Git.
If you want to scale from 3 pods to 5 pods, you don’t run kubectl scale.
You edit deployment.yaml in Git.
ArgoCD sees the change and syncs the cluster.
Drift Detection: If a cowboy engineer changes the cluster manually (SSH), ArgoCD detects the drift and overwrites it back to the Git state.
This enforces “Infrastructure as Code” religiously.
10. Database Seeding for Previews
A Preview Environment is useless if it has an empty database. You log in, and there are no products. We implement Automated Seeding.
- Deploy DB (Neon / Supabase Branch).
- Run
npm run seed. - Injects: 10 Products, 2 Users (Admin/Customer), 5 Orders. Now the PM can test the “Order History” page immediately. This is the difference between a “Technical Deploy” and a “Usable Product”.
11. Advanced Rollback: Canary Deployments
For high-risk apps, Blue/Green is too binary. We use Canary Deployments.
- Deploy v2 to 5% of traffic.
- Pipeline watches Error Rate.
- If Error Rate < 0.1%, increase to 20%.
- If Error Rate spikes, Automatic Rollback to 0%. This limits the “Blast Radius” of a bad bug. Only 5% of users saw the error. This requires sophisticated Load Balancers (AWS ALB / Cloudflare), but it is the ultimate safety net.
12. Conclusion
Manual operations are the enemy of scale. Every time a human touches a server, the risk of error is 10%. Every time a script touches a server, the risk is 0% (after the first successful run). Automate everything. Make “Doing the Right Thing” the “Easy Thing”.
Manual operations are the enemy of scale. Every time a human touches a server, the risk of error is 10%. Every time a script touches a server, the risk is 0% (after the first successful run). Automate everything. Make “Doing the Right Thing” the “Easy Thing”.
Deployment Anxiety?
Do you cross your fingers every time you release?