Deploying a monolith without downtime sounds like a contradiction, but it’s entirely achievable by treating your monolith not as a single, indivisible unit, but as a collection of independently deployable services that just happen to share a process.
Let’s see this in action. Imagine we have a simple monolith with two core functionalities: user management and product catalog. We’ll use a rolling deployment strategy with a load balancer.
Here’s a simplified docker-compose.yml for our monolith:
version: '3.8'
services:
monolith:
image: my-monolith-app:v1.0
ports:
- "8080:8080"
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first
And a basic Nginx configuration for the load balancer:
http {
upstream monolith_backend {
server 172.18.0.2:8080; # IP of container 1
server 172.18.0.3:8080; # IP of container 2
server 172.18.0.4:8080; # IP of container 3
}
server {
listen 80;
location / {
proxy_pass http://monolith_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
Now, we want to deploy version v1.1 of our monolith. Instead of stopping everything and starting the new version, we’ll update one instance at a time.
First, we build and push the new image:
docker build -t my-monolith-app:v1.1 .
docker push my-monolith-app:v1.1
Then, we update our docker-compose.yml to point to the new image:
version: '3.8'
services:
monolith:
image: my-monolith-app:v1.1 # Updated image tag
ports:
- "8080:8080"
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first
We initiate the deployment:
docker-compose up -d --no-deps monolith
Docker Swarm (or your orchestrator) will now execute the update_config. It will:
- Start a new container with
my-monolith-app:v1.1. - Wait for 10 seconds (
delay). - Register the new container with the load balancer.
- Stop one of the old containers (
order: start-firstmeans it stops after starting the new one).
This process repeats for each replica. Because the load balancer is configured to distribute traffic across available healthy instances, and new instances are registered before old ones are removed, no user request is dropped. A user hitting the monolith during the deployment might hit v1.0 on one request and v1.1 on the next, but they’ll never see an error.
The core problem this solves is the "all or nothing" deployment of traditional applications. By breaking down the deployment process into granular, sequential steps managed by an orchestrator and a load balancer, we eliminate the single point of failure that a monolithic deployment usually represents. The "indivisible unit" becomes a set of independently replaceable instances.
Internally, the orchestrator (like Swarm or Kubernetes) manages the lifecycle of containers. The update_config tells it how to replace old containers with new ones. parallelism: 1 means one at a time. delay: 10s gives the new container time to start up and potentially perform health checks before it starts receiving traffic. order: start-first ensures there’s always at least one instance running, and ideally, two during the transition of each instance. The load balancer is the crucial piece that directs traffic only to healthy, running instances.
The levers you control are the replicas count (how many instances of your monolith are running), parallelism (how many can be updated concurrently), delay (how long to wait between updates), and order (whether to start new ones before stopping old ones, or vice versa). The health check mechanism on your load balancer or within the orchestrator is also critical – it ensures that a newly started instance is actually ready to serve traffic before it’s added to the pool.
A common misconception is that this requires a complex microservices architecture. It doesn’t. This pattern works by treating the deployment process of a monolith as if it were a distributed system, even if the code is bundled together. The key is having an orchestrator that understands how to manage rolling updates and a load balancer that can dynamically adjust its backend pool.
The next hurdle you’ll likely encounter is managing stateful data during these rolling updates, especially if your monolith’s database schema changes.