Bentornati, oggi voglio condividere con voi una breve guida su come collegare e configurare un runner di Gitlab, utile per tutti quelli che hanno un’istanza self hosted o in Cloud.
Obiettivo
Eseguire una pipeline ogni commit effettuato su Gitlab.
Cos’è un runner?
In Gitlab una pipeline è costituita da diversi step detti “Job” (build, test, lint, deploy), raggruppati in “Stage”

Per eseguire un Job è necessario che ci sia un Runner attivo, un vero e proprio agente che si occupi di eseguire l’azione vera e propria (se è un job di build il runner eseguirà la compilazione del codice).
Acquistando un piano di Gitlab cloud abbiamo incluso una serie di minuti di esecuzione e se non bastasse è possibile acquistare altro tempo di esecuzione.

Se invece (come nel mio caso) abbiamo a disposizione un’istanza self hosted non avremo a disposizione nessun runner preconfigurato, in questo caso configurarci un runner sarà obbligatorio se volessimo sfruttare l’utilizzo delle pipeline.
Alternative
Prima di incominciare ci tengo a precisare che esistono delle alternative per entrambi i casi (Gitlab in cloud o self hosted): uno degli strumenti più popolari e gratuiti è Jenkins, configurabile in modo tale che sia lui ad eseguire delle azioni in funzione di determinati eventi (es. commit). Ogni realtà aziendale è diversa e va valutata caso per caso, sta a voi scegliere se avere tutto all’internodi un unico strumento o avere N strumenti.
Prerequisiti
Il vantaggio di avere un runner self hosted è che non dovremo preoccuparci dei limiti di tempo e potremo eseguire quanti job vorremo. Dobbiamo però preoccuparci di creare una macchina dove eseguirli.
Quando ho configurato i runner in azienda mi è stata fornita dai miei sistemisti una macchina virtuale Linux anziché una macchina fisica, in questo caso loro decidono e noi ci adattiamo.
A prescindere ci sono alcuni vincoli, ossia che la macchina sia Linux e che ci sia installato Docker (o Podman), questo perché eseguiremo i runner all’interno di un container (per questioni di disaccoppiamento / mantenibilità / flessibilità).
Una volta che siamo pronti possiamo procedere.
Tipologia di runner
Esistono 3 tipologie di runner:
- Instance runner: una volta configurato sarà disponibile per tutti i progetti del repository
- Group runner: sarà disponibile solamente per i progetti appartenenti ad un gruppo
- Project runner: dedicato ad un singolo progetto
Ognuno di voi sceglierà, in funzione del repository e dei progetti, quale scegliere.
Se avete progetti più importanti di altri allora sarà strategico avere un runner dedicato come progetto o come gruppo. Considerate che ogni runner può eseguire N job in contemporanea (N è definito nella configurazione), avere un runner dedicato ad un progetto significherà che ad ogni commit la pipeline partirà immediatamente, in caso contrario andrà in “coda”.
Step 1: Creazione runner su Gitlab
In base alla tipologia di runner dovremo navigare diversi menù per raggiungere la pagina di creazione, in ogni caso colleghiamoci all’istanza Gitlab con un’utenza che abbia i privilegi di amministratore.
Instance runner
Clicchiamo il pulsante Admin nel menù laterale, in basso.

Ora clicchiamo su CI/CD –> Runners.

Clicchiamo sul pulsante azzurro “Create instance runner“:

Group runner
Clicchiamo sul gruppo dal menù laterale scegliamo Build –> Runners

Clicchiamo sul pulsante azzurro “Create group runner“

Project runner
Clicchiamo sul dettaglio del progetto, dopodiché clicchiamo su Settings –> CI/CD.

Si aprirà una pagina con una serie di configurazioni, espandiamo il box “Runners” e clicchiamo su “Create project runner“.

A prescindere dalla tipologia scelta arriveremo a questa pagina (il titolo in alto riporta la tipologia di runner, per il resto è tutto uguale):

Tags
I tags sono utilizzati per richiamare il runner corretto in funzione del job. Se ho un progetto composto da backend .NET e frontend angular allora dovrò avere due runner, uno per ogni linguaggio. Nella pipeline richiamerò il runner corretto utilizzando il tag.
Runner description
Vi prego, mettetela una descrizione, non c’è niente di peggio nel ritrovarsi con 15 runner e nessuna descrizione 🙂
Una volta che abbiamo inserito le informazioni clicchiamo su “Create runner“. La pagina che apparirà sarà la seguente:

Lasciamo aperta questa pagina e non chiudiamola, il token generato ci servirà presto.
Step 2: avvio container Docker runner
Ora dobbiamo inserire il token generato da Gitlab nella configurazione del container.
Colleghiamoci alla macchina dove abbiamo deciso di avviare il container docker, creiamo una cartella dedicata e generiamo file chiamato docker-compose.yml tramite questo comando:
nano docker-compose.yml
Incolliamo questo contenuto:
version: '3.8'
services:
gitlab-runner:
image: gitlab/gitlab-runner:latest
container_name: gitlab-runner
restart: always
volumes:
- <local_path_of_configuration>:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
Salviamo e usciamo (CTRL+X e successivamente Y per salvare).
Avviamo il docker compose tramite il comando
docker compose up -d
Ora che il container è avviato dobbiamo lanciare il comando di registrazione tramite la sua shell, scriviamo quindi
docker exec -it <contianer_id> gitlab-runner register --url <gitlab_url> --token <gitlab_token>
Seguite la procedura fino alla fine., avendo cura di selezionare docker come executor: questo punto è molto importante perché renderà questa configurazione indipendente dal sistema operativo. Il runner avvierà un container docker ogni volta che dovrà eseguire l’azione del job. Se stiamo configurando il runner per compilare angular allora inseriremo node-alpine:current
come immagine, se invece vogliamo compilare un progetto net core inseriamo mcr.microsoft.com/dotnet/sdk:9.0
.
Se tutto è andato a buon fine vedrete che sarà apparso un file config.toml
all’interno della directory “config”, sulla vostra macchina: questo è il file di configurazione del runner.
Un container può gestire più runner, in Gitlab però ogni runner sarà visualizzato come una singola riga. Un runner non corrisponde necessariamente ad una istanza Docker dedicata, può darsi che più runner siano legati ad un unico container
Step 3: Configurazione runner
Vi riporto qui un esempio di configuazione del file config.toml.
concurrent = 4
check_interval = 0
connection_max_age = "15m0s"
shutdown_timeout = 0
####### Logs #######
#log_level = "debug"
#log_format = "text"
###################
[session_server]
session_timeout = 1800
######### Angular runner ############
[[runners]]
name = "angular-runner"
url = "<gitlab_endpoint>"
id = 1
token = "<gitlab_runner1_token>"
token_obtained_at = 2025-07-09T14:50:10Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "docker"
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
tls_verify = false
image = "node-alpine:current"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
shm_size = 0
network_mtu = 0
memory = "4g" #Max 4GB RAM
cpus = "4" #CPU Limit
######### Dotnet runner ############
[[runners]]
name = "dotnet-runner"
url = "<gitlab_endpoint>"
id = 3
token = "<gitlab_runner2_token>"
token_obtained_at = 2025-07-10T08:41:03Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "docker"
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
tls_verify = false
image = "mcr.microsoft.com/dotnet/sdk:9.0"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
shm_size = 0
network_mtu = 0
memory = "4g" #Max 4GB RAM
cpus = "4" # CPU Limit
Soffermiamoci un attimo ad analizzare questa configurazione.
- La configurazione permette di gestire sia un progetto Angular (primo runner) che un progetto .NET 9 (secondo runner)
- La procedura di creazione runner (step 1 + Step 2) è stata eseguita due volte sullo stesso container
- Ogni modifica effettuata al file config.toml è recepita immediatamente, non è necessario riavviare il container
- E’ buona norma limitare l’utilizzo della CPU e della memoria, per non sovraccaricare il sistema rendendolo unresponsive
- concurrent = 4 in cima al file indica che eseguirà massimo 4 job in parallelo
Come ultimo punto ritorniamo su Gitlab e premiamo il pulsante “View runners” (lo trovate in fondo alla pagina che avete lasciato aperto): sotto status dovrà comparire la dicitura verde Online.

Debug e risoluzione dei problemi
Nel caso in cui qualcosa andasse storto sarà necessario visualizzare i log del container per capire il problema. Come prima cosa vi consiglio di abilitare i log a livello di debug (trovate l’esempio commentato sopra).
Una volta abilitati (non è necessario riavviare il container) è possibile visualizzare i log con il seguente comando:
docker logs -f <container_id>
A me è capitato che il firewall bloccasse alcune chiamate effettuate con i verbi PUT e PATCH, tramite i log ho trovato e risolto il problema.
Step 4: Pipeline Gitlab
E’ arrivato il momento di testare tutto il processo, per farlo utilizzerò un semplice progetto .NET 9 dove andrò ad eseguire il build ad ogni commit.
Creiamo il nuovo progetto e nella root del progetto aggiungiamo la pipeline Gitlab nel file gitlab-ci.yml
stages:
- build
variables:
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
DOTNET_NOLOGO: "1"
before_script:
- export PATH="$PATH:/root/.dotnet/tools"
- dotnet --version
build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:9.0
script:
- dotnet restore
- dotnet build --configuration Release --no-restore
artifacts:
paths:
- '**/bin/**'
expire_in: 1 hour
In Gitlab il risultato sarà il seguente:



Conclusioni
Per eseguire una pipeline in Gitlab è necessario avere un runner e in questo articolo abbiamo visto come installarne e configurarne uno.
Avere una pipeline di analisi che parte ad ogni commit è una best practice, oltre che ad essere strategico: non solo possiamo verificare che il nostro applicativo compili, ma possiamo anche aggiungere ulteriori step di analisi (es. sonarqube, rilevamento vulnerabilità, rilevamento bug, ecc) o step di deploy automatici sui nostri ambienti (testa, staging, produzione).
Grazie per l’attenzione, al prossimo articolo!