Deployment Best Practices: Ship with Confidence in 2025
Deployment Best Practices: Ship with Confidence in 2025
Deploying software should be boring. If your team dreads deployment day, something is wrong with your pipeline. Modern tooling makes it possible to deploy dozens of times per day with minimal risk — if you set it up right.
The Deployment Spectrum
Teams typically fall somewhere on this spectrum:
Manual FTP upload → Script-based deploy → CI/CD pipeline → GitOps → Progressive delivery
Risky Reliable
You don’t need to be at the far right to be effective. But you should be past “manual” as quickly as possible.
Principle 1: Automate Everything
If a human has to remember a step, that step will eventually be forgotten. Your deployment should be triggered by a single action — typically a merge to your main branch.
# This should be all it takes
git push origin main
# → CI runs tests
# → Build succeeds
# → Deploy happens automatically
# → Smoke tests pass
# → Done
The Minimum Viable Pipeline
# .gitlab-ci.yml
stages:
- quality
- build
- deploy
quality:
stage: quality
script:
- npm ci
- npm run lint
- npm run typecheck
- npm run test
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
deploy:
stage: deploy
script:
- netlify deploy --prod --dir=dist
only:
- main
Principle 2: Deploy Small Changes
Large deployments are risky because when something breaks, you have many possible causes. Small, frequent deployments are safer:
Risky: 2 weeks of work → 1 big deploy → Something breaks → Which of 47 commits caused it?
Better: Daily deploys → 3-5 commits → Something breaks → Easy to identify and revert
Feature Flags for Incomplete Work
Don’t let incomplete features block deployment. Use feature flags:
if (featureFlags.isEnabled('new-checkout-flow')) {
return <NewCheckout />;
}
return <CurrentCheckout />;
This lets you merge code to main continuously without exposing unfinished features to users.
Principle 3: Preview Deployments
Every pull request should generate a preview deployment. This gives:
- Developers: A live URL to test their changes
- Reviewers: Something to click through, not just code to read
- Stakeholders: Early visibility into features
Most hosting platforms support this natively:
Netlify: Automatic deploy previews on every PR
Vercel: Automatic preview deployments
Railway: Preview environments per PR
Principle 4: Zero-Downtime Deployments
Users should never see a “maintenance” page. For static sites, this is automatic — the new build replaces the old one atomically. For server applications:
Blue-Green Deployment
Before: [Load Balancer] → [Blue (v1)] ← traffic
[Load Balancer] [Green (idle)]
During: [Load Balancer] → [Blue (v1)] ← traffic
[Load Balancer] [Green (v2)] ← testing
After: [Load Balancer] [Blue (v1)] ← standby
[Load Balancer] → [Green (v2)] ← traffic
Traffic switches instantly. If v2 has issues, switch back to Blue.
Rolling Deployment
For containerized applications, update instances one at a time:
[Instance 1: v1] [Instance 2: v1] [Instance 3: v1]
[Instance 1: v2] [Instance 2: v1] [Instance 3: v1] ← health check
[Instance 1: v2] [Instance 2: v2] [Instance 3: v1] ← health check
[Instance 1: v2] [Instance 2: v2] [Instance 3: v2] ← complete
Principle 5: Rollback Strategy
Every deployment should have a tested rollback plan. The fastest rollback is redeployment of the previous version:
# Keep the last known-good build artifact
# If the new deploy fails, redeploy the previous artifact
netlify deploy --prod --dir=previous-dist
For database migrations, this is harder. Never deploy a migration that can’t be reversed:
Safe: ADD COLUMN (nullable) → DROP COLUMN to rollback
Safe: CREATE INDEX → DROP INDEX to rollback
Risky: DROP COLUMN → Data is gone
Risky: RENAME COLUMN → Old code breaks immediately
Rule: Make your database change backward-compatible first, deploy the code that uses it second.
Principle 6: Environment Parity
Your staging environment should mirror production as closely as possible:
Same OS, same runtime versions
Same environment variable structure
Same database engine (not SQLite in dev, Postgres in prod)
Same CDN and caching behavior
Same SSL/TLS configuration
Differences between environments are where bugs hide.
Principle 7: Post-Deploy Verification
Don’t assume a successful build means a successful deploy. Run automated checks after deployment:
# Smoke tests after deploy
curl -f https://yoursite.com/ || exit 1 # Homepage loads
curl -f https://yoursite.com/api/health || exit 1 # API responds
curl -f https://yoursite.com/blog || exit 1 # Key pages work
Health Check Endpoints
Every application should expose a health check:
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
version: process.env.BUILD_VERSION,
timestamp: new Date().toISOString()
});
});
Security in Deployment
- Never commit secrets — Use environment variables, not
.envfiles in the repo - Scan dependencies — Run
npm auditin your pipeline - Set security headers — CSP, HSTS, X-Frame-Options
- Use HTTPS everywhere — No exceptions, even for staging
# Essential security headers
Content-Security-Policy: default-src 'self'; ...
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Monitoring After Deploy
The deploy isn’t done when the code is live. Watch these metrics for 15-30 minutes after every deploy:
- Error rate — Any spike compared to the previous hour?
- Response time — P50 and P99 latency within normal range?
- Traffic patterns — Are users still reaching key pages?
If anything looks off, roll back first, investigate second.
Start Today
You don’t need a sophisticated platform to deploy well. Start with:
- Git-based deploys — Push to main triggers deployment
- Automated tests — Block deploys that fail tests
- Preview deployments — Every PR gets a live URL
- Health checks — Verify the deploy worked
Complexity can come later. Reliability starts now.
At TiaTech, we set up deployment pipelines as part of every project we deliver. If your team is still deploying manually or struggling with reliability, let’s talk.