Skip to content

setup gitlab runner with cacheable docker executor

When using gitlab ci, it always take a long time to build docker image because cache is not used.
Here is how to setup executer using local cache to speedup build time.

When do you need this?

If you have a standalone server that only used for gitlab runner, Shell Executor orUse Docker socket binding should be enough for you. You can skip this article.

If you can bear few minutes build time, just use kaniko or Use Docker-in-Docker.

Keep watching if you don't want to use socket binding or shell executor and want to reduce docker build time from few minutes to few seconds.

Setup GitLag Runner

  1. get registation token from gitlab admin panel, /admin/runners

  2. set var

GITLAB_HOST=192.168.0.11
GITLAB_PORT=443
GITLAB_URL="https://${GITLAB_HOST}:${GITLAB_PORT}/"
REGISTRATION_TOKEN="xxxxxxxxxxxxx"

HARBOR_HOST=192.168.0.12
HARBOR_PORT=443

DEPLOY_FOLDER=/srv/gitlab-runner
  1. perpare file ane folder
mkdir -p ${DEPLOY_FOLDER}
mkdir -p ${DEPLOY_FOLDER}/config
mkdir -p ${DEPLOY_FOLDER}/config/certs
cd ${DEPLOY_FOLDER}

# download gitlab server certificate
openssl s_client -showcerts -connect ${GITLAB_HOST}:${GITLAB_PORT} < /dev/null 2>/dev/null | sudo openssl x509 -outform PEM > ${DEPLOY_FOLDER}/config/certs/${GITLAB_HOST}.crt

# download harbor server certificate
openssl s_client -showcerts -connect ${HARBOR_HOST}:${HARBOR_PORT} < /dev/null 2>/dev/null | sudo openssl x509 -outform PEM > ${DEPLOY_FOLDER}/${HARBOR_HOST}.crt
  1. register gitlab runner and generate config file
sudo docker run --rm -v ${DEPLOY_FOLDER}/config:/etc/gitlab-runner/ docker.io/gitlab/gitlab-runner:v15.8.2 register \
 --non-interactive \
 --tag-list="dind-runner" \
 --name="dind-runner" \
 --executor "docker" \
 --docker-image "docker:23" \
 --docker-tlsverify="false" \
 --run-untagged="true" \
 --custom_build_dir-enabled \
 --builds-dir="/builds" \
 --docker-volumes="/builds:/builds" \
 --env='GIT_CLONE_PATH=$CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_NAME' \
 --cache-dir="/cache" \
 --docker-volumes="/cache:/cache" \
 --docker-volumes="/var/run/docker.sock:/var/run/docker.sock" \
 --docker-volumes="/etc/docker/certs.d:/etc/docker/certs.d" \
 --url="${GITLAB_URL}" \
 --registration-token="${REGISTRATION_TOKEN}"
  1. update concurrent to 10
sudo sed -i 's/concurrent.*/concurrent = 10/' ${DEPLOY_FOLDER}/config/config.toml
  1. create docker-compose file
echo "
services:
  dind:
    container_name: dind
    image: docker:23-dind
    restart: always
    privileged: true
    environment:
      # force docker deamon to disable TLS
      DOCKER_TLS_CERTDIR: ''
    command:
      - --storage-driver=overlay2
    networks:
      - gitlab-runner
    volumes:
      - ${DEPLOY_FOLDER}/${HARBOR_HOST}.crt:/etc/docker/certs.d/${HARBOR_HOST}/ca.crt

  runner:
    container_name: runner
    restart: always
    image: docker.io/gitlab/gitlab-runner:v15.8.2
    depends_on:
      - dind
    environment:
      - DOCKER_HOST=tcp://dind:2375
    volumes:
      - ${DEPLOY_FOLDER}/config:/etc/gitlab-runner
    networks:
      - gitlab-runner

networks:
  gitlab-runner: {}
" > ${DEPLOY_FOLDER}/docker-compose.yml
  1. start container cd ${DEPLOY_FOLDER} && docker compose up -d --wait
  2. view logs docker compose logs -n 100 -f

you can download single script file here

create gitlab-ci file

create .gitlab-ci.yml file in your git project and commit, it will trigger pipline

build-docker-image:
  stage: deploy
  tags:
    - docker-dind-runner
  variables:
    CI_REGISTRY: 192.168.0.12:443
    CI_REGISTRY_USER: admin
    CI_REGISTRY_PASSWORD: xxxxxxxx
  script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
    - docker build . -t "${CI_REGISTRY}/apps/${CI_PROJECT_PATH_SLUG}:latest" --build-arg BUILDKIT_INLINE_CACHE=1
    - docker push "${CI_REGISTRY}/apps/${CI_PROJECT_PATH_SLUG}:latest"

I am using a new dotnet webapi as demo, the first build takes 01:18 but second build only take 18s with code update. As long as you don't update .csproj file, you can use docker build layer local cache.

you can watch job container created inside dind container when trigger job

docker exec -t dind watch -n 1 docker ps

also setup crontab to clearup every month prevent disk full

(crontab -l && echo "0 0 1 * *  docker exec dind docker system prune --all --force --filter 'until=168h'") | crontab -
(crontab -l && echo "0 0 1 * *  docker exec dind docker system prune --all --force --volumes") | crontab -
crontab -l