Skip to content

Deploying Gitlab Runner on Your Workstation

Gitlab limited the number of CI minutes freely available, and although I’m working on an opensource project and could possibly apply to get more free minutes, I decided not to if possible. I’m happy to be able to use their service freely, and in their competition with a megacorp, I’d rather not participate in unnecessarily using their limited financial means.

Most deployment of Gitlab Runners are probably done on servers, but the workstation I’m developing on has a lot of spare resources, which I would be happy to use to run CI jobs. And it is absolutely possible to do it, even if you’re behind a firewall, because all network communications between the runner and the Gitlab instance you’re using are initiated by the runner.

Getting the Gitlab Runner up and running is rather easy, though I had to search for the right way to get Docker in Docker working fine while retaining the possibility to run the jobs on Gitlab’s shared runners. This prompted me to document the process.

I decided to run the Gitlab Runner inside a docker container. The runner is started on my workstation with:

docker run -d --name gitlab-runner --restart always \
    -v $PWD/deployment/gitlab-runner/config:/etc/gitlab-runner \
    -v /var/run/docker.sock:/var/run/docker.sock

This command has only the be run once. The container is not deleted when it is stopped, so its configuration of volumes mounted is kept intact between the host’s reboots. Running this command, you will get the runner’s config files under ./deployment/gitlab-runner/config. If you need to restart the runner, you can do it with docker restart gitlab-runner.

The runner will handle builds of only one project. To register it for this project, visit the project on the Gitlab instance, and in the left sidebar select Settings > CI/CD and expand the Runners section. In the left columns, under Project Runner, you find both items you will need to register your runner: the gitlab url (in my case and the registration token. Start the registration by running:

docker run --rm -it \
    -v $PWD/deployment/gitlab-runner/config:/etc/gitlab-runner \
    gitlab/gitlab-runner \

You get prompted for some information, here is the ouput of my run with come comments indicated by <-----:

Enter the GitLab instance URL (for example,                     <------ the gitlab instance you register with
Enter the registration token:
this_is_a_private_token                 <------ the token you copied from the gitlab ui
Enter a description for the runner:
[405ab5bc8d5c]: your_runner_name        <------ is displayed in the job output in the web interface
Enter tags for the runner (comma-separated):
Enter optional maintenance note for the runner:

WARNING: Support for registration tokens and runner parameters in the 'register' command has been deprecated in GitLab Runner 15.6 and will be replaced with support for authentication tokens. For more information, see
Registering runner... succeeded                     runner=GR1348941UvpM9L-P
Enter an executor: docker+machine, docker-ssh+machine, instance, custom, docker, docker-ssh, virtualbox, kubernetes, parallels, shell, ssh:
Enter the default Docker image (for example, ruby:2.7):
docker                                  <------- IMPORTANT : I think this is required if using service dind in your gitlab-ci.yml
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

I didn’t investigate the deprecation warning, but it seems this step will have to be updated later on.

Now comes the problem I encountered with running Docker in Docker: my gitlab-ci.yaml sets the DOCKER_HOST variable to make the jobs pass on the shared runners:

    DOCKER_HOST: tcp://docker:2375/

but this doesn’t work on my dedicated runner. Ideally, it would be possible to override environment variables from the runner’s config, but this is currently not possible.After trying multiple approaches, the best solution I found is to define an environment variable in my runners called RUNNER_DOCKER_HOST which is set to /var/run/docker.sock as my runner mounts the host’s docker.sock. The downside is that it requires changes to the scripts calling docker…. To implement this, 2 things are required:

  • Configure the runner:
    1. To mount the host’s docker.sock in $PWD/deployment/gitlab-runner/config/config.toml under the volumes key:
      volumes = ["/var/run/docker.sock:/var/run/docker.sock","/cache"]
    1. Set the environment variable RUNNER_DOCKER_HOST:
      environment = ["RUNNER_DOCKER_HOST=unix:///var/run/docker.sock"]
  • Update your script to update the DOCKER_HOST value if RUNNER_DOCKER_HOST is defined. In bash this can be done with DOCKER_HOST=${RUNNER_DOCKER_HOST:-$$DOCKER_HOST}. The easiest is probably to define an alias for the docker command to be DOCKER_HOST=${RUNNER_DOCKER_HOST:-$$DOCKER_HOST} docker. For example in my Makefile I define a variable DOCKER := DOCKER_HOST=$${RUNNER_DOCKER_HOST:-$$DOCKER_HOST} docker, and replace the calls to docker by $(DOCKER).

With that in place, your build will work both on Gitlab’s shared runners and your own runners.


I automated the setup of a gitlab runner with these Makefile targets:

    # Check the required token is passed by a environment variable
    echo $${GITLAB_RUNNER_TOKEN:?You need to provide GITLAB_RUNNER_TOKEN}
    # start the runner
    docker run -d --name gitlab-runner --restart always \
        -v $$PWD/private/gitlab-runner/config:/etc/gitlab-runner \
        -v /var/run/docker.sock:/var/run/docker.sock \
    # register the runner
    docker run --rm -it \
        -v $$PWD/private/gitlab-runner/config:/etc/gitlab-runner \
        gitlab/gitlab-runner \
        register \
            --non-interactive \
            --executor $${GITLAB_RUNNER_EXECUTOR:-docker} \
            --docker-image $${GITLAB_RINNER_IMAGE:-docker} \
            --name $${GITLAB_RUNNER_NAME:-$$(hostname)} \
            --url $${GITLAB_RUNNER_SERVER:-} \
            --registration-token $${GITLAB_RUNNER_TOKEN}
    # Set variable RUNNER_DOCKER_HOST so that the scripts it runs can detect it needs to update the DOCKER_HOST with this value
    # Thisis needed because the DOCKER_HOST value needs to be a different value on the shared gitlab runners than on dedicated runners
    # (dedicated runners use /var/run/docker.sock and shared runner use dind)
    docker run --rm -it \
        -v $$PWD/private/gitlab-runner/config:/etc/gitlab-runner \
        ubuntu \
        sed -i -e '/\[\[runners\]\]/a \ \ environment = \["RUNNER_DOCKER_HOST=unix:///var/run/docker.sock"\]' /etc/gitlab-runner/config.toml
    # configure the runner to mount the host's docker.sock to run docker commands
    docker run --rm -it \
        -v $$PWD/private/gitlab-runner/config:/etc/gitlab-runner \
        ubuntu \
        sed -i -e 's+volumes = \["/cache"\]+volumes = ["/var/run/docker.sock:/var/run/docker.sock","/cache"]+' /etc/gitlab-runner/config.toml
    docker stop gitlab-runner
    docker start gitlab-runner
restart-gitlab-runner: stop-gitlab-runner start-gitlab-runner
    docker stop gitlab-runner || true
    docker rm gitlab-runner || true
purge-gitlab-runner: clean-gitlab-runner
    sudo rm -rf private/gitlab-runner

With this in your Makefile, you can setup and start your local runner with

GITLAB_RUNNER_TOKEN="my-token" make setup-gitlab-runner

The only difference with the rest of the post is that the config will be placed under private/ rather than deployment/.