Simple docker-compose continuous deployment 🚀
Tags: devops, docker, productivity, bash
Published: 2021-05-03 17:40:40

This post is about a small and simple process to add Continuous Deployment to some projects where 0-downtime deployment is not needed. With the following methods, the containers will need few seconds to restart on deployment, therefore it is not adapted to a high availability project.

You will need a repository (Github, Gitlab, Bitbucket, ...), a pipeline tool (Bitbucket pipeline, circleCI, github actions, Gitlab CI, ...), an image registry (Docker hub), and a server.

In this post, I use a **bitbucket repository**, a **bitbucket pipeline** and **docker hub**. You can of course adapt this to your needs and tools!

As well, this is a simple version. For sensitive projects, apply best practices, read more about Docker, security, CI/CD, etc.

Why it is worth the time to set up automated deployment for a project:

* Fast build
* Easy rollback
* A docker image on production, staging, or local remains the same
* Use simple-technologies (docker-compose & bash script)

## Set up the pipeline to build and push your image on a registry 

First, set up the variables you need from the settings of your repository:

* $DOCKER_HUB_USER_TOKEN_NAME
* $DOCKER_HUB_TOKEN
* $IMAGE, name of your image on docker-hub, example: user/project
* DEPLOY_SERVER: IP or Domain name of your server

$BITBUCKET_COMMIT is a variable provided by bitbucket, giving you the commit ID and I am using it as a tag for the docker image.

```yaml
# bitbucket-pipelines.yml
image: node:12

pipelines:
  branches:
    production:
      - step:
          name: Build & Push to registry
          script:
            - docker login --username $DOCKER_HUB_USER_TOKEN_NAME --password $DOCKER_HUB_TOKEN
            - docker build -t $IMAGE:$BITBUCKET_COMMIT -t $IMAGE:$production .
            - docker push $IMAGE:$BITBUCKET_COMMIT
          caches:
            - docker
          services:
            - docker 

      - step:
          name: Deploy to production
          deployment: production
          # comment this step to deploy automatically on production
          # It is better to run tests before deploying
          trigger: manual 
          script:
            - echo "  [+] Start deploy script on the server"
            - ssh root@$DEPLOY_SERVER "/docker/deploy.sh $BITBUCKET_COMMIT"

options:
  docker: true
definitions:
  services:
    docker:
      memory: 2048
```

### What is this file doing?

In the step "Build & Push to registry", first it logs in to the registry using a token you generated on docker hub (go to settings > security). Then it builds the image using the Dockerfile at the root of your repository and adds 2 tags to it (commit ID and `production`). Finally, it pushes the image to the registry.

The following part will listen to the production branch for a new commit. You can replace it with `main` or `master` depending on your branch's names.

```yaml
  branches:
    production:
      - step:
```

```yaml
      - step:
          name: Deploy to production 
					deployment: production
          trigger: manual
```

The manual trigger is advised to prevent downtime of a few second a moment you might want a high availability of service (during the day).

```yaml
options:
  docker: true
definitions:
  services:
    docker:
      memory: 2048
```

The last part is useful to activate docker and to change the default memory setting, preventing the pipeline to run out of memory.

## Script to pull and run the new image

This script is on the server in a folder, the same that I specified in my pipeline: `/docker/deploy.sh`.

Set the rights to a write mode: `chmod +x deploy.sh` 

```bash
#! /bin/bash

if [ -z "$1" ];  then
        echo "  [!] No argument supplied, this script expects a docker tag to run."
        exit 1
fi

tag=$1
image="user/project"

echo "[>] Starting deployment"

echo "  [+] Remove containers, volume and networks older than 1 week..."
docker system prune --force --filter "until=168h"

cd /docker

echo "  [+]  Bitbucket commit ID: $tag"

echo "  [+]  Pull image $image:$tag"
pull=$(docker pull $image:$tag)

# Check if docker pull returns empty string
if [[ -z "$pull" ]]; then
        echo "  [!] Fail to pull image with tag $tag"
        exit 1
fi

echo "  [+] Start (or Restart) containers: docker-compose up -d"
TAG=$tag docker-compose up -d

echo "[>] Deployment done."

```

> Tips: If you want to debug this script, you can add the option `set -x` at the beginning of the script to have more detailed information during the script process.

If you need to roll back to any docker image, you can either rerun the deployment of a previous commit, or SSH to the server and run `TAG=abcdefg docker-compose up -d` (with abcdefg the commit ID you want to roll back to)


## Best practices to note

1. Add SSH port, which should be different from the default 22. Or whitelist [bitbucket](https://support.atlassian.com/bitbucket-cloud/docs/what-are-the-bitbucket-cloud-ip-addresses-i-should-use-to-configure-my-corporate-firewall/) and refuse unknown connections.

2. Create a deploy user, and use it to connect with SSH and do the deploy actions (so you will need to add this user to the docker group)

## To go further

With a monitoring tool like grafana, you can add an ["after script" on bitbucket pipeline](https://bitbucket.org/blog/after-scripts-now-available-for-bitbucket-pipelines) and set [custom annotations](https://grafana.com/docs/grafana/latest/http_api/annotations/) to indicates the deployments on your dashboards.

Connect [bitbucket pipelines to slack](https://support.atlassian.com/bitbucket-cloud/docs/integrate-slack-with-pipelines/) to get notifications on some events such as failed pipeline.