Zero Downtime Migration: How to Move Your App Without Downtime
A maintenance window is a negotiation: you're asking your users to accept service interruption in exchange for a better future. Most of the time, that negotiation is unnecessary. With the right strategy, you can migrate a live production application — code, database, and all — to a new cloud platform without dropping a single request. This guide covers the complete playbook for zero-downtime application migration.
Why Traditional Migrations Cause Downtime
Traditional migrations cause downtime for one of three reasons:
- 1DNS propagation delay — you point DNS to the new server and wait for the old one to drain
- 2Database unavailability — the database is taken offline to ensure a consistent export
- 3Application deployment gap — the old app is stopped before the new one is confirmed healthy
Zero-downtime migration eliminates all three through parallel operation, traffic shifting, and health-check-driven cutover.
The Core Pattern: Blue-Green Migration
Blue-green migration means running old (blue) and new (green) environments simultaneously and shifting traffic between them. The key insight: never stop blue until green is fully verified.
Internet → Load Balancer / DNS
│
┌────────┴────────┐
▼ ▼
[BLUE] [GREEN]
Old Env New Env
(100%) (0% → 100%)Step 1: Deploy the New Environment Without Routing Traffic to It
Set up your new environment on PandaStack — containers, databases, environment variables — but don't update DNS yet.
npm install -g @pandastack/cli
panda login
panda init
# Deploy to PandaStack (gets a unique pandastack.io URL)
git push origin main
# Verify the new deployment is healthy — use the internal URL, not your domain
curl https://my-app.pandastack.io/api/health
curl https://my-app.pandastack.io/api/readyYour real domain still points to the old environment. No users are affected yet.
Step 2: Mirror Your Database to the New Platform
Use streaming replication or logical replication to keep the new database in sync with the old one in real time:
# Baseline restore
pg_dump --format=custom --no-acl --no-owner "$OLD_DB_URL" > migration-baseline.dump
pg_restore --no-acl --no-owner -d "$NEW_DB_URL" migration-baseline.dump
# Enable logical replication on the source
psql "$OLD_DB_URL" -c "CREATE PUBLICATION blue_green_pub FOR ALL TABLES;"
psql "$OLD_DB_URL" -c "SELECT pg_create_logical_replication_slot('blue_green_slot', 'pgoutput');"
# Subscribe on the target
psql "$NEW_DB_URL" -c "
CREATE SUBSCRIPTION blue_green_sub
CONNECTION 'host=old-host dbname=mydb user=replicator password=secret'
PUBLICATION blue_green_pub
WITH (copy_data = false, slot_name = 'blue_green_slot');
"
# Monitor lag until it reaches zero
psql "$OLD_DB_URL" -c "
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag
FROM pg_replication_slots WHERE slot_name = 'blue_green_slot';
"Step 3: Run Both Environments Simultaneously
With replication running, your new environment is continuously synced. Now test aggressively against the green URL:
# Run your full integration test suite against the new environment
BASE_URL=https://my-app.pandastack.io npm test
# Load test to verify performance
npx autocannon -d 30 -c 50 https://my-app.pandastack.io/api/products
# Compare response times between old and new
for i in {1..10}; do
OLD=$(curl -o /dev/null -s -w "%{time_total}" https://old-app.example.com/api/products)
NEW=$(curl -o /dev/null -s -w "%{time_total}" https://my-app.pandastack.io/api/products)
echo "Old: ${OLD}s | New: ${NEW}s"
doneStep 4: Gradual Traffic Shift Using DNS Weighted Routing
Many DNS providers support weighted routing. Use it to shift a percentage of traffic to the new environment:
# Example with AWS Route 53 CLI (conceptual — adapt to your DNS provider)
# Start with 10% to new environment
aws route53 change-resource-record-sets --hosted-zone-id YOUR_ZONE_ID --change-batch '{
"Changes": [{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "app.example.com",
"Type": "CNAME",
"SetIdentifier": "green",
"Weight": 10,
"TTL": 60,
"ResourceRecords": [{"Value": "my-app.pandastack.io"}]
}
}]
}'Monitor error rates. If green looks healthy at 10%, increase to 50%, then 100%.
Step 5: The Final Cutover
When you're ready to commit:
# Lower your DNS TTL 24 hours before cutover for faster propagation
# Then update DNS to point fully to PandaStack
# Pause new writes briefly (seconds, not minutes)
# Wait for replication lag = 0
# Update DNS
# Resume traffic
# Verify
curl -I https://app.example.com
curl https://app.example.com/api/healthStep 6: Cleanup
# Drop replication subscription and slot
psql "$NEW_DB_URL" -c "DROP SUBSCRIPTION blue_green_sub;"
psql "$OLD_DB_URL" -c "SELECT pg_drop_replication_slot('blue_green_slot');"
psql "$OLD_DB_URL" -c "DROP PUBLICATION blue_green_pub;"Keep the old environment running in read-only mode for 48 hours as a safety net, then decommission it. Zero-downtime migration takes more planning than a maintenance window, but your users will never know the difference — which is exactly the point. Visit [docs.pandastack.io](https://docs.pandastack.io) for PandaStack's deployment and database documentation.