Skip to content

Instantly share code, notes, and snippets.

@roziscoding
Created March 15, 2022 23:53
Show Gist options
  • Save roziscoding/a2eee8bfd8b7f8e691795abc6822cec5 to your computer and use it in GitHub Desktop.
Save roziscoding/a2eee8bfd8b7f8e691795abc6822cec5 to your computer and use it in GitHub Desktop.
zx script for zero downtime docker compose deployment
#!/usr/bin/env zx
const SERVICE_NAME = 'community-api-public';
const reloadNginx = () =>
quiet($`docker compose exec nginx /usr/sbin/nginx -s reload > /dev/null`);
const trim = (promise) =>
promise.then((result) => result.stdout).then((output) => output.trim());
const getHealthStatus = (id) =>
trim(quiet($`docker inspect -f {{.State.Health.Status}} ${id}`));
const waitForHealthy = async (id) =>
new Promise(async (resolve, reject) => {
const MAX_ATTEMPTS = process.env.MAX_ATTEMPTS
? parseInt(process.env.MAX_ATTEMPTS)
: 10;
console.log(
`Waiting for container ${id} to be healthy. Max attempts: ${MAX_ATTEMPTS}`,
);
let success = false;
let attempts = 0;
while (!success) {
if (attempts >= MAX_ATTEMPTS) {
return reject(
new Error(
`Container ${id} is not healthy after ${MAX_ATTEMPTS} attempts`,
),
);
}
const status = await getHealthStatus(id);
if (status === 'unhealthy') {
return reject(new Error(`Container ${id} is unhealthy`));
}
if (status === 'healthy') {
success = true;
return resolve();
}
attempts++;
await sleep(10000);
}
});
console.log(`Deploying service ${SERVICE_NAME}`);
const OLD_CONTAINER_ID = await trim(
$`docker ps -f name=${SERVICE_NAME} -q | tail -n1`,
);
console.log(`Old container id: ${OLD_CONTAINER_ID}`);
console.log('Starting new container');
await $`docker-compose up -d --no-deps --scale ${SERVICE_NAME}=2 --no-recreate ${SERVICE_NAME}`;
const NEW_CONTAINER_ID = await trim(
$`docker ps -f name=${SERVICE_NAME} -q | head -n1`,
);
console.log(`New container id: ${NEW_CONTAINER_ID}`);
await waitForHealthy(NEW_CONTAINER_ID).catch(async (err) => {
console.error(err.message);
await $`docker rm --force ${NEW_CONTAINER_ID}`;
process.exit(1);
});
console.log('Container is healthy. Reloading nginx');
await reloadNginx();
console.log('Stopping old container');
await $`docker stop ${OLD_CONTAINER_ID}`;
await $`docker rm ${OLD_CONTAINER_ID}`;
console.log('Reloading nginx');
reloadNginx();
console.log('Scaling compose service down');
await $`docker-compose up -d --no-deps --scale ${SERVICE_NAME}=1 --no-recreate ${SERVICE_NAME}`;
console.log('Deployment finished');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment