The coffee was cold, and the alert on my screen was bright red. It was 2 AM, and a deployment had just taken our payment API offline for four minutes. Four minutes doesn’t sound like much, but in that time, we lost transactions, trust, and a good night’s sleep. I knew then we had to fix our process for good. We needed a way to ship updates without our users ever noticing a hiccup. This is how I learned to set up Blue-Green deployments. It’s not just a technique; it’s a peace-of-mind guarantee for any team that cares about continuous availability.
Think of it like this: you have two identical stages, Blue and Green. Only one is live, serving all your traffic. The other sits quietly in the background. When you have a new version of your application ready, you deploy it to the idle stage. You test it thoroughly, warm it up, and only when it’s perfectly healthy do you flip a switch. All traffic instantly moves from the old stage to the new one. If something goes wrong? You flip the switch back just as fast. The user sees none of this. No spinning wheels, no error messages. Just a smooth, continuous experience.
So, why does this matter to you right now? Because whether you’re building the next big thing or maintaining a critical service, downtime is a silent killer of reputation and revenue. This guide is my hands-on walkthrough for building this safety net using Spring Boot, Docker, and Nginx. I’ll share the exact code and configurations we used to stop worrying about deployments.
Let’s start with the heart of it: the Spring Boot application. It needs to be stateless and ready to run in multiple instances. Here’s a basic Dockerfile to containerize it.
FROM eclipse-temurin:21-jre-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
The real magic for managing two environments lies in docker-compose.yml. This file defines our two separate but identical stacks.
version: '3.8'
services:
app-blue:
build: ./app
container_name: myapp-blue
environment:
- SPRING_PROFILES_ACTIVE=blue
ports:
- "8080:8080"
depends_on:
- db
app-green:
build: ./app
container_name: myapp-green
environment:
- SPRING_PROFILES_ACTIVE=green
ports:
- "8081:8081"
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myappdb
POSTGRES_PASSWORD: secret
Notice the different ports and Spring profiles. This lets both applications run simultaneously on the same host, completely isolated from each other but connected to the same database. Using a tool like Flyway for database migrations is crucial here. It ensures the schema updates are safe, repeatable, and applied identically whether Blue or Green starts first.
But how does traffic know where to go? That’s the job of our traffic director, Nginx. It acts as a reverse proxy, sitting in front of everything. Its configuration uses upstream blocks to define our two groups of servers.
http {
upstream live_backend {
server app-blue:8080;
}
upstream idle_backend {
server app-green:8081;
}
server {
listen 80;
location / {
proxy_pass http://live_backend;
proxy_set_header Host $host;
}
}
}
In this state, all traffic is sent to the live_backend, which points to our Blue environment. The Green environment, defined in idle_backend, receives no traffic at all. It’s just waiting. The switch is elegantly simple. To promote Green to live, we just swap the upstream names in the proxy_pass directive and reload Nginx. A reload is a graceful process; it doesn’t drop existing connections.
# A simplified version of a promotion script
sed -i 's/live_backend/idle_backend/g' /etc/nginx/conf.d/upstream.conf
nginx -s reload
# Now swap the upstream labels for next time
sed -i 's/idle_backend/live_backend/g' /etc/nginx/conf.d/upstream.conf
sed -i 's/app-green/app-blue/g' /etc/nginx/conf.d/upstream.conf
This atomic switch is what makes the pattern so powerful. It happens in milliseconds. But you wouldn’t just flip the switch blindly, would you? This is where health checks become your best friend. Spring Boot Actuator gives you a /health endpoint out of the box. Before promoting the idle environment, your automation scripts should poll this endpoint.
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class CustomReadinessCheck implements HealthIndicator {
@Override
public Health health() {
// Add your own logic here, like checking database connectivity
boolean isServiceReady = performCriticalCheck();
if (isServiceReady) {
return Health.up().withDetail("service", "ready").build();
}
return Health.down().withDetail("service", "initializing").build();
}
}
Your deployment script should wait for a status: UP from this endpoint on the new environment before telling Nginx to switch traffic. This ensures you’re only ever directing users to a healthy instance. What about the shared database, though? Isn’t that a risk? It’s the most common concern I hear. The key is that your new application version must be backward-compatible with the old one. The database schema changes must be additive, not destructive. You can’t remove a column that the old version’s code still expects to find. This requires discipline in your development process, but it’s a manageable constraint.
After the switch, you’re left with the old version running on the now-idle stage. This is your golden safety net. You don’t shut it down immediately. You let it run for a while. If a bug suddenly appears in your new version under real traffic, you can roll back instantly by running the promotion script in reverse, pointing Nginx back to the old, stable version. This rollback capability transforms a potential crisis into a minor blip.
Setting this up might seem like a lot of moving parts, but the confidence it brings is worth it. You start deploying not with a prayer, but with a plan. You can push updates on a Friday afternoon without that familiar knot in your stomach. Your users get a better, more reliable service.
I’m curious, have you ever had a deployment go wrong that this setup could have prevented? The road to robust systems is paved with shared experiences. If this walkthrough helped you see a path forward, feel free to share it with a teammate who’s also battling deployment dread. Got a different twist on this pattern or a question about the finer details? Drop a comment below—let’s build more resilient systems together.
As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva