Build docker image with kaniko in GitLab pipeline

GitLab offers a CICD pipeline template for building docker images – Docker.gitlab-ci.yml. As shown below, it uses docker-in-docker approach. To be more specific:

  • docker:latest (image) is the docker client
  • docker:dind (service) is the docker daemon, dind stands for docker-in-docker.
  # Use the official docker image.
  image: docker:latest
  stage: build
    - docker:dind
  # Default branch leaves tag empty (= latest tag)
  # All other branches are tagged with the escaped branch name (commit ref slug)
    - |
      if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
        echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
        echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
    - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
    - docker push "$CI_REGISTRY_IMAGE${tag}"
  # Run this job in a branch where a Dockerfile exists
        - Dockerfile

To support docker-in-docker, the Gitlab runner needs Docker executor to run in privileged mode.

The --privileged flag gives all capabilities to the container. When the operator executes docker run --privileged, Docker will enable access to all devices on the host as well as set some configuration in AppArmor or SELinux to allow the container nearly all the same access to the host as processes running outside containers on the host.

  executor = "docker"
    privileged = true

For security concerns, many organisations have disabled docker executor privileged mode in their GitLab runners. Therefore docker-in-docker approach won’t work in those cases, and “no such host” error will be thrown.

Fortunately, kaniko is the alternative that we can use to build docker images in GitLab pipeline. Most importantly, kaniko does not need privileged mode, as it doesn’t depend on a Docker daemon and executes each command within a Dockerfile completely in userspace.

The kaniko executor image is responsible for building an image from a Dockerfile and pushing it to a registry. Within the executor image, we extract the filesystem of the base image (the FROM image in the Dockerfile). We then execute the commands in the Dockerfile, snapshotting the filesystem in userspace after each one. After each command, we append a layer of changed files to the base image (if there are any) and update image metadata.

Here is the .gitlab-ci.yml that I created to build a docker image with kaniko and push it to the docker hub registry.


  stage: build
    entrypoint: [""]
    - AUTH=$(echo -n ${DOCKER_HUB_USER}:${DOCKER_HUB_PASSWORD} | base64)
    - cp -f "${CI_PROJECT_DIR}/config.json" /kaniko/.docker/config.json
    - sed -i "s/PLACE_HOLDER/$AUTH/" /kaniko/.docker/config.json
    - |
      /kaniko/executor --context "${CI_PROJECT_DIR}" --dockerfile "${CI_PROJECT_DIR}/Dockerfile" --destination "${DOCKER_HUB_USER}/${DOCKER_IMAGE_NAME}:${$CI_COMMIT_REF_SLUG}"

Here is the config.json file which will be used by kaniko to login into docker hub registry. The DOCKER_HUB_USER and DOCKER_HUB_PASSWORD are saved as Masked variables in CI/CD settings, and will be base64 encoded then used to replace the PLACE_HOLDER in the run time.

  "auths": {
    "": {
      "auth": "PLACE_HOLDER"

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s