I cut my builds' Internet access — and found the hidden downloads costing our CI and my time

I started running builds in a no‑network sandbox to catch hidden downloads. It broke many repos, saved CI minutes, and forced a small, honest tradeoff.

Written by: Arjun Malhotra

Open laptop on a wooden desk showing a code editor and terminal windows
Photo by Christina @ wocintechchat.com on Unsplash

The night a deploy failed because an external tarball server was down, I went into the usual mild panic — stack traces, frantic Slack pings, and a half-hour scramble to re-run jobs. The next morning we realised the same flaky external host was causing dozens of seconds of waste in our CI, multiple re-runs, and an occasional failed release. We were paying for every second.

That was the moment I stopped trusting builds that could reach the internet.

A small experiment I added a single, tiny job to our pipeline: run the build inside a container with the network disabled. In GitLab CI it was docker run —network=none. Locally I used podman run —network=none or systemd-nspawn. The goal was simple: if the build succeeds with no network, no hidden external fetches are happening.

Within the first week it blew up.

What I found

Two practical consequences followed: our CI time dropped noticeably (fewer network waits and retries), and build failures caused by external outages vanished. More importantly, we stopped shipping surprises—runtime downloads that only showed up in production or on certain CI runners.

The exact rule I started enforcing If a build or test needs the internet to succeed, it must be explicit.

Explicit means:

This is not “no external dependencies ever” — it’s “no implicit, silent downloads during normal builds”.

How I implemented it (short, practical steps)

  1. Add a no-network CI job

    • In each pipeline, add a sanity job that builds and runs tests with network disabled (docker/podman run —network=none or use a small VM without outbound access). It should be early and fast — it fails fast if something reaches out.
  2. Make a developer flow for bootstrap

    • We created a bootstrap job that runs in a controlled environment and updates our internal cache registry (private npm registry, a small object store for binaries, or apt repo).
    • Developers run bootstrap when adding a new external tool — it’s explicit and documented in CONTRIBUTING.md.
  3. Replace hidden downloads with cached artifacts

    • For node, we run npm ci using our private registry / Verdaccio, with the registry URL injected only in the bootstrap step.
    • For binaries, we checked them into a repo subfolder or stored them in an internal S3 bucket and referenced them via CI artifacts (not via curl at runtime).
  4. Fail early and loudly

    • The no-network job is a gate. If it fails, the developer sees the exact stack trace and knows exactly which script attempted network access.

Why this actually helps in India Our office internet is flaky and mobile data costs matter when I verify things on a hotspot. CI minutes are billed in INR as real pressure when you get many retries. Hidden network calls inflated both our invoice and my time. Once we blocked implicit external access, developers stopped blaming “the network” and started fixing the root cause.

The honest failure — and the tradeoff I didn’t expect I thought this would be frictionless. It wasn’t.

What actually changed my team’s behaviour Two things:

  1. Visible savings: After a month, we tracked CI minutes and saw a reduction of ~18% (smaller for teams heavily using big integration tests). Developers also reported fewer flaky runs. When people saw the numbers in rupees on our billing dashboard, the bootstrap script stopped being a nuisance and became policy.

  2. Concrete dev ergonomics: We automated the bootstrap for local machines. A one-time command (scripts/bootstrap-cache.sh) populates the local cache from our internal store, so new laptops work without manual fiddling. The trick is to make the “explicit bootstrap” as painless as possible.

When you shouldn’t do this If your project depends on fetching dynamic artifacts (like constantly-updated datasets), or you cannot afford the maintenance of a cache, this model will hurt you. Also, for very small one‑person projects, the overhead isn’t worth it.

What I walked away with Stopping implicit network access exposed sloppy assumptions we all had — little curls, unpinned downloads, and “works on my machine” conveniences. It forced explicit decisions: cache this, vendor that, or make the bootstrap step obvious.

The takeaway is not paranoia. It’s a single lightweight rule: builds must succeed without the internet unless you intentionally opt into a clearly documented bootstrap flow. That rule costs some maintenance. It saved me time, reduced wasted CI rupees, and made our builds predictable — which, for a small team with limited ops bandwidth, is worth the tradeoff.