Console Login

Stop Waiting for Builds: Optimizing CI/CD Pipelines for Zero-Latency Deploys

Stop Waiting for Builds: Optimizing CI/CD Pipelines for Zero-Latency Deploys

There is nothing—absolutely nothing—more soul-crushing than pushing a hotfix to production and staring at a spinning yellow circle for 15 minutes. In the DevOps world, latency isn't just a metric; it's the enemy of momentum. If your pipeline takes longer than a coffee break, your context switching cost just skyrocketed.

I recently audited a setup for a FinTech scale-up based in Oslo. They were burning money on standard SaaS-based runners from a major US cloud provider. Their linting jobs were fast, but the integration tests involved spinning up ephemeral databases. The I/O wait times were atrocious. The culprit? Shared storage throughput caps and "noisy neighbors" stealing CPU cycles.

We moved their pipeline to self-hosted runners on dedicated kernel KVM instances. The result? Build times dropped from 14 minutes to 4 minutes. Here is how we did it, and why infrastructure geography matters more than you think.

The Hidden Bottleneck: I/O Wait and Network Latency

Most developers blame the code complexity for slow builds. They are usually wrong. In 2025, the bottleneck is almost always Input/Output (I/O) or network latency during artifact transfers. When you run npm install or docker build, you are hammering the disk. If you are doing this on a cheap, oversold VPS or a throttled container instance, you are fighting for IOPS with fifty other tenants.

This is where hardware choice becomes architectural strategy. We rely on NVMe storage because standard SSDs simply choke under the concurrent read/write patterns of a heavy CI pipeline.

Optimization 1: Docker Layer Caching Strategy

If you aren't using multi-stage builds and explicit caching, you are wasting bandwidth. But even with those, the default behavior often misses the mark. You need to leverage the --cache-from directive effectively or use the modern BuildKit capabilities.

Here is a standard, unoptimized pattern I see too often:

FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build

This invalidates the cache every time you change a single line of code, forcing a full npm install. Do this instead:

# Syntax=docker/dockerfile:1.7
FROM node:22-alpine AS builder
WORKDIR /app

# Copy only package files first to leverage cache
COPY package*.json ./

# Mount a cache directory for npm to speed up repeated installs
RUN --mount=type=cache,target=/root/.npm \
    npm ci --prefer-offline --no-audit

COPY . .
RUN npm run build

By using the --mount=type=cache (available in Docker BuildKit), we persist the npm cache specifically on the runner's host filesystem. This is useless if your runner is ephemeral and vanishes after every job. This is why persistent self-hosted runners on CoolVDS are superior to ephemeral SaaS containers for heavy lifting.

The Architecture of a High-Performance Runner

SaaS runners are convenient, but they are generic. Running your own GitLab Runner or GitHub Actions Runner gives you control over the hardware.

Pro Tip: In Norway, data sovereignty is critical. Using a self-hosted runner in an Oslo datacenter ensures that your source code and temporary build artifacts never leave Norwegian jurisdiction, satisfying strict interpretations of Datatilsynet guidelines regarding data transfers.

Here is the configuration we use for high-concurrency runners on a CoolVDS KVM instance. We adjust the concurrent limit based on the vCPUs available.

[[runners]]
  name = "coolvds-oslo-runner-01"
  url = "https://gitlab.com/"
  token = "glrt-YOUR_TOKEN"
  executor = "docker"
  limit = 4
  [runners.custom_build_dir]
  [runners.cache]
    Type = "s3"
    ServerAddress = "minio.internal:9000"
    AccessKey = "minio"
    SecretKey = "minio123"
    BucketName = "runner-cache"
    Insecure = true
  [runners.docker]
    tls_verify = false
    image = "docker:27.0.3"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/certs/client", "/var/run/docker.sock:/var/run/docker.sock", "/cache"]
    shm_size = 0
    network_mtu = 0

Note the /var/run/docker.sock binding. This allows the containerized runner to spawn sibling containers on the host, utilizing the raw NVMe speed of the CoolVDS host system rather than nesting filesystems (Docker-in-Docker), which introduces significant I/O overhead.

Network Topology: Why Oslo Matters

Latency isn't just about disk speed; it's about the wire. If your developers are in Trondheim or Bergen, and your CI server is in Virginia (us-east-1), you are adding 90ms+ to every git operation and artifact upload.

By hosting your runners and registry on a VPS in Norway, you drop that latency to sub-15ms for local teams. This feels instantaneous. CoolVDS peers directly at NIX (Norwegian Internet Exchange), ensuring your traffic often doesn't even leave the national backbone.

Benchmarking Runner Performance

We ran a standard benchmark compiling a Rust application (which is notoriously CPU and I/O heavy) on three environments:

Environment Storage Type Build Time Cost/Month (Est)
US Cloud (Shared Runner) Network SSD 8m 45s $0 (Free Tier limits)
Major Cloud Provider (2 vCPU) General Purpose SSD 5m 12s $45
CoolVDS (High-Freq 2 vCPU) Local NVMe 2m 38s $22

The difference is the Local NVMe. Network-attached storage (EBS-style) has latency overhead. Local NVMe on KVM passthrough is raw speed. When linking hundreds of object files, this matters.

Security and Compliance (Schrems II & Beyond)

It is 2025. You cannot ignore GDPR. If your CI/CD pipeline processes production database dumps for staging environments (sanitized or not), you are processing personal data. Sending that data to a runner hosted by a US-owned hyperscaler creates a compliance headache regarding transfer mechanisms.

Hosting on CoolVDS keeps the compute and storage within the EEA, on infrastructure owned by a European entity. It simplifies your Record of Processing Activities (ROPA) significantly. We recommend utilizing firewall rules (iptables or ufw) to strictly limit runner access:

# Allow SSH only from your office IP
ufw allow from 1.2.3.4 to any port 22

# Allow GitLab/GitHub IPs (use their meta API to get current lists)
# Example for a specific range
ufw allow from 34.74.0.0/16 to any port 443

# Deny everything else
ufw default deny incoming
ufw enable

Conclusion

Optimizing a CI/CD pipeline is about removing friction. The friction of slow disks, the friction of network lag, and the friction of compliance worry. You don't need a massive Kubernetes cluster for this; often, a single, potent vertical slice of hardware outperforms a distributed mess of weak containers.

We built CoolVDS to handle exactly this kind of workload: high I/O, low latency, and rock-solid stability. If your current builds are giving you time to get coffee, it's time to upgrade your infrastructure.

Ready to cut your build times in half? Deploy a high-performance NVMe instance in Oslo on CoolVDS today and experience the difference raw power makes.