<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cyrille Sepele</title>
    <description>The latest articles on DEV Community by Cyrille Sepele (@sepcy).</description>
    <link>https://dev.to/sepcy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3889620%2F7478edcd-5cf2-4d7e-b8b2-3bbce474ea8d.png</url>
      <title>DEV Community: Cyrille Sepele</title>
      <link>https://dev.to/sepcy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sepcy"/>
    <language>en</language>
    <item>
      <title>We Cut Our GitLab Build Time by 59% With One Change</title>
      <dc:creator>Cyrille Sepele</dc:creator>
      <pubDate>Sat, 16 May 2026 20:58:07 +0000</pubDate>
      <link>https://dev.to/sepcy/we-cut-our-gitlab-build-time-by-59-with-one-change-lle</link>
      <guid>https://dev.to/sepcy/we-cut-our-gitlab-build-time-by-59-with-one-change-lle</guid>
      <description>&lt;p&gt;You know the feeling. You push a one-line fix, open the pipeline, and watch your runner spend two minutes downloading &lt;code&gt;node_modules&lt;/code&gt;. Again. The same &lt;code&gt;node_modules&lt;/code&gt; it downloaded ten minutes ago. On the last push. That was also a one-line fix.&lt;/p&gt;

&lt;p&gt;Shared runners have the memory of a goldfish. And you're paying for it in build minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: shared runners forget everything
&lt;/h2&gt;

&lt;p&gt;GitLab's shared runners are ephemeral by design. Each job gets a clean machine. Great for isolation. Terrible for your afternoon.&lt;/p&gt;

&lt;p&gt;Every single job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker pulls your base images from scratch&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm install&lt;/code&gt; / &lt;code&gt;pip install&lt;/code&gt; / &lt;code&gt;bundle install&lt;/code&gt; downloads every dependency again&lt;/li&gt;
&lt;li&gt;Docker-in-Docker builds re-download every layer, every time&lt;/li&gt;
&lt;li&gt;Your test suite can't reuse compilation artifacts from the previous run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"But there's the &lt;code&gt;cache:&lt;/code&gt; keyword!" Sure. It uploads a tarball to object storage and downloads it on the next run. In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploading and downloading a 500MB archive takes its own sweet time&lt;/li&gt;
&lt;li&gt;Cache misses are silent and frequent (good luck debugging that)&lt;/li&gt;
&lt;li&gt;Docker image layers? The &lt;code&gt;cache:&lt;/code&gt; keyword can't help you there. You end up in a rabbit hole of registry-based workarounds and BuildKit inline caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small projects, whatever. For anything with real dependencies or Docker builds, you feel it on every push.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the cache actually sticks around
&lt;/h2&gt;

&lt;p&gt;When your runner lives on a dedicated machine that doesn't self-destruct after each job, things get better fast:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker layer cache just works.&lt;/strong&gt; Your &lt;code&gt;FROM node:20&lt;/code&gt; isn't pulled every run. Your &lt;code&gt;RUN apt-get install&lt;/code&gt; layer is already built. Docker's native caching does what it was designed to do. No config, no tricks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;/cache&lt;/code&gt; volume persists between jobs.&lt;/strong&gt; GitLab runners support a local cache directory mounted as a Docker volume. On a shared runner, that volume dies with the VM. On a dedicated machine, it stays. Your &lt;code&gt;cache:&lt;/code&gt; directive in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; writes to local disk instead of round-tripping through S3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker-in-Docker benefits the most.&lt;/strong&gt; If you're building container images in CI, a persistent Docker daemon means every subsequent build reuses layers from previous builds. No registry hacks. No BuildKit configuration. Just Docker doing its thing.&lt;/p&gt;

&lt;p&gt;None of this is magic. It's just what happens when your runner isn't destroyed after every job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof: same job, same project, very different numbers
&lt;/h2&gt;

&lt;p&gt;Here's our &lt;code&gt;build app&lt;/code&gt; job:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmirkzauyb1pkufbv2tpb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmirkzauyb1pkufbv2tpb.png" alt="Shared runner: 1 minute 54 seconds. RocketRunner: 47 seconds." width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same job. Same codebase. &lt;strong&gt;59% faster.&lt;/strong&gt; And that's a warm cache. The first run is comparable to a shared runner. Every run after that benefits from Docker layers and dependencies already sitting on disk.&lt;/p&gt;

&lt;p&gt;The queue time drop matters too. Shared runners serve everyone on GitLab.com, so your job waits in line behind strangers. A dedicated runner picks up your job immediately because it has nothing better to do.&lt;/p&gt;

&lt;p&gt;Now multiply that by 50 pipeline runs a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  "I'll just self-host a runner."
&lt;/h2&gt;

&lt;p&gt;You can. And if you have a dedicated ops person, or you genuinely enjoy debugging Docker daemon crashes on a Saturday morning, go for it.&lt;/p&gt;

&lt;p&gt;For everyone else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provisioning the server and keeping it updated&lt;/li&gt;
&lt;li&gt;Installing and configuring Docker + GitLab Runner&lt;/li&gt;
&lt;li&gt;Monitoring disk space (those Docker layers add up quietly)&lt;/li&gt;
&lt;li&gt;Rotating tokens, managing SSH keys&lt;/li&gt;
&lt;li&gt;Getting paged at 2 am because the runner went offline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cache benefits of a persistent runner are real. The Sunday afternoon you lose figuring out why &lt;code&gt;/var/lib/docker&lt;/code&gt; filled up the disk is also real.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built instead
&lt;/h2&gt;

&lt;p&gt;This is why &lt;a href="https://rocketrunner.io" rel="noopener noreferrer"&gt;RocketRunner&lt;/a&gt; exists.&lt;/p&gt;

&lt;p&gt;You get a dedicated VM. Real hardware, not a shared slice. Docker and the GitLab runner are installed and registered with your project automatically. Because it's your machine running your Docker daemon, all caching works natively.&lt;/p&gt;

&lt;p&gt;You don't configure any of this. It's a side effect of having a runner that doesn't get thrown away after every job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like in your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Typical Node.js setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:20&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CI_COMMIT_REF_SLUG}&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules/&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a shared runner, &lt;code&gt;npm ci&lt;/code&gt; downloads everything every time. The cache round-trip to S3 often takes longer than the install itself. Ironic.&lt;/p&gt;

&lt;p&gt;On RocketRunner, that cache lives on a local volume. First run populates it. Second run reads from disk. Done.&lt;/p&gt;

&lt;p&gt;For Docker builds, the gap gets embarrassing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker:24.0.5&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker:24.0.5-dind&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker build -t myapp:latest.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shared runner: pulls &lt;code&gt;docker:24.0.5&lt;/code&gt;, pulls every layer in your Dockerfile, every time. A 3-minute build that should take 20 seconds. You go make coffee. You come back. It's still pulling.&lt;/p&gt;

&lt;p&gt;RocketRunner: Docker daemon is already running. Base images are cached. Unchanged layers are skipped. It finishes before you can alt-tab away.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this matters (and when it doesn't)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Projects with Docker-in-Docker builds&lt;/li&gt;
&lt;li&gt;Monorepos with large dependency trees&lt;/li&gt;
&lt;li&gt;Teams running 20+ pipelines per day&lt;/li&gt;
&lt;li&gt;Anything where &lt;code&gt;npm install&lt;/code&gt; takes longer than your actual tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Probably overkill:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small projects with minimal dependencies&lt;/li&gt;
&lt;li&gt;Pipelines that only run linters or simple scripts&lt;/li&gt;
&lt;li&gt;Teams running fewer than a handful of pipelines per week&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;RocketRunner starts with a &lt;a href="https://rocketrunner.io" rel="noopener noreferrer"&gt;48-hour free trial&lt;/a&gt;. Setup takes about 2 minutes. Connect your GitLab account, pick a server size, choose a region, and your runner is live.&lt;/p&gt;

&lt;p&gt;Smallest plan runs at $0.018/hr with a $10.59/month cap. Most teams pay between $1-10/month.&lt;/p&gt;

&lt;p&gt;If your pipelines spend more time downloading dependencies than running your actual code, a persistent cache might be all you need.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>cicd</category>
      <category>devops</category>
      <category>docker</category>
    </item>
    <item>
      <title>Cheap Dedicated CI/CD Runners for GitLab: Shared vs Self-Hosted vs Rented</title>
      <dc:creator>Cyrille Sepele</dc:creator>
      <pubDate>Sat, 09 May 2026 21:01:17 +0000</pubDate>
      <link>https://dev.to/sepcy/cheap-dedicated-cicd-runners-for-gitlab-shared-vs-self-hosted-vs-rented-2a2a</link>
      <guid>https://dev.to/sepcy/cheap-dedicated-cicd-runners-for-gitlab-shared-vs-self-hosted-vs-rented-2a2a</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsrnp6o0kohe010ewnyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frsrnp6o0kohe010ewnyg.png" alt="3 ways to run GitLab CI jobs — cost and isolation compared" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your GitLab pipelines are slow, flaky, or eating into your SaaS bill, you've probably looked at your runner setup. There are three ways to run GitLab CI jobs, and the cost difference between them is bigger than most people realise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shared runner problem
&lt;/h2&gt;

&lt;p&gt;GitLab's shared runners are the path of least resistance. You don't set anything up, and they work. Until they don't.&lt;/p&gt;

&lt;p&gt;The issues show up gradually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jobs queuing behind other users' workloads&lt;/li&gt;
&lt;li&gt;Inconsistent build times&lt;/li&gt;
&lt;li&gt;No control over the hardware&lt;/li&gt;
&lt;li&gt;Shared filesystem state that causes flaky tests you can't reproduce locally&lt;/li&gt;
&lt;li&gt;No pipeline caching — every job starts cold, every time&lt;/li&gt;
&lt;li&gt;Limited CI minutes on the Free tier (400 min/month on GitLab.com)&lt;/li&gt;
&lt;li&gt;Extra minutes cost $10 per 1,000 if you exceed your allowance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're running Docker-in-Docker or anything that needs real isolation, shared runners are a constant source of friction.&lt;/p&gt;

&lt;p&gt;For a solo project or a weekend hack, shared runners are fine. For a team shipping to production, the unpredictability gets expensive fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-hosting: more control, more overhead
&lt;/h2&gt;

&lt;p&gt;The obvious answer is to run your own runner on a VPS. Full control, no queuing, dedicated hardware. A &lt;strong&gt;Hetzner CX23 (4GB RAM)&lt;/strong&gt; costs about &lt;strong&gt;€3.99/month&lt;/strong&gt; on paper — hard to beat.&lt;/p&gt;

&lt;p&gt;The catch is everything else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provision the server&lt;/li&gt;
&lt;li&gt;Install Docker and the GitLab runner binary&lt;/li&gt;
&lt;li&gt;Register it with your project or group (get the token, run the register command, handle the config)&lt;/li&gt;
&lt;li&gt;Keep it updated&lt;/li&gt;
&lt;li&gt;Monitor it&lt;/li&gt;
&lt;li&gt;Remember to destroy it when you're done, or keep paying for it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real cost of self-hosting isn't the €3.99/month server. It's the engineer who updates the runner binary when it falls behind, debugs the registration token when it expires, and gets paged when the disk fills up. If that's 30 minutes a month at a $50/hr developer rate, you've already spent more than the server costs.&lt;/p&gt;

&lt;p&gt;For a team that already owns and operates infrastructure, this overhead is absorbed. For a solo developer, a startup, or anyone who just wants pipelines to work, it's babysitting you didn't sign up for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Renting a dedicated runner by the hour
&lt;/h2&gt;

&lt;p&gt;There's a third option that most people haven't considered: &lt;strong&gt;renting a dedicated runner, billed hourly, with zero setup&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The model works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You sign in with GitLab&lt;/li&gt;
&lt;li&gt;Pick a server size and region&lt;/li&gt;
&lt;li&gt;The runner is provisioned and registered with your project automatically. No SSH, no config files&lt;/li&gt;
&lt;li&gt;You pay only while the runner exists&lt;/li&gt;
&lt;li&gt;Delete it, and billing stops immediately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://rocketrunner.io/" rel="noopener noreferrer"&gt;RocketRunner&lt;/a&gt; does this. A &lt;strong&gt;Small runner (4GB RAM, 2 vCPUs)&lt;/strong&gt; costs &lt;strong&gt;$0.018/hr&lt;/strong&gt;, or about &lt;strong&gt;$10.59/month&lt;/strong&gt; maximum if you run it 24/7. Most teams pay far less because they only run it when they need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual cost comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Monthly cost&lt;/th&gt;
&lt;th&gt;Setup time&lt;/th&gt;
&lt;th&gt;Isolation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab shared runners&lt;/td&gt;
&lt;td&gt;Included (with limits)&lt;/td&gt;
&lt;td&gt;0 min&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted on Hetzner CX23&lt;/td&gt;
&lt;td&gt;~$4.71/month + engineering time&lt;/td&gt;
&lt;td&gt;30–60 min&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rented dedicated runner&lt;/td&gt;
&lt;td&gt;$0.018/hr (~$1–10/month typical)&lt;/td&gt;
&lt;td&gt;2 min&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The self-hosted option has the lowest server bill if you run jobs 24/7. But once you factor in the engineering time to set it up and keep it running, renting by the hour is cheaper for most teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  When renting makes sense
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You're a solo developer or small team that doesn't want to maintain infrastructure&lt;/li&gt;
&lt;li&gt;You need full VM isolation (Docker-in-Docker, privileged containers, clean state per run)&lt;/li&gt;
&lt;li&gt;Your pipeline load is unpredictable, and you don't want to pay for idle compute&lt;/li&gt;
&lt;li&gt;You want runners in a specific region (EU or US) for compliance or latency reasons&lt;/li&gt;
&lt;li&gt;You're prototyping and want something live in 2 minutes, not 45&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Shared runners are free but unreliable. Self-hosting is cheap on paper but comes with hidden ops overhead. Renting a dedicated runner by the hour sits in between: full isolation, no setup, and a cost that scales with actual usage — no engineer babysitting required.&lt;/p&gt;

&lt;p&gt;If you've been putting up with slow or flaky GitLab pipelines, it's worth trying a dedicated runner. With a &lt;strong&gt;48-hour free trial&lt;/strong&gt; and no contracts, the cost of finding out is zero.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://rocketrunner.io/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Spin up a dedicated GitLab runner in 2 minutes&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://rocketrunner.io/" rel="noopener noreferrer"&gt;Get started at rocketrunner.io&lt;/a&gt;&lt;/strong&gt; — card required, no charge for 48 hours.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>devops</category>
      <category>cicd</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
