We are building our own infrastructure to have something personal. So the question comes - should we rely fully on external infrastructure if we do not have to? My answer to this question is simple: If I can run something myself then I will at least try to. That includes the CI/CD infrastructure.
To be frank, I do not feel ready to fully host my own git repository BUT little step is better than none. There comes the GitLab Runner which is being spin up whevener we want to build something using GitLab’s robust CI/CD. That includes the site you are on right now.
What is GitLab Runner?
As per the official documentation:
GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline.
When developers push code to GitLab, they can define automated tasks in a .gitlab-ci.yml file. These tasks might include running tests, building applications, or deploying code. GitLab Runner is the application that executes these tasks on computing infrastructure.
As an administrator, you are responsible for providing and managing the infrastructure where these CI/CD jobs run. This involves installing GitLab Runner applications, configuring them, and ensuring they have adequate capacity to handle your organization’s CI/CD workload.
Long story short this is your workhorse for CI/CD workloads. There are multiple of those available from GitLab itself but they can (should not, but can) store data about your projects and they have some limits on them in regards to usage. To keep it more private and limitless we can create our own runners on our own infrastructure.
Preparations
For starters, you would need to create a PAT - Personal Access Token in GitLab itself. You can do so by going here or just reaching there yourself. Click your profile icon in the top right corner, go to Access and then to Personal access tokens. There you will have an option to create one. Click on Generate token. Set some meaningful name for it and be sure to add privileges for api and create_runner plus manage_runner. If that will be also the same PAT for the IAC repository access, you can also give it read_repository and write_repository.
You will get see the token ONLY ONCE so make sure to copy it and store it in your ansible vault to not lose it. In case you forgot: :)
ansible-vault edit <path>
I named mine gitlab_token.
To make it fully functional we will also need a group_id for the group your IAC repository belongs to. Go to the group in GitLab, then reach for the settings. Here you should have a number like 12312312. Copy it over to Ansible Vault. Mine variable is simply named gitlab_group_id.
Alright. We have the building blocks now. Let’s move over to our HACK repository.
Getting it ready
I will now show you three building blocks of our new LXC container with the GitLab Runner on the ProxmoxVE instance. You can go one by one testing it along the way (highly recommended) or just deep dive with the whole code and provision it with one set of commands. The floor is yours. :)
Infrastructure
We would need some infrastructure to get the runner on. With our previous efforts it is very simple. We will just create a new LXC container based on our LXC module.
# ./terraform/proxmox/test/lxc-runner.tf
module "lxc_runner" {
source = "../modules/lxc"
vm_id = var.vm_ids["lxc_runner"]
hostname = var.hostnames["lxc_runner"]
tags = var.tags["lxc_runner"]
proxmox_node = var.proxmox_node
template_file_id = data.terraform_remote_state.shared.outputs.ubuntu_2404
admin_password = var.admin_password
cores = 1
memory = 512
swap = 512
disk_size = 8
ip = var.ips["lxc_runner"]
gateway = "192.168.1.1"
}
Make sure to put the relevant variables in place in variables.tf file. Also, add the entry with IP to your Ansible inventory.
The Makefile does not change at the moment so you can safely run
make tf-test-apply
to get the new container on your homelab. It is just a plain system at the moment, so make sure to run the user bootstrap too:
make bootstrap-ansible-user
Deploying the runner
To have the runner up and running we will need to first define the compose file that will be later used by the docker_deploy role to put it online. Create it in the proper folder in ansible/files.
# ./ansible/files/runner/compose.yml
services:
gitlab-runner:
image: gitlab/gitlab-runner:alpine3.21
container_name: runner
hostname: runner
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- gitlab-runner-config:/etc/gitlab-runner
volumes:
gitlab-runner-config:
name: gitlab-runner-config
We are getting closer and closer. Now onto the playbook.
# ./ansible/playbooks/setup-lxc-runner.yml
---
- name: Setup runner node
hosts: lxc-runner
become: true
vars_files:
- "{{ playbook_dir }}/../vault/secrets.yml"
roles:
- common
- docker
- name: Deploy GitLab Runner
hosts: lxc-runner
become: true
vars_files:
- "{{ playbook_dir }}/../vault/secrets.yml"
roles:
- role: docker_deploy
vars:
service_name: runner-1
service_dir: /opt/runner
service_compose_file: "{{ playbook_dir }}/../files/runner/compose.yml"
This one will take care of deploying the Docker container to our LXC. It will just be running idle as registration (connection to the group of repositories) will come in a minute.
If you feel like it add this entry to the Makefile so the process in the future will go smoother:
# ...
### lxc-runner
setup-lxc-runner:
ansible-playbook ansible/playbooks/setup-lxc-runner.yml \
-i ansible/inventory/hosts.yml
# ...
Run it. Enjoy it.
make setup-lxc-runner
Once it is up successfully you can log in to the container itself and verify if the proper container is running.
docker ps
Registering the runner
Last but not least. Registration of the runner. Of course you could do that manually but that’s not what we want. We will append a little set of tasks to the deployment playbook to make it automated. Now the GitLab token and group ID will come into play.
# ./ansible/playbooks/setup-lxc-runner.yml
# ...
- name: Deploy GitLab Runner
hosts: lxc-runner
become: true
vars_files:
- "{{ playbook_dir }}/../vault/secrets.yml"
roles:
- role: docker_deploy
vars:
service_name: runner-1
service_dir: /opt/runner
service_compose_file: "{{ playbook_dir }}/../files/runner/compose.yml"
tasks:
- name: Ensure python3-requests is installed
ansible.builtin.package:
name: python3-requests
state: present
become: true
- name: Check if runner config exists
community.docker.docker_container_exec:
container: runner
command: sh -c "test -s /etc/gitlab-runner/config.toml && grep -q 'token' /etc/gitlab-runner/config.toml"
register: verify_result
failed_when: false
- name: Create runner via GitLab API
ansible.builtin.uri:
url: "https://gitlab.com/api/v4/user/runners"
method: POST
headers:
PRIVATE-TOKEN: "{{ gitlab_token }}"
body_format: json
body:
runner_type: "group_type"
group_id: "{{ gitlab_group_id | int }}"
run_untagged: true
tag_list: ["docker", "linux"]
description: "home-runner"
status_code: 201
register: runner_api_response
when: verify_result.rc != 0
- name: Register runner with generated token
community.docker.docker_container_exec:
container: runner
command: >
gitlab-runner register
--non-interactive
--url "https://gitlab.com/"
--token "{{ runner_api_response.json.token }}"
--executor "docker"
--docker-image alpine:latest
--description "home-runner"
when: verify_result.rc != 0
What it does is basically check if the configuration is already present in the Docker container and if not it creates the GitLab Runner in your project group via API request and then registers the one that you have on your machine. Simple, yet beautiful.
Rerun the setup and after that you can check if the runner is connected in your GitLab instance. You would typically look for a URL like https://gitlab.com/groups/<your_group>/-/runners.
Summary
Over the course of this short article you provisioned the infrastructure for the GitLab Runner, deployed it and registered it. You can use two simple commands to get it up and running in no time. Well, if you chain them in the Makefile you could easily make it one command. :)
Thanks for reading and see you next time!
… and most importantly: HAVE FUN!