How to Set Up GitLab Runner on Windows Server 2022
GitLab Runner is the open-source agent that executes CI/CD jobs defined in your .gitlab-ci.yml files. Running a self-hosted GitLab Runner on Windows Server 2022 lets you build, test, and deploy Windows-native applications using PowerShell, batch scripts, MSBuild, and other Windows-specific toolchains that are not available on Linux-based shared runners. This guide covers the complete process from downloading the runner binary to configuring caching and troubleshooting connectivity.
Downloading GitLab Runner for Windows
GitLab Runner for Windows is distributed as a single executable binary. There is no traditional installer. Download the latest release from the GitLab Runner releases page. Use PowerShell to download it directly to your server:
# Create a dedicated directory for GitLab Runner
New-Item -ItemType Directory -Path "C:GitLab-Runner" -Force
# Download the latest 64-bit release
Invoke-WebRequest -Uri "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe" `
-OutFile "C:GitLab-Runnergitlab-runner.exe"
After downloading, verify the binary exists and check its version:
C:GitLab-Runnergitlab-runner.exe --version
This should print the version, architecture, and build SHA. Always compare this to the expected version from the GitLab releases page. Optionally, verify the SHA256 checksum published on the downloads page against the downloaded file:
Get-FileHash "C:GitLab-Runnergitlab-runner.exe" -Algorithm SHA256 | Select-Object Hash
Installing GitLab Runner as a Windows Service
GitLab Runner can run interactively from a terminal for testing, but for production use it should run as a Windows service that starts automatically with the server. Open an elevated PowerShell session (Run as Administrator) and install the service:
cd C:GitLab-Runner
.gitlab-runner.exe install
This installs the Windows service with the display name “gitlab-runner.” By default it runs as LocalSystem. For better security, specify a dedicated service account:
# Create a dedicated service account first
net user gitlabrunner StrongPassword456! /add
net localgroup Administrators gitlabrunner /add
# Install with the service account
.gitlab-runner.exe install --user ".gitlabrunner" --password "StrongPassword456!"
Start the service and verify it is running:
.gitlab-runner.exe start
Get-Service -Name gitlab-runner
You should see the service status as “Running.” The runner process is now active but not yet registered with any GitLab instance — it will not pick up any jobs until you complete the registration step.
Registering the Runner with GitLab
Registration connects your runner binary to a specific GitLab project, group, or entire instance. You need the GitLab instance URL and a registration token. Find the registration token in GitLab under Settings > CI/CD > Runners > New project runner (for project-level runners) or Admin Area > CI/CD > Runners > Register an instance runner (for instance-level runners).
Run the interactive registration command:
.gitlab-runner.exe register
The command prompts for several values. You can also pass all values on the command line for unattended registration:
.gitlab-runner.exe register `
--non-interactive `
--url "https://gitlab.yourdomain.com/" `
--registration-token "YOUR_REGISTRATION_TOKEN" `
--executor "shell" `
--shell "powershell" `
--description "Windows Server 2022 Runner" `
--tag-list "windows,windows-2022,powershell" `
--run-untagged false `
--locked false
After registration, restart the runner service so it picks up the new configuration:
.gitlab-runner.exe restart
Executor Types: Shell and Docker-Windows
The executor determines how jobs are run. The two most relevant executors for Windows Server 2022 are shell and docker-windows.
The shell executor runs jobs directly on the host using PowerShell or cmd.exe. It has direct access to all installed software on the server. This is the simplest option but carries risk: a malicious or broken job script can affect the host system. Use it for trusted internal repositories.
The docker-windows executor runs jobs inside Windows containers. This provides better isolation and repeatability. It requires Docker Desktop or Docker Engine with Windows container support installed on the server. When registering a docker-windows runner, specify the image to use:
.gitlab-runner.exe register `
--non-interactive `
--url "https://gitlab.yourdomain.com/" `
--registration-token "YOUR_TOKEN" `
--executor "docker-windows" `
--docker-image "mcr.microsoft.com/windows/servercore:ltsc2022" `
--description "Windows Docker Runner" `
--tag-list "windows,docker-windows"
Configuring Concurrent Jobs
By default, GitLab Runner runs one job at a time. To run multiple jobs in parallel (up to the number of CPU cores available), edit the config.toml file. On Windows, this file is located at:
C:GitLab-Runnerconfig.toml
Set the top-level concurrent value to the desired number of parallel jobs:
concurrent = 4
check_interval = 3
[[runners]]
name = "Windows Server 2022 Runner"
url = "https://gitlab.yourdomain.com/"
token = "RUNNER_TOKEN_FROM_REGISTRATION"
executor = "shell"
shell = "powershell"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
After editing config.toml, restart the runner service to apply changes:
.gitlab-runner.exe restart
Understanding config.toml on Windows
The config.toml file is the central configuration for all registered runners on the host. Each registered runner creates a [[runners]] block. The shell value controls which shell is used for script execution. Valid values on Windows are:
shell = "powershell" # Windows PowerShell 5.1
shell = "pwsh" # PowerShell 7+
shell = "cmd" # Command Prompt (legacy)
The builds_dir and cache_dir settings control where build workspaces and caches are stored. Redirect these to a data volume to avoid filling the OS disk:
[[runners]]
name = "Windows Server 2022 Runner"
url = "https://gitlab.yourdomain.com/"
token = "RUNNER_TOKEN"
executor = "shell"
shell = "pwsh"
builds_dir = "D:gitlab-builds"
cache_dir = "D:gitlab-cache"
Runner Tags and Using PowerShell Executor
Tags allow your .gitlab-ci.yml to target specific runners. When registering with –tag-list “windows,windows-2022,powershell”, only jobs that specify those tags will be picked up by this runner. Example .gitlab-ci.yml job targeting the Windows runner:
build-windows:
tags:
- windows-2022
- powershell
script:
- Write-Host "Building on Windows Server 2022"
- msbuild MyApp.sln /p:Configuration=Release /p:Platform=x64
With the PowerShell executor (shell = “pwsh”), each job script runs as a PowerShell 7 script. GitLab Runner wraps the CI/CD script in a temporary .ps1 file, sets the execution policy to RemoteSigned in the runner’s service context, and calls pwsh.exe. Environment variables set in GitLab CI/CD settings are injected as PowerShell environment variables accessible via $env:VARIABLE_NAME.
Caching Configuration on Windows
Caching speeds up builds by preserving build dependencies (NuGet packages, npm modules, pip packages) between runs. Configure local caching in config.toml:
[[runners]]
[runners.cache]
Type = "local"
Path = "D:gitlab-cache"
Shared = false
For distributed caching across multiple runners (important if you have multiple runner instances), use S3-compatible storage or Azure Blob Storage:
[runners.cache]
Type = "azure"
[runners.cache.azure]
AccountName = "yourstorageaccount"
AccountKey = "YOUR_STORAGE_ACCOUNT_KEY"
ContainerName = "gitlab-runner-cache"
In your .gitlab-ci.yml, define cache paths relative to the build directory. For NuGet packages:
build:
tags:
- windows-2022
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- packages/
- .nuget/
script:
- nuget restore
- msbuild /p:Configuration=Release
Troubleshooting Runner Connectivity
If the runner registers successfully but does not pick up jobs, check several common causes. First, verify the runner service is running and that the runner appears as online in GitLab under Settings > CI/CD > Runners. The runner polls GitLab at the check_interval (default 3 seconds).
Check the runner log for errors. The runner writes logs to the Windows Event Log under “Application” with source “gitlab-runner.” You can also view the log in real time by running the runner in the foreground temporarily:
.gitlab-runner.exe stop
.gitlab-runner.exe run --working-directory "C:GitLab-Runner"
If you see TLS certificate errors when connecting to a self-hosted GitLab instance, install your internal CA certificate into the Windows certificate store, or place the CA certificate file in C:GitLab-Runnercerts with the filename matching your GitLab hostname:
# Place CA cert as: C:GitLab-Runnercertsgitlab.yourdomain.com.crt
# GitLab Runner automatically looks for certs in this path
For network connectivity issues, confirm the server can reach the GitLab instance on port 443. Use Test-NetConnection to verify the connection:
Test-NetConnection -ComputerName gitlab.yourdomain.com -Port 443
A TcpTestSucceeded value of True confirms basic network access. If False, check firewall rules on both the Windows Server and any intermediate network appliances between the runner server and the GitLab instance.