Console Login

Stop Burning Cash: optimizing High-Velocity CI/CD Pipelines in 2023

Stop Burning Cash: Optimizing High-Velocity CI/CD Pipelines in 2023

I still remember the silence in the Slack channel last November. It was Black Friday week. A critical pricing bug was bleeding revenue. The fix was ready—three lines of code—but the deployment pipeline sat in a "Pending" state for 45 minutes.

Why? Because we were relying on shared, oversubscribed runners provided by a massive cloud conglomerate. Their "burstable" CPU credits had evaporated, and our build queue was dead in the water. We lost thousands of Euros that afternoon waiting for a container to spin up.

That was the last time I relied on shared infrastructure for mission-critical pipelines. If you are serious about DevOps, you don't rent a slice of a toaster; you provision raw power. In 2023, the standard for a decent CI/CD pipeline isn't just "it works"—it's "it works in under 3 minutes."

Here is how we architect pipelines that don't choke, focusing on the specific constraints of the Nordic infrastructure landscape.

The Bottleneck is Rarely CPU. It’s I/O.

Most developers throw more vCPUs at a slow build. This is usually a mistake. Analyze your build process. Whether you are running npm ci, compiling Go binaries, or building Docker images, your pipeline is fundamentally an I/O operation. You are reading thousands of small files, writing artifacts, and pushing layers.

On standard SATA SSD setups (or heaven forbid, spinning rust), your iowait spikes the moment parallel jobs kick in. You can verify this on your current runner:

iostat -x 1 10

If your %iowait consistently creeps above 5-10%, your storage is the bottleneck. This is where the infrastructure choice becomes binary: you either suffer or you upgrade to NVMe.

Pro Tip: In Norway, where latency to the NIX (Norwegian Internet Exchange) is minimal, your network throughput is rarely the issue. The choke point is almost always disk input/output operations per second (IOPS).

Step 1: The Self-Hosted Runner Architecture

Stop using shared SaaS runners. They are security black holes and performance bottlenecks. Deploying self-hosted runners on a high-performance VPS gives you isolation and consistent resource availability.

We use GitLab CI extensively. Below is a production-hardened config.toml for a runner deployed on a CoolVDS instance running Debian 12 (Bookworm). Note the concurrency settings and the usage of the Docker socket.

concurrent = 10
check_interval = 0

[[runners]]
  name = "coolvds-oslo-runner-01"
  url = "https://gitlab.com/"
  token = "YOUR_TOKEN_HERE"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "docker:24.0.5"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
    shm_size = 0

By mounting /var/run/docker.sock, we allow the container to spawn sibling containers, vastly speeding up Docker-in-Docker operations compared to the dind service approach, though security implications must be managed via trusted pipelines.

Step 2: Kernel Tuning for High Concurrency

A default Linux install is tuned for a desktop or a light web server, not a CI grinder that opens 50,000 sockets and file descriptors in two minutes. If you hit "Too many open files" errors, it's not a bug; it's a configuration oversight.

Check your current limit:

ulimit -n

If it says 1024, you are throttling your own builds. Here is the sysctl configuration we deploy on our CoolVDS nodes to handle massive concurrency without breaking a sweat.

# /etc/sysctl.conf optimization for CI/CD workloads

# Increase system-wide file descriptor limit
fs.file-max = 2097152

# Increase the size of the receive queue
net.core.netdev_max_backlog = 16384

# Increase the maximum connections
net.core.somaxconn = 8192

# Optimize TCP window sizes for high-bandwidth links (common in Nordic datacenters)
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.tcp_wmem = 4096 65536 33554432

# Allow more local ports for ephemeral connections (crucial for test suites)
net.ipv4.ip_local_port_range = 1024 65535

# Reuse specific TCP connections
net.ipv4.tcp_tw_reuse = 1

Apply these with sysctl -p. This tuning allows the runner to handle the massive burst of network connections that occur when pulling dependencies from npm or Maven central.

Step 3: Docker Layer Caching & BuildKit

In 2023, if you aren't using Docker BuildKit, you are living in the past. It handles dependency resolution more efficiently and allows for parallel build stages. Ensure this variable is set in your CI environment:

export DOCKER_BUILDKIT=1

Furthermore, structure your Dockerfile to maximize cache hits. The instruction order matters. Never copy your source code before installing dependencies.

# BAD PRACTICE
# COPY . .
# RUN npm install

# OPTIMIZED PATTERN
FROM node:18-alpine
WORKDIR /app

# Copy only dependency definitions first
COPY package*.json ./

# Install dependencies (this layer is cached unless package.json changes)
RUN npm ci --only=production

# Then copy source
COPY . .

CMD ["node", "server.js"]

The Data Sovereignty Factor (Schrems II & GDPR)

Technically, you could host your runners anywhere. Legally? That is a different minefield. Since the Schrems II ruling, transferring personal data (which often inadvertently ends up in build artifacts or database dumps used in testing) to US-owned cloud providers carries significant compliance risk for Norwegian companies.

Hosting your CI/CD infrastructure on CoolVDS ensures your data remains within European jurisdiction, specifically on servers physically located in optimized data centers. Plus, the latency advantage is undeniable.

Latency Check: Oslo to Central Europe

metricStandard Cloud (Frankfurt)CoolVDS (Local)
Ping to NIX25-35 ms< 2 ms
Disk I/O (Write)~150 MB/s (Throttled)1000+ MB/s (NVMe)
Data JurisdictionUncertain (US Cloud Act)Norway/EU Strict

Why Hardware Matters: The CoolVDS Reality

We built CoolVDS because we were tired of "noisy neighbors"—other tenants on a shared host eating up CPU cycles that belonged to us. In a CI/CD context, consistency is king. You need to know that a build takes 4 minutes, every single time.

We utilize KVM virtualization to ensure strict resource isolation. When you execute a heavy compile job, you are getting the dedicated CPU cycles you paid for, backed by NVMe storage that chews through node_modules folders like they don't exist.

One final check to ensure your current scheduler is optimized for NVMe:

cat /sys/block/vda/queue/scheduler

If you see [mq-deadline] or [none], you are good. If you see cfq, you are running on legacy configurations that are hurting your throughput.

Conclusion

Optimization isn't about one magic switch. It's the aggregation of marginal gains: 20% from a better Dockerfile, 15% from kernel tuning, and 50% from simply running on hardware that isn't oversubscribed trash.

Don't let a slow pipeline kill your developer momentum. Test your build times on a platform built for performance.

Deploy a high-frequency CoolVDS NVMe instance in Oslo today and stop waiting for the progress bar.