Deploy Docker Container from Gitlab CI

Containers are all the rage nowadays and for a good reason. They help in unifying development and production environments. They also provide application encapsulation and isolation, among other things. But to get the most out of them, you should build and deploy them automatically. This post will show you how to do it using Gitlab CI and docker-compose.

CI configuration

Below, you can see an example .gitlab-ci.yml file for a simple static website that builds and deploys its Docker container:

image: docker:latest
services:
  - docker:dind

stages:
  - build
  - deploy

build:docker-image:
  stage: build
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - docker build --pull -t "$CI_REGISTRY_IMAGE" .
    - docker push "$CI_REGISTRY_IMAGE"
  only:
    - master

deploy:docker-image:
  stage: deploy
  variables:
    DOCKER_HOST: "ssh://deploy@$DOCKER_HOST_IP"
  before_script:
    # Configure SSH
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | ssh-add -
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    # Configure Docker
    - apk add docker-compose
    - docker login registry.gitlab.com -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
  script:
    - docker-compose pull
    - docker-compose up -d --force-recreate
  after_script:
    - docker logout registry.gitlab.com
  only:
    - master

As you can see, it has of several general options and two stages - build and deploy. Each of the stages has just one job.

There are just three common options. First, is the image used for running the pipeline. The official Docker one is the natural choice. The second option, services, enables Docker in Docker (dind). Without it, it is not possible to run Docker on the Gitlab.com runner. The stages options just list the CI stages, build and deploy in this case.

Build stage

Now, let’s get into the more interesting bits. To start with, the build stage. It is fairly straightforward. As you can see, it just pushes the built image to Gitlab’s image registry. All the variables prefixed with “CI_” are provided by Gitlab, so you do not need to make any extra changes to your repository. The image will be available at registry.gitlab.com/<username>/<repository>.

Deploy stage

The second job deploys the newly built image. It is a bit more complicated, but nothing magical. Docker can use SSH for connecting to a remote host, so that’s what we do. Then, it logs into Gitlab’s registry on the remote server, pulls the new image and starts it with docker-compose.

As usual, in order to use SSH, you need to generate a pair of keys and add the public key to your remote server. However, you need to save the private key as environment variable in your repository. You can do that in Settings > CI/CD > Variables. In the same location, save the DOCKER_HOST_IP variable, that stores the server’s IP address. The last line in the SSH configuration skips the interactive SSH host key check.

The actual deploy part uses docker-compose, so it requires docker-compose.yml. But you could just as well start the container using the docker command or another method of your choice. Again, the variables prefixed with “CI_” are provided by the runner.

In the given example, both of these jobs only run on the master branch, but that’s up to you.

Conclusion

That’s it. Now, you can enjoy deploying your application with a single git push. Magic.