Skip to content

Instantly share code, notes, and snippets.

@aamsur-mkt
Last active May 15, 2020 04:31
Show Gist options
  • Save aamsur-mkt/3e3646758d5ad3606805b461190ed9df to your computer and use it in GitHub Desktop.
Save aamsur-mkt/3e3646758d5ad3606805b461190ed9df to your computer and use it in GitHub Desktop.
API Deploy Without Downtime

Thanks @oelmekki for your insights. It has been very useful and encouraging when there is so few info on rolling updates with docker-compose.

I ended up writing the following script docker_update.sh <service_name>, which seems to work very decently. It relies on healthcheck command, which is not mandatory (change -f "health=healthy" accordingly) but cleaner IMHO than waiting for container to simply being up, when it takes a little time to boot (which will be the case if you run eg. npm install && npm start as a command).

#!/bin/bash

cd "$(dirname "$0")/.."

SERVICE_NAME=${1?"Usage: docker_update <SERVICE_NAME>"}

echo "[INIT] Updating docker service $SERVICE_NAME"

OLD_CONTAINER_ID=$(docker ps --format "table {{.ID}}  {{.Names}}  {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F  "  " '{print $1}')
OLD_CONTAINER_NAME=$(docker ps --format "table {{.ID}}  {{.Names}}  {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F  "  " '{print $2}')

echo "[INIT] Scaling $SERVICE_NAME up"
docker-compose up -d --no-deps --scale $SERVICE_NAME=2 --no-recreate $SERVICE_NAME

NEW_CONTAINER_ID=$(docker ps --filter="since=$OLD_CONTAINER_NAME" --format "table {{.ID}}  {{.Names}}  {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F  "  " '{print $1}')
NEW_CONTAINER_NAME=$(docker ps --filter="since=$OLD_CONTAINER_NAME" --format "table {{.ID}}  {{.Names}}  {{.CreatedAt}}" | grep $SERVICE_NAME | tail -n 1 | awk -F  "  " '{print $2}')

until [[ $(docker ps -a -f "id=$NEW_CONTAINER_ID" -f "health=healthy" -q) ]]; do
  echo -ne "\r[WAIT] New instance $NEW_CONTAINER_NAME is not healthy yet ...";
  sleep 1
done
echo ""
echo "[DONE] $NEW_CONTAINER_NAME is ready!"

echo "[DONE] Restarting nginx..."
docker-compose restart nginx

echo -n "[INIT] Killing $OLD_CONTAINER_NAME: "
docker stop $OLD_CONTAINER_ID
until [[ $(docker ps -a -f "id=$OLD_CONTAINER_ID" -f "status=exited" -q) ]]; do
  echo -ne "\r[WAIT] $OLD_CONTAINER_NAME is getting killed ..."
  sleep 1
done
echo ""
echo "[DONE] $OLD_CONTAINER_NAME was stopped."

echo -n "[DONE] Removing $OLD_CONTAINER_NAME: "
docker rm -f $OLD_CONTAINER_ID
echo "[DONE] Scaling down"
docker-compose up -d --no-deps --scale $SERVICE_NAME=1 --no-recreate $SERVICE_NAME

And here's my docker-compose.yml:

app:
    build: .
    command: /app/server.sh
    healthcheck:
      test: curl -sS http://127.0.0.1:4000 || exit 1
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 30s
    volumes:
      - ..:/app
    working_dir: /app
  nginx:
    depends_on:
      - app
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - "../nginx:/etc/nginx/conf.d"
      - "/var/log/nginx:/var/log/nginx"

And my nginx.conf:

upstream project_app {
  server app:4000;
}

server {
  listen 80;
  server_name example.com;

  location / {
    proxy_pass http://project_app;
  }
}

Hope it can be useful to some. I wish it would be integrated into docker-compose by default.

thanks https://github.com/augnustin ref : docker/compose#1786 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment