CI/CD pipelines, deployment strategies, feature flags, Docker, and how the best teams ship 50+ times per day without breaking production.
How you deploy determines your risk, downtime, and ability to rollback. Each strategy trades off complexity, cost, and risk differently.
Two identical environments: Blue (current production) and Green (new version). Switch traffic instantly. Instant rollback by switching back.
Gradually shift traffic to the new version. Start with 1%, watch metrics, increase to 10% → 25% → 50% → 100%. Roll back immediately if error rates spike.
Replace instances one by one. At any point, some servers run the old version and some run the new. Kubernetes does this by default. No extra infrastructure needed.
Decouple deployment from release. Ship code to production but control who sees it. Feature flags enable trunk-based development, A/B testing, and instant kill switches.
// Simple feature flag implementation const flags = { ai_category_suggestion: isEnabled('ai_category_suggestion'), new_dashboard: isEnabledForUser('new_dashboard', user.id), dark_mode_beta: user.isBetaUser && isEnabled('dark_mode_beta'), }; // In component function ExpenseForm() { const { ai_category_suggestion } = useFeatureFlags(); return ( <div> <NoteField /> {ai_category_suggestion && <AISuggestionChip />} </div> ); } // Use LaunchDarkly, Flagsmith, or PostHog for production // They support: percentage rollout, user targeting, A/B tests
| Flag Type | Purpose | Example |
|---|---|---|
| Release toggle | Hide unfinished features in production | New checkout flow — 0% until ready |
| Experiment toggle | A/B test different implementations | 50% see "Add" button, 50% see "Record" |
| Ops toggle | Kill switch for problematic features | Disable AI API calls if Groq is down |
| Permission toggle | Enable for specific users/roles | Premium features for paid users only |
Containers solve the "works on my machine" problem by packaging code with its exact dependencies. Docker has become the standard unit of deployment.
# Multi-stage Dockerfile — production-optimized # Stage 1: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # Stage 2: Production image (much smaller) FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] # Result: 50MB image vs 900MB single-stage
| Version | Type | When to bump | Example change |
|---|---|---|---|
| 2.0.0 | MAJOR | Breaking API changes | Rename endpoint, change response format, drop support |
| 2.1.0 | MINOR | New backward-compatible features | Add new optional field, new endpoint, new flag |
| 2.1.3 | PATCH | Bug fixes only | Fix crash, fix wrong calculation, security patch |
| 2.1.3-beta.1 | Pre-release | Testing a future release | Alpha, beta, rc1 before official release |
semantic-release parse your commit messages and automatically bump versions: feat: → MINOR, fix: → PATCH, feat!: or BREAKING CHANGE: → MAJOR. Then auto-publish to npm and create GitHub releases. Zero manual version management.