Tools: GitLab SE behind Cloudflare Zero Trust: Private CI Runner

Tools: GitLab SE behind Cloudflare Zero Trust: Private CI Runner

Introduction

Constraints / assumptions

High‑level design

Human access

CI execution

Phase 1 --- Make it work

Expose an internal GitLab entrypoint

Provision a runner VM

Validate with a smoke pipeline

Phase 2 --- Reduce trust / Harden access

Disable privileged Docker mode

Restrict job scheduling with tags

Lessons learned

Where to go next

Repository

Published Labs in This Series In the previous labs, we deployed a self‑hosted GitLab instance behind Cloudflare Zero Trust to protect access to the web interface and repositories. That setup works well for human users, but CI systems introduce a different kind of traffic. GitLab runners interact with GitLab non‑interactively: they fetch repositories, call APIs, and upload job results automatically. When the public GitLab domain is protected by Cloudflare Access, these interactions can be redirected to authentication flows that CI jobs cannot complete. This lab explores a practical solution: running CI jobs on a GitLab Runner inside the private network, allowing CI traffic to bypass the Zero Trust authentication layer entirely. The core idea is simple: Humans access GitLab through Cloudflare Zero Trust. CI jobs interact with GitLab entirely inside the private network. Extend the existing GitLab deployment by introducing a private GitLab Runner VM that executes CI jobs without relying on the public Cloudflare‑protected endpoint. By the end of this lab: This lab intentionally keeps the environment simple and reproducible. The environment is intentionally local. The focus of the lab is network design and CI architecture, not cloud infrastructure configuration. The architecture introduces two separate access paths to GitLab. Developers reach GitLab through the public domain protected by Cloudflare Zero Trust. Developer → Cloudflare Zero Trust → GitLab public domain GitLab communicates with the runner entirely inside the private network. GitLab VM → internal NGINX listener → GitLab Runner VM The runner registers using the internal endpoint and uses the same address for repository checkout through the clone_url setting. This ensures CI jobs never follow the public access path and therefore never encounter Cloudflare authentication redirects. The first phase focuses on simply getting a pipeline to run on the private runner. Two architectural adjustments are required. The previous lab disabled GitLab's bundled NGINX because external traffic was handled by the Cloudflare tunnel and reverse proxy. However, CI jobs require a stable HTTP endpoint that supports Git operations. Using Puma directly is unreliable for this purpose, and the public domain leads to Cloudflare authentication redirects. To solve this, the GitLab VM exposes an internal NGINX listener bound to the private IP. http://<GITLAB_PRIVATE_IP>{=html}:8081 This endpoint is used for: A second VM is created inside the same private network and configured with: During registration, the runner configuration sets: url → internal GitLab endpoint

clone_url → internal GitLab endpoint The clone_url parameter ensures repository checkout uses the internal address instead of the public GitLab domain. A minimal CI project confirms that the runner works correctly by verifying: Once the pipeline succeeds, the private CI path is confirmed to work correctly. Once the runner works, the next step is reducing permissions that are not actually required. Privileged containers grant very broad capabilities to CI jobs, including access to host‑level resources. The smoke pipeline only runs basic commands inside a container, so privileged mode is unnecessary. Disabling it reduces the attack surface of the runner host. The runner is configured with explicit tags and untagged jobs are disabled. Pipelines must explicitly reference these tags to run on the runner. This prevents unrelated projects or misconfigured jobs from executing there accidentally. Running GitLab behind Cloudflare Zero Trust introduces an important architectural distinction. Interactive users and automated CI jobs have very different requirements. Human users can complete authentication flows through Cloudflare Access. CI systems cannot. Instead of weakening the Zero Trust configuration, a better approach is introducing a dedicated internal access path for CI systems. This lab demonstrates that pattern clearly: Another key lesson is that CI runners should start permissive enough to work, but once pipelines are validated, unnecessary privileges should be removed. This lab opens the door to several useful extensions. Possible follow‑up experiments include: Each of these steps moves the lab environment closer to a realistic CI/CD platform while keeping the architecture transparent and reproducible. The full lab --- including Vagrant configuration, provisioning scripts, diagrams, and the reproducible runbook --- is available in the repository linked below. 👉 https://github.com/ic-devops-lab/devops-labs/tree/main/GitLabSEBehindCloudflare03Runner Each lab explores a boundary in infrastructure design and gradually shifts trust from network assumptions toward identity and workload isolation. Templates let you quickly answer FAQs or store snippets for re-use. as well , this person and/or - GitLab remains accessible through Cloudflare Zero Trust- CI jobs run on a runner VM in the same private network- repository checkout uses an internal GitLab endpoint- pipelines run successfully without hitting Cloudflare authentication - GitLab is already running behind Cloudflare Zero Trust- GitLab and the runner VM share the same private network- infrastructure is provisioned using Vagrant- the runner uses the Docker executor- the goal is architecture clarity, not production‑grade scaling - runner registration- GitLab API calls- repository checkout - GitLab Runner- a Docker executor- explicit runner tags - runner identity- container execution- GitLab reachability- successful repository checkout - public access remains protected- CI traffic stays inside the private network- repository checkout uses a stable internal endpoint - GitLab behind Cloudflare --- Runner VM (multiple runners andscheduling policies)- GitLab behind Cloudflare --- S3 storage VM for caches,artifacts, and pages- GitLab behind Cloudflare --- Automation with Ansible- GitLab behind Cloudflare --- Full CI/CD example\(build → checks → unit tests → reports → packaging →containerization → deployment) - Securing a Remote Linux Host with firewalld and OpenVPN- GitLab Behind Cloudflare Zero Trust- GitLab Behind Cloudflare Tunnel --- Removing Inbound SSH Exposure