Blog post
Configuring GitLab Runner on Docker to use a Squid proxy
How I configured a Docker-based GitLab Runner to route all traffic through a Squid HTTP proxy.
Intro
When a GitLab Runner does not have direct access to the internet, the runner, its job containers and Docker-in-Docker all need to know about the HTTP(S) proxy. Setting the proxy only on the runner host is not enough because the CI job and its Docker daemon run in separate containers.
Prerequisites
- A self-hosted GitLab instance.
- Docker installed on the machine running GitLab Runner.
- A Squid proxy reachable from the runner and its Docker network.
- A registered GitLab Runner using the Docker executor.
Running GitLab Runner in Docker
I run the runner as a Docker container and mount its configuration directory. The registration token is stored in the mounted configuration and is not included here:
services:
gitlab-runner:
image: gitlab/gitlab-runner
container_name: gitlab-runner
restart: unless-stopped
volumes:
- ./config:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
environment:
- NO_PROXY=localhost,docker:2375,docker:2376
- no_proxy=localhost,docker:2375,docker:2376
- HTTP_PROXY=http://proxy:3128
- http_proxy=http://proxy:3128
- HTTPS_PROXY=http://proxy:3128
- https_proxy=http://proxy:3128
The runner container needs access to the Docker socket to start job containers. If the runner and Squid container are on different Docker networks, make sure proxy resolves and is reachable from the runner's network.
Configuring the runner
In config/config.toml, the runner uses the Docker executor and passes both lowercase and uppercase proxy variables to every job. Some tools only read one casing, so I define both variants.
[[runners]]
name = "some-gitlab-runner"
executor = "docker"
# Make Git use the proxy while the runner fetches the repository.
pre_get_sources_script = "git config --global http.proxy $HTTP_PROXY; git config --global https.proxy $HTTPS_PROXY"
environment = [
"https_proxy=http://proxy:3128",
"http_proxy=http://proxy:3128",
"HTTPS_PROXY=http://proxy:3128",
"HTTP_PROXY=http://proxy:3128",
"no_proxy=localhost,docker:2375,docker:2376",
"NO_PROXY=localhost,docker:2375,docker:2376",
"NODE_USE_ENV_PROXY=1"
]
... other runner configuration ...
The pre_get_sources_script is important because Git source retrieval happens before the job's normal before_script. It explicitly configures Git before the runner clones the repository.
The no_proxy list keeps connections to the local Docker service out of Squid. Add any other internal GitLab, registry or service hostnames that should bypass the proxy.
Passing the proxy to Docker-in-Docker
Optionally, a job can use the docker:dind service to run Docker commands in a separate daemon. The docker:dind service runs in its own container and does not automatically inherit the job container's environment variables. The pre_build_script creates Docker's proxy configuration before the build starts:
pre_build_script = "mkdir -p $HOME/.docker/ && echo \"{ \\\"proxies\\\": { \\\"default\\\": { \\\"httpProxy\\\": \\\"$HTTP_PROXY\\\", \\\"httpsProxy\\\": \\\"$HTTPS_PROXY\\\", \\\"noProxy\\\": \\\"$NO_PROXY\\\" } } }\" > $HOME/.docker/config.json"
This writes a config.json in the job user's home directory. Docker uses it when creating containers, so image builds and other Docker operations can use the same proxy settings.
A CI job using Docker-in-Docker can then look like this:
default:
image: docker:28.5
variables:
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_VERIFY: "1"
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
build:
stage: build
services:
- name: docker:28.5-dind
alias: docker
script:
- docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
Testing the configuration
First check that the runner can reach Squid from its Docker network:
docker exec gitlab-runner sh -c 'wget -S -O - https://registry.npmjs.org/ 2>&1 | head'
Then run a small CI job that prints the proxy-related environment variables and tests both an HTTP and HTTPS endpoint:
proxy-test:
image: node:latest
script:
- env | grep -i proxy
- node -e "fetch('https://registry.npmjs.org/').then(response => console.log(response.status))"
Tips and troubleshooting
- Add internal GitLab and registry hostnames to both
NO_PROXYandno_proxywhen they should not go through Squid. - If HTTPS requests fail, check whether Squid permits the destination's
CONNECTrequest and whether the job image has current CA certificates. - Avoid putting proxy credentials directly in
config.toml; use protected CI/CD variables or another secret-management solution when authentication is required. - Make sure every application within the CI job is configured to use the proxy, as some tools ignore the environment variables and require explicit configuration.
Problems?
If you find mistakes or have suggestions, let me know. When troubleshooting, first check the runner's logs and the Squid access logs. You can also run a job with curl -v to see how the proxy is being used.