8.2 KiB
Rolling Update Deployment Guide
This guide explains how to perform zero-downtime deployments using the rolling update strategy.
Overview
The rolling update approach allows you to deploy new backend code without any downtime for users. Here's how it works:
- Build new backend image while old container is still running
- Start new container on port 8082 (old one stays on 8080)
- Health check new container to ensure it's ready
- Switch Nginx to point to new container (zero downtime)
- Stop old container after grace period
Architecture
┌─────────────┐
│ Nginx │ (Port 80/443)
│ (Host) │
└──────┬──────┘
│
├───> Backend (Port 8080) - Primary
└───> Backend-New (Port 8082) - Standby (during deployment)
Prerequisites
- Nginx running on host (not in Docker)
- Backend containers managed by Docker Compose
- Health check endpoint available at
/actuator/health/readiness - Sufficient memory for two backend containers during deployment (~24GB)
Quick Start
1. Make Script Executable
cd /opt/app/backend/lottery-be
chmod +x scripts/rolling-update.sh
2. Run Deployment
# Load database password (if not already set)
source scripts/load-db-password.sh
# Run rolling update
sudo ./scripts/rolling-update.sh
That's it! The script handles everything automatically.
What the Script Does
-
Checks prerequisites:
- Verifies Docker and Nginx are available
- Ensures primary backend is running
- Loads database password
-
Builds new image:
- Builds backend-new service
- Uses Docker Compose build cache for speed
-
Starts new container:
- Starts
lottery-backend-newon port 8082 - Waits for container initialization
- Starts
-
Health checks:
- Checks
/actuator/health/readinessendpoint - Retries up to 30 times (60 seconds total)
- Fails deployment if health check doesn't pass
- Checks
-
Updates Nginx:
- Backs up current Nginx config
- Updates upstream to point to port 8082
- Sets old backend (8080) as backup
- Tests Nginx configuration
-
Reloads Nginx:
- Uses
systemctl reload nginx(zero downtime) - Traffic immediately switches to new backend
- Uses
-
Stops old container:
- Waits 10 seconds grace period
- Stops old backend container
- Old container can be removed or kept for rollback
Manual Steps (If Needed)
If you prefer to do it manually or need to troubleshoot:
Step 1: Build New Image
cd /opt/app/backend/lottery-be
source scripts/load-db-password.sh
docker-compose -f docker-compose.prod.yml --profile rolling-update build backend-new
Step 2: Start New Container
docker-compose -f docker-compose.prod.yml --profile rolling-update up -d backend-new
Step 3: Health Check
# Wait for container to be ready
sleep 10
# Check health
curl http://127.0.0.1:8082/actuator/health/readiness
# Check logs
docker logs lottery-backend-new
Step 4: Update Nginx
# Backup config
sudo cp /etc/nginx/conf.d/lottery.conf /etc/nginx/conf.d/lottery.conf.backup
# Edit config
sudo nano /etc/nginx/conf.d/lottery.conf
Change upstream from:
upstream lottery_backend {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
}
To:
upstream lottery_backend {
server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8080 backup;
}
Step 5: Reload Nginx
# Test config
sudo nginx -t
# Reload (zero downtime)
sudo systemctl reload nginx
Step 6: Stop Old Container
# Wait for active connections to finish
sleep 10
# Stop old container
docker-compose -f docker-compose.prod.yml stop backend
Rollback Procedure
If something goes wrong, you can quickly rollback:
Automatic Rollback
The script automatically rolls back if:
- Health check fails
- Nginx config test fails
- Nginx reload fails
Manual Rollback
# 1. Restore Nginx config
sudo cp /etc/nginx/conf.d/lottery.conf.backup /etc/nginx/conf.d/lottery.conf
sudo systemctl reload nginx
# 2. Start old backend (if stopped)
cd /opt/app/backend/lottery-be
docker-compose -f docker-compose.prod.yml start backend
# 3. Stop new backend
docker-compose -f docker-compose.prod.yml --profile rolling-update stop backend-new
docker-compose -f docker-compose.prod.yml --profile rolling-update rm -f backend-new
Configuration
Health Check Settings
Edit scripts/rolling-update.sh to adjust:
HEALTH_CHECK_RETRIES=30 # Number of retries
HEALTH_CHECK_INTERVAL=2 # Seconds between retries
GRACE_PERIOD=10 # Seconds to wait before stopping old container
Nginx Upstream Settings
Edit /etc/nginx/conf.d/lottery.conf:
upstream lottery_backend {
server 127.0.0.1:8082 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8080 backup; # Old backend as backup
keepalive 32;
}
Monitoring
During Deployment
# Watch container status
watch -n 1 'docker ps | grep lottery-backend'
# Monitor new backend logs
docker logs -f lottery-backend-new
# Check Nginx access logs
sudo tail -f /var/log/nginx/access.log
# Monitor memory usage
free -h
docker stats --no-stream
After Deployment
# Verify new backend is serving traffic
curl http://localhost/api/health
# Check container status
docker ps | grep lottery-backend
# Verify Nginx upstream
curl http://localhost/actuator/health
Troubleshooting
Health Check Fails
# Check new container logs
docker logs lottery-backend-new
# Check if container is running
docker ps | grep lottery-backend-new
# Test health endpoint directly
curl -v http://127.0.0.1:8082/actuator/health/readiness
# Check database connection
docker exec lottery-backend-new wget -q -O- http://localhost:8080/actuator/health
Nginx Reload Fails
# Test Nginx config
sudo nginx -t
# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log
# Verify upstream syntax
sudo nginx -T | grep -A 5 upstream
Memory Issues
If you run out of memory during deployment:
# Check memory usage
free -h
docker stats --no-stream
# Option 1: Reduce heap size temporarily
# Edit docker-compose.prod.yml, change JAVA_OPTS to use 8GB heap
# Option 2: Stop other services temporarily
docker stop lottery-phpmyadmin # If not needed
Old Container Won't Stop
# Force stop
docker stop lottery-backend
# If still running, kill it
docker kill lottery-backend
# Remove container
docker rm lottery-backend
Best Practices
-
Test in staging first - Always test the deployment process in a staging environment
-
Monitor during deployment - Watch logs and metrics during the first few deployments
-
Keep backups - The script automatically backs up Nginx config, but keep your own backups too
-
Database migrations - Ensure migrations are backward compatible or run them separately
-
Gradual rollout - For major changes, consider deploying during low-traffic periods
-
Health checks - Ensure your health check endpoint properly validates all dependencies
-
Graceful shutdown - Spring Boot graceful shutdown (30s) allows active requests to finish
Performance Considerations
- Build time: First build takes longer, subsequent builds use cache
- Memory: Two containers use ~24GB during deployment (brief period)
- Network: No network interruption, Nginx handles the switch seamlessly
- Database: No impact, both containers share the same database
Security Notes
- New container uses same secrets and configuration as old one
- No exposure of new port to internet (only localhost)
- Nginx handles all external traffic
- Health checks are internal only
Next Steps
After successful deployment:
- ✅ Monitor new backend for errors
- ✅ Verify all endpoints are working
- ✅ Check application logs
- ✅ Remove old container image (optional):
docker image prune
Support
If you encounter issues:
- Check logs:
docker logs lottery-backend-new - Check Nginx:
sudo nginx -t && sudo tail -f /var/log/nginx/error.log - Rollback if needed (see Rollback Procedure above)
- Review this guide's Troubleshooting section