How you replace the old version of an app with the new one determines whether users see downtime, how quickly you can roll back, and how much infrastructure you need. Here are the main deployment strategies, what they cost, and how to implement them on VPS servers.
At a glance
| Strategy | Downtime | Rollback speed | Extra resources | Complexity |
|---|---|---|---|---|
| Recreate | Yes (seconds to minutes) | Redeploy old version | None | Lowest |
| Rolling | No (with health checks) | Medium | Little | Low–medium |
| Blue-green | No | Instant (switch back) | 2× during deploy | Medium |
| Canary | No | Fast (shift traffic back) | Some | Higher |
| Feature flags | No | Instant (toggle) | None | Code-level |
Recreate
Stop the old version, start the new one. A plain docker compose up -d with one container does this. It’s fine for internal tools, low-traffic sites, or maintenance windows. Show a maintenance page during longer migrations.
Rolling updates
Replace instances one at a time, behind a load balancer, waiting for each new instance to pass health checks:
- Kubernetes: the default for Deployments (
maxUnavailable: 0,maxSurge: 1). See Kubernetes deployments and services. - Ansible:
serial: 1across servers. See Ansible Docker deploy. - Docker Compose: several replicas behind Nginx or Traefik, updated in turn. See Docker Compose scale.
During the rollout, old and new versions run at the same time, so APIs and database schemas must be compatible with both.
Blue-green
Run two complete environments. Blue serves traffic, green gets the new version. After testing green, switch traffic (Nginx upstream, load balancer, or DNS) to green. Rollback means switching back.
upstream app { server 127.0.0.1:8081; } # blue
# deploy green on :8082, test it, then:
# upstream app { server 127.0.0.1:8082; } && nginx -s reload
Detailed guide: blue-green deployment and zero-downtime Node.js deploys.
Canary releases
Send a small share of traffic (say 5%) to the new version, watch error rates and latency, then increase gradually. With Nginx:
upstream app {
server 10.0.0.11:8080 weight=19; # stable
server 10.0.0.12:8080 weight=1; # canary (~5%)
}
Kubernetes ingress controllers, service meshes, and tools like Argo Rollouts automate weighted canaries. Canaries need good monitoring, or you won’t notice problems. See observability basics.
Feature flags
Deploy code with new features switched off, then enable them for internal users, a percentage, or everyone, without redeploying. They separate deploying from releasing, and make rollback a toggle. See feature flags.
Database migrations: the hard part
Code rolls back easily, and data doesn’t. Use expand and contract:
- Expand: add new columns or tables in a backwards-compatible way (old code still works).
- Deploy code that writes to both, or reads the new structure.
- Backfill data.
- Contract: remove old columns in a later release, once nothing uses them.
Always back up before migrations. See database backup automation.
Rollbacks
- Deploy immutable versions (image tags, Git tags), so “previous version” is unambiguous. See Git tags and releases.
- Keep the previous version ready (old containers, the blue environment, the previous ReplicaSet).
- Practise rollbacks, and know which migrations are reversible.
Choosing for a VPS setup
| Situation | Strategy |
|---|---|
| Single VPS, small app | Recreate in a quiet window, or blue-green with two containers |
| Two+ app servers behind Nginx | Rolling (one server at a time) |
| Critical changes, real traffic | Canary + monitoring |
| Big features | Feature flags |
| Kubernetes | Rolling (default), canary with ingress/Argo Rollouts |
Quick reference
| Strategy | Key idea |
|---|---|
| Recreate | Stop old, start new |
| Rolling | Replace instances gradually |
| Blue-green | Two environments, switch traffic |
| Canary | Small % first, then increase |
| Feature flags | Deploy dark, release by toggle |
Related: CI/CD pipeline explained and Nginx load balancing.