{"id":4967,"date":"2026-10-02T19:02:37","date_gmt":"2026-10-02T13:32:37","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/creating-a-cicd-pipeline-with-gitlab-runner-on-a-vps\/"},"modified":"2026-10-02T19:02:37","modified_gmt":"2026-10-02T13:32:37","slug":"creating-a-cicd-pipeline-with-gitlab-runner-on-a-vps","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/creating-a-cicd-pipeline-with-gitlab-runner-on-a-vps\/","title":{"rendered":"Creating a CI\/CD Pipeline with GitLab Runner on a VPS"},"content":{"rendered":"<p style=\"font-size:16px;line-height:1.7;color:#333\">Scaling continuous delivery pipelines inevitably confronts engineering teams with a painful operational bottleneck: public shared runners plagued by unpredictable scheduling queues, rigid vCPU throttling, and ballooning monthly bills for compute minute overages. By provisioning a dedicated self-hosted GitLab Runner on an optimized Virtual Private Server (VPS), infrastructure architects regain absolute sovereignty over hardware allocations, eliminate build queue latency entirely, and accelerate build execution by up to 70% through persistent local NVMe caching. For developers and teams validating microservice prototypes or staging web applications, pairing agile staging environments on <a href=\"https:\/\/cpanelfree.com\" style=\"color:#001b41;text-decoration:underline;font-weight:600\">CpanelFree<\/a> with an isolated VPS-based runner creates a reliable, zero-friction CI\/CD ecosystem before rolling out to high-capacity clusters.<\/p>\n<p><!-- more --><\/p>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">What Is the Optimal GitLab Runner Setup on a VPS?<\/h2>\n<div class=\"wp-block-group has-background\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:20px 0\">\n<p style=\"margin:0;font-size:15px;line-height:1.6;color:#333\"><strong>Direct Answer:<\/strong> An enterprise-grade <strong>gitlab runner setup vps<\/strong> involves provisioning a Linux host with Docker, installing the official <code>gitlab-runner<\/code> repository, and registering the daemon with a scoped project authentication token. Configuring the Docker executor inside <code>\/etc\/gitlab-runner\/config.toml<\/code> enables isolated job execution, while tuning local host volume caching and kernel sysctls delivers sub-second job dispatching with zero SaaS queue overhead.<\/p>\n<\/div>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Modern software engineering demands tight feedback loops. When developers push commits, waiting four to ten minutes in a shared SaaS runner queue derails engineering velocity, causes context-switching, and slows deployment cadences. A dedicated VPS acts as a private execution worker that polls your GitLab instance (whether GitLab.com or a self-hosted Community\/Enterprise Edition instance) over an outbound TLS connection. Because the runner initiates all communication outbound, your build server requires no exposed public ingress ports, drastically reducing your attack surface while maximizing execution speed through dedicated local vCPU, RAM, and NVMe disk access.<\/p>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">Architectural Comparison: Shared GitLab SaaS vs. Dedicated VPS Runner<\/h2>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Before diving into configuration syntax, evaluating the architectural trade-offs between GitLab&#8217;s shared runner fleet and a dedicated VPS deployment clarifies why high-throughput engineering teams make the transition. The matrix below benchmarks key operational attributes across both topologies.<\/p>\n<figure class=\"wp-block-table is-style-regular\">\n<table style=\"width:100%;border-collapse:collapse;margin:24px 0;font-size:15px;text-align:left\">\n<thead style=\"background:#001b41;color:#ffffff\">\n<tr>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Feature \/ Metric<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Standard \/ Default<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Tuned \/ Production<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Build Queue Latency<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">180s &#8211; 600s (Shared Pool)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">&lt; 2s (Instant Local Dispatch)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Executor Isolation<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Shell (Host Contamination Risk)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Docker \/ Rootless DinD Containers<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Cache Restoration Throughput<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">12 &#8211; 25 MB\/s (Remote S3 Object Store)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">1,800+ MB\/s (Local NVMe Bind Mount)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Compute Resource Ceiling<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Fixed 1-2 vCPU Quotas<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Uncapped Host vCPU &amp; RAM Bursting<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Monthly CI Cost per 10k Mins<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">$100 &#8211; $250 \/ month<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Fixed VPS Cost ($5 &#8211; $20 \/ month)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">While SaaS shared runners are convenient for small hobby projects, their remote caching mechanisms across object storage buckets introduce tremendous latency on dependencies like <code>node_modules<\/code>, Maven repositories, or Cargo target directories. On a VPS with local NVMe storage, cache directories can be mounted directly into build containers via local volume bindings, making cache checks instantaneous.<\/p>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">Step 1: Host System Preparation and Linux Kernel Hardening<\/h2>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Running continuous integration tasks generates significant disk I\/O, rapid process spawning, container network namespace destruction, and high file descriptor consumption. Standard distribution defaults on Ubuntu or Debian will experience kernel connection tracking exhaustion or <code>too many open files<\/code> crashes under concurrent pipeline workloads. Apply the following kernel optimizations to prepare your VPS for production CI loads.<\/p>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Create the configuration file at <code>\/etc\/sysctl.d\/99-gitlab-runner.conf<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/sysctl.d\/99-gitlab-runner.conf\n# Linux Kernel Performance &amp; Stability Tuning for CI\/CD Workloads\n\n# File descriptor ceiling for concurrent container spawns and build tools\nfs.file-max = 2097152\nfs.inotify.max_user_watches = 524288\nfs.inotify.max_user_instances = 8192\n\n# Virtual memory tuning: prevent aggressive swapping during large compiler passes\nvm.swappiness = 10\nvm.dirty_ratio = 15\nvm.dirty_background_ratio = 5\nvm.max_map_count = 262144\n\n# Network connection tracking and port reuse for container bridges\nnet.core.somaxconn = 65535\nnet.ipv4.ip_forward = 1\nnet.ipv4.tcp_tw_reuse = 1\nnet.ipv4.tcp_fin_timeout = 15\nnet.netfilter.nf_conntrack_max = 262144\nnet.ipv4.ip_local_port_range = 10240 65535\n<\/code><\/pre>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Load the sysctl rules immediately without requiring a system reboot:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo sysctl -p \/etc\/sysctl.d\/99-gitlab-runner.conf<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note:<\/strong> Enabling <code>net.ipv4.ip_forward = 1<\/code> is mandatory for Docker bridge networking. If this parameter remains disabled, containers running inside your GitLab Runner will fail to resolve external DNS records or fetch upstream package dependencies during compilation.<\/p>\n<\/blockquote>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">Step 2: Installing Docker Engine and the GitLab Runner Daemon<\/h2>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">To maintain container isolation, we configure the runner with the <strong>Docker executor<\/strong>. This guarantees that every pipeline job executes inside a clean, reproducible container image (e.g., <code>node:20-alpine<\/code>, <code>golang:1.24<\/code>, or <code>python:3.12-slim<\/code>) rather than executing directly on the host shell where environment pollution and security risks thrive.<\/p>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Execute the commands below to install Docker Engine and the official GitLab Runner repository:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># Step A: Install Docker Engine Community Edition\nsudo apt-get update &amp;&amp; sudo apt-get install -y ca-certificates curl gnupg lsb-release\nsudo install -m 0755 -d \/etc\/apt\/keyrings\ncurl -fsSL https:\/\/download.docker.com\/linux\/ubuntu\/gpg | sudo gpg --dearmor -o \/etc\/apt\/keyrings\/docker.gpg\nsudo chmod a+r \/etc\/apt\/keyrings\/docker.gpg\n\necho \"deb [arch=$(dpkg --print-architecture) signed-by=\/etc\/apt\/keyrings\/docker.gpg] https:\/\/download.docker.com\/linux\/ubuntu $(lsb_release -cs) stable\" | sudo tee \/etc\/apt\/sources.list.d\/docker.list &gt; \/dev\/null\n\nsudo apt-get update &amp;&amp; sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin\n\n# Step B: Install the Official GitLab Runner Package\ncurl -L \"https:\/\/packages.gitlab.com\/install\/repositories\/runner\/gitlab-runner\/script.deb.sh\" | sudo bash\nsudo apt-get install -y gitlab-runner\n\n# Step C: Add gitlab-runner user to Docker group\nsudo usermod -aG docker gitlab-runner\nsudo systemctl enable --now gitlab-runner<\/code><\/pre>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Once installed, navigate to your GitLab project or group dashboard under <strong>Settings &gt; CI\/CD &gt; Runners<\/strong>. Click <em>New project runner<\/em>, assign tags (such as <code>vps-docker<\/code>, <code>production-ci<\/code>), and copy the generated runner authentication token (formatted as <code>glrt-...<\/code>). Register the runner via the command line using non-interactive automation flags:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># Non-interactive GitLab Runner registration\nsudo gitlab-runner register \\\n  --non-interactive \\\n  --url \"https:\/\/gitlab.com\/\" \\\n  --token \"glrt-YOUR_AUTHENTICATION_TOKEN_HERE\" \\\n  --executor \"docker\" \\\n  --docker-image \"docker:26.1-cli\" \\\n  --description \"vps-production-runner-01\" \\\n  --docker-privileged=\"false\" \\\n  --docker-pull-policy=\"if-not-present\"<\/code><\/pre>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">Step 3: Production Tuning for <code>\/etc\/gitlab-runner\/config.toml<\/code><\/h2>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">The default configuration generated by <code>gitlab-runner register<\/code> is intentionally basic and unoptimized for heavy workloads. To achieve rapid pipeline throughput and safe resource boundaries, modify <code>\/etc\/gitlab-runner\/config.toml<\/code>. This file controls global daemon concurrency, execution timeouts, persistent volume bindings, and pull policies.<\/p>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Open <code>\/etc\/gitlab-runner\/config.toml<\/code> and adjust it to reflect the enterprise configuration below:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/gitlab-runner\/config.toml\n# Production Tuned GitLab Runner Configuration\n\nconcurrent = 4\ncheck_interval = 2\nshutdown_timeout = 30\nlisten_address = \"127.0.0.1:9252\"\n\n[session_server]\n  session_timeout = 1800\n\n[[runners]]\n  name = \"vps-production-runner-01\"\n  url = \"https:\/\/gitlab.com\/\"\n  id = 142095\n  token = \"glrt-YOUR_AUTHENTICATION_TOKEN_HERE\"\n  token_obtained_at = 2026-03-15T08:00:00Z\n  token_expires_at = 0001-01-01T00:00:00Z\n  executor = \"docker\"\n  output_limit = 8192\n  \n  [runners.custom_build_dir]\n  [runners.cache]\n    MaxUploadedArchiveSize = 1048576000\n    [runners.cache.shared]\n  \n  [runners.docker]\n    tls_verify = false\n    image = \"docker:26.1-cli\"\n    privileged = false\n    disable_entrypoint_overwrite = false\n    oom_kill_disable = false\n    disable_cache = false\n    volumes = [\n      \"\/var\/run\/docker.sock:\/var\/run\/docker.sock\",\n      \"\/cache\",\n      \"\/opt\/ci-cache\/npm:\/root\/.npm:rw\",\n      \"\/opt\/ci-cache\/cargo:\/usr\/local\/cargo\/registry:rw\"\n    ]\n    shm_size = 2147483648\n    pull_policy = [\"if-not-present\"]\n    network_mode = \"bridge\"\n<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note:<\/strong> Notice the <code>pull_policy = [\"if-not-present\"]<\/code> and the shared host volumes in <code>\/opt\/ci-cache\/<\/code>. By caching package directories across jobs and instructing Docker to reuse local images instead of querying remote registries on every step, pipeline execution times decrease from minutes down to seconds.<\/p>\n<\/blockquote>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Ensure the host caching directory exists with proper permissions, and restart the runner service to apply the configuration:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo mkdir -p \/opt\/ci-cache\/npm \/opt\/ci-cache\/cargo\nsudo chmod -R 777 \/opt\/ci-cache\nsudo gitlab-runner restart<\/code><\/pre>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">Step 4: Designing the Multi-Stage <code>.gitlab-ci.yml<\/code> Pipeline<\/h2>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">With the runner daemon actively listening for jobs, configure the root <code>.gitlab-ci.yml<\/code> file in your repository. A production pipeline should segregate tasks into clean stages: code quality analysis, unit testing, container compilation, and continuous deployment. By utilizing explicit runner tags (<code>vps-docker<\/code>), jobs target your private VPS exclusively.<\/p>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Below is a robust, production-tested pipeline demonstrating caching, automated artifact generation, and secure zero-downtime container publishing:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># .gitlab-ci.yml\n# High-Efficiency Multi-Stage CI\/CD Pipeline\n\nstages:\n  - lint\n  - test\n  - build\n  - deploy\n\ndefault:\n  tags:\n    - vps-docker\n\nvariables:\n  DOCKER_IMAGE_NAME: \"$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA\"\n  DOCKER_IMAGE_LATEST: \"$CI_REGISTRY_IMAGE:latest\"\n\n# Stage 1: Static Code Analysis &amp; Linting\nlint:code:\n  stage: lint\n  image: node:20-alpine\n  script:\n    - npm ci --prefer-offline\n    - npm run lint\n  cache:\n    key: \"$CI_COMMIT_REF_SLUG-npm\"\n    paths:\n      - node_modules\/\n    policy: pull-push\n\n# Stage 2: Automated Unit and Integration Testing\ntest:unit:\n  stage: test\n  image: node:20-alpine\n  script:\n    - npm ci --prefer-offline\n    - npm run test:coverage\n  artifacts:\n    name: \"coverage-$CI_COMMIT_SHORT_SHA\"\n    expire_in: 7 days\n    reports:\n      junit: junit.xml\n    paths:\n      - coverage\/\n\n# Stage 3: Container Image Build &amp; Push\nbuild:container:\n  stage: build\n  image: docker:26.1-cli\n  before_script:\n    - echo \"$CI_JOB_TOKEN\" | docker login -u \"$CI_REGISTRY_USER\" --password-stdin \"$CI_REGISTRY\"\n  script:\n    - docker build --pull -t \"$DOCKER_IMAGE_NAME\" -t \"$DOCKER_IMAGE_LATEST\" .\n    - docker push \"$DOCKER_IMAGE_NAME\"\n    - docker push \"$DOCKER_IMAGE_LATEST\"\n\n# Stage 4: Production Deployment via Secure SSH\ndeploy:production:\n  stage: deploy\n  image: alpine:latest\n  rules:\n    - if: '$CI_COMMIT_BRANCH == \"main\"'\n      when: manual\n  before_script:\n    - apk add --no-cache openssh-client\n    - eval $(ssh-agent -s)\n    - echo \"$DEPLOY_SSH_PRIVATE_KEY\" | tr -d '\\r' | ssh-add -\n    - mkdir -p ~\/.ssh &amp;&amp; chmod 700 ~\/.ssh\n    - ssh-keyscan -H \"$PRODUCTION_SERVER_IP\" &gt;&gt; ~\/.ssh\/known_hosts\n  script:\n    - ssh \"$PRODUCTION_DEPLOY_USER@$PRODUCTION_SERVER_IP\" \"docker pull $DOCKER_IMAGE_NAME &amp;&amp; docker compose up -d --remove-orphans\"\n  environment:\n    name: production\n    url: https:\/\/example.com\n<\/code><\/pre>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">When taking production builds from staging into live traffic, your underlying host infrastructure must deliver predictable execution speeds and unthrottled I\/O. For web hosting and container workloads requiring bulletproof reliability, deploying on <a href=\"https:\/\/merahost.org\" style=\"color:#001b41;text-decoration:underline;font-weight:600\" target=\"_blank\" rel=\"noopener\">MeraHost Enterprise Cloud<\/a> guarantees dedicated NVMe disk arrays, enterprise LiteSpeed performance, and a strict Same Renewal Price policy that shields your operational budget from surprise price increases.<\/p>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">Automated Maintenance: Pruning Docker Artifacts and Monitoring<\/h2>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Because CI\/CD pipelines continuously pull intermediate base images and create build layers, an unmanaged Docker runner daemon will rapidly exhaust disk space within weeks. Implementing automated Docker garbage collection prevents catastrophic disk-full downtime.<\/p>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Deploy a dedicated systemd timer and service to prune dangling images and volumes nightly at 02:00 UTC. Create the service unit at <code>\/etc\/systemd\/system\/docker-ci-prune.service<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/systemd\/system\/docker-ci-prune.service\n[Unit]\nDescription=Automated Docker Garbage Collection for GitLab Runner\nAfter=docker.service\nRequires=docker.service\n\n[Service]\nType=oneshot\nExecStart=\/usr\/bin\/docker system prune -af --filter \"until=168h\"\nExecStart=\/usr\/bin\/docker volume prune -f --filter \"label!=keep\"\nStandardOutput=journal\nStandardError=journal\n<\/code><\/pre>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Next, pair this service with a matching timer at <code>\/etc\/systemd\/system\/docker-ci-prune.timer<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/systemd\/system\/docker-ci-prune.timer\n[Unit]\nDescription=Trigger Nightly Docker Cleanup for GitLab Runner\n\n[Timer]\nOnCalendar=*-*-* 02:00:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n<\/code><\/pre>\n<p style=\"font-size:15px;line-height:1.7;color:#444\">Enable and start the cleanup timer with systemctl:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo systemctl daemon-reload\nsudo systemctl enable --now docker-ci-prune.timer<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note:<\/strong> In addition to automated pruning, the runner exports internal Prometheus metrics on port <code>9252<\/code> as configured in <code>config.toml<\/code>. You can query <code>http:\/\/127.0.0.1:9252\/metrics<\/code> to inspect active worker allocations, job duration histograms, and pending failure tallies.<\/p>\n<\/blockquote>\n<h2 style=\"color:#001b41;font-size:24px;font-weight:700;margin-top:36px;margin-bottom:16px\">Frequently Asked Questions (FAQs)<\/h2>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">Should I use the Shell executor or the Docker executor on my VPS?<\/summary>\n<p style=\"margin-top:10px;color:#444\">The Docker executor is overwhelmingly recommended for production. The Shell executor executes build commands directly on the host operating system with the permissions of the <code>gitlab-runner<\/code> user, creating substantial security risks, file system contamination, and dependency drift between builds. The Docker executor encapsulates every pipeline job inside a clean, reproducible container, discarding runtime mutations when the container exits while allowing host volumes to preserve build caches safely.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">How do I prevent Docker builds from exhausting the VPS disk space?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Disk exhaustion is typically caused by orphaned build layers and dangling intermediate images. To prevent this, deploy an automated systemd timer that runs <code>docker system prune -af --filter \"until=168h\"<\/code> nightly, which purges images and stopped containers older than 7 days without removing actively cached base images. Additionally, configure multi-stage Dockerfiles with <code>--cache-from<\/code> arguments in your CI scripts to reuse registry cache layers efficiently.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">Can a single VPS run concurrent builds for multiple GitLab projects?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Yes. By increasing the <code>concurrent = X<\/code> directive in <code>\/etc\/gitlab-runner\/config.toml<\/code>, a single VPS can execute multiple jobs concurrently across different projects. Each job runs inside its own isolated Docker container bridge network. As a rule of thumb, set your concurrency limit to <code>(Total vCPU Cores * 2)<\/code>, provided your VPS has sufficient RAM (at least 2GB RAM per concurrent worker) to prevent out-of-memory kernel kills.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">What security risks exist when mounting \/var\/run\/docker.sock into the runner?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Mounting <code>\/var\/run\/docker.sock<\/code> into a runner container gives any command executed within the container root-level control over the host&#8217;s Docker daemon, allowing privilege escalation. For private repositories managed by trusted engineering teams, socket binding offers significant speed advantages. However, for untrusted repositories or open-source forks, you should avoid socket binding and instead utilize Docker-in-Docker (DinD) with TLS or rootless build tools like Kaniko and Buildah.<\/p>\n<\/details>\n<div class=\"wp-block-group has-background\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:8px;padding:32px;margin:40px 0;text-align:center\">\n<h3 style=\"color:#001b41;margin-top:0;font-size:24px;font-weight:700\">Deploy Enterprise-Grade Production Infrastructure<\/h3>\n<p style=\"color:#444;font-size:16px;line-height:1.6;max-width:680px;margin:12px auto 24px auto\">Need guaranteed performance with zero price hikes? Host mission-critical workloads on <strong style=\"color:#001b41\">MeraHost<\/strong> with pure Enterprise NVMe, LiteSpeed Web Server, and Same Renewal Price, Always (starting at \u20b999\/mo).<\/p>\n<div class=\"wp-block-buttons\" style=\"display:flex;gap:16px;justify-content:center;flex-wrap:wrap\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" href=\"https:\/\/merahost.org\" style=\"background:#001b41;color:#ffffff;font-weight:700;padding:12px 28px;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\" target=\"_blank\" rel=\"noopener\">Explore MeraHost NVMe Cloud &rarr;<\/a><\/div>\n<div class=\"wp-block-button is-style-outline\"><a class=\"wp-block-button__link\" href=\"https:\/\/cpanelfree.com\" style=\"background:transparent;color:#001b41;font-weight:600;padding:12px 24px;border:2px solid #001b41;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\">Deploy Free Staging on CpanelFree<\/a><\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Master GitLab Runner setup on a VPS with hardened Docker executors, caching, and CI\/CD pipelines. Boost build speeds and cut shared runner queue delays.<\/p>\n","protected":false},"author":1,"featured_media":4966,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[222],"tags":[57,177,87,101],"class_list":["post-4967","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-almalinux","tag-databases-performance","tag-devops","tag-sysadmin"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4967","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=4967"}],"version-history":[{"count":0,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4967\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/4966"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=4967"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=4967"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=4967"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}