Jens Willmer

Tutorials, projects, dissertations and more..

Zero Downtime Deployment with Docker Rollout

This guide demonstrates a Zero Downtime Deployment using Docker Rollout, featuring an intentional failure, rollback scenario, and a successful rollout. Additional I demonstrate the use of a Traefik reverse proxy to expose the service ports.

Prerequisites:

Ensure you have:

# Create directory for Docker cli plugins
mkdir -p ~/.docker/cli-plugins

# Download docker-rollout script to Docker cli plugins directory
curl https://raw.githubusercontent.com/wowu/docker-rollout/master/docker-rollout -o ~/.docker/cli-plugins/docker-rollout

# Make the script executable
chmod +x ~/.docker/cli-plugins/docker-rollout

Directory Setup

Create the following files in your working directory:

Dockerfile-1 (working version):

# Use the official NGINX base image
FROM nginx:latest

# Create a custom text file with "Hello World V1"
RUN echo "Hello World V1" > /usr/share/nginx/html/index.html

# Expose port 80 (the default NGINX port)
EXPOSE 80

Dockerfile-2 (intentional failure):

# Use the official NGINX base image
FROM nginx:latest

# Create a custom text file with "Hello World V2"
RUN echo "Hello World V2" > /usr/share/nginx/html/index.html

# Expose port 8080 (causing health check failure due to mismatch)
EXPOSE 8080

Note: Version 2 exposes port 8080, which will cause the health check to fail initially as it is configured to check port 80.

docker-compose.yml:

services:
  web:
    image: static-site:1
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 2
      start_period: 5s
    networks:
      - my_network

  test:
    image: alpine
    container_name: test
    command: sh -c "apk add --no-cache wget && tail -f /dev/null"
    networks:
      - my_network

networks:
  my_network:
    driver: bridge

Build Docker Images

  1. Build version 1:
docker build -t static-site:1 -f Dockerfile-1 .
  1. Build version 2 (the failing one):
docker build -t static-site:2 -f Dockerfile-2 .

Run the Initial Version

Start the services using Docker Compose:

docker-compose up -d

This will start version 1 (static-site:1) of the web service.

Test the Initial Deployment

Verify that version 1 is running by executing wget inside the test container:

docker exec -it test wget -qO- http://web

You should see:

Hello World V1

Update the Web Service Version for Rollout

Before running Docker Rollout, update the image of the web service to version 2 in your docker-compose.yml file:

web:
  image: static-site:2
  # rest of the configuration remains the same

Important: Updating the version in the docker-compose.yml file is necessary before executing the rollout command to initiate the update.

Perform Zero Downtime Deployment with Docker Rollout

Run Docker Rollout to perform the zero downtime deployment:

docker rollout web --file docker-compose.yml

Observations During Rollout:

  • No downtime for the web service: The web service remains fully operational during the update process.
  • One container becomes unhealthy: Since version 2 exposes a different port (8080), the new version’s container will fail the health check, causing the deployment to fail.
  • Automatic rollback: Docker Rollout will automatically roll back to version 1, ensuring the service remains healthy.

Update the Health Check for a Successful Rollout

To observe a successful rollout, update the health check in the docker-compose.yml file to match port 8080, which version 2 is using:

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:8080 || exit 1"]
  interval: 10s
  timeout: 5s
  retries: 2
  start_period: 5s
  1. Update the docker-compose.yml file with the health check for port 8080.
  2. Run the rollout again with:
docker rollout web --file docker-compose.yml

Observations During Successful Rollout:

  • The service will now update successfully from version 1 to version 2, as the health check matches the correct port.
  • You should now see the new version output by running:
docker exec -it test wget -qO- http://web

Output:

Hello World V2

Setting up Traefik Reverse Proxy for HTTP Access

You can use Traefik to expose your services externally without needing to define specific ports or container names in your Docker Compose file. This is especially useful when working with the Docker Rollout plugin, as it imposes limitations similar to Docker Swarm, where ports or container names cannot be explicitly defined. Traefik simplifies this by handling service discovery and routing dynamically, allowing you to maintain external accessibility while supporting zero downtime deployments.

By routing traffic based on service labels instead of container specifics, Traefik enables seamless updates and rollbacks without requiring manual changes to the container’s network settings.

docker-compose.yml with Traefik added:

version: '3'
services:
  web:
    image: static-site:1
    restart: always
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 2
      start_period: 5s
    networks:
      - my_network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.web.rule=PathPrefix(`/`)"
      - "traefik.http.services.web.loadbalancer.server.port=80"

  traefik:
    image: traefik:v2.4
    command:
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
    networks:
      - my_network

  test:
    image: alpine
    container_name: test
    command: sh -c "apk add --no-cache wget && tail -f /dev/null"
    networks:
      - my_network

networks:
  my_network:
    driver: bridge

Summary

The Docker Rollout plugin enables near zero downtime deployments by updating service instances one at a time, ensuring that previous versions remain active until the new ones pass health checks. While the plugin strives to eliminate downtime, there is still a small possibility of downtime during the transition, as noted in Issue #21, which is currently being worked on and may be resolved in the future.

Additionally, using the restart: always policy is discouraged in favor of restart: unless-stopped, as outlined in Issue #25. This prevents conflicts with Docker Rollout’s management of container lifecycles during updates.

Key features of Docker Rollout:

  • Sequential updates: New instances are spun up one at a time, with health checks ensuring each instance is functioning properly before proceeding.
  • Health check validation: Only instances that pass their health checks are considered live, reducing the risk of introducing faulty versions.
  • Automatic rollback: In the event of a failure, Docker Rollout triggers a rollback to the last known good version, maintaining service stability.
  • Zero downtime goal: Though there may be brief downtimes in specific scenarios, Docker Rollout aims to keep services uninterrupted.
  • Flexible configuration: It supports custom timeouts, wait times, and health check parameters for diverse deployment needs.