Orbit

Blocked by Our Policy: When a Build's Outbound Connection Is Refused

If your deployment failed with Blocked by our policy, our build network refused a connection your build tried to make. That refusal was our policy acting, not a fault in your code. This guide…

If your deployment failed with Blocked by our policy, our build network refused a connection your build tried to make. That refusal was our policy acting, not a fault in your code. This guide explains what a build is allowed to reach, why a refused connection looks like a hang rather than an error, how to read the evidence we show you, and how to get a destination of your own allowed.

The one thing to know first: this failure does not fix itself. Retrying changes nothing, because the same policy refuses the same destination on the next run. The fix is either to stop needing that destination at build time, or to declare it.

Why the Build Network Refuses Anything at All

Every Orbit build runs in its own single-use microVM. That sandbox is disposable, it holds a checkout of your repository, and it runs commands from your package.json, which means it runs code from your dependency tree too, including install scripts you have never read.

The build network is therefore default deny. The firewall on the build host runs a forward chain whose policy is drop, and traffic from a build sandbox only leaves if a rule explicitly says so. Nothing else gets out. This is what stops a compromised package in somebody's dependency tree from reaching the rest of the estate, the internet at large, or another customer's build running on the same machine.

An allowlist you could write to without constraint would not be a safer version of this. It would be this control switched off. That is why declaring a destination has rules, and why some declarations wait for a person.

What a Build Is Allowed to Reach

Three things, and nothing else:

  • DNS on port 53, to our own resolver only. Build sandboxes cannot query public DNS directly. Every lookup goes through our recursive resolver, which is also what lets us tell you the name your build asked for when a connection is refused.
  • TCP 443 and TCP 80, to allowlisted addresses. Package registries and source hosts, plus anything your project has declared and had approved.
  • Return traffic for connections that were already allowed out.

The standing allowlist covers the hosts a normal build needs:

registry.npmjs.org          registry.yarnpkg.com
github.com                  codeload.github.com
raw.githubusercontent.com   objects.githubusercontent.com
gitlab.com                  bitbucket.org
ghcr.io                     deb.nodesource.com
pypi.org                    files.pythonhosted.org
archive.ubuntu.com          security.ubuntu.com
ports.ubuntu.com

These are hostnames, not addresses, and the addresses behind them move constantly: registry.npmjs.org sits behind a CDN that rotates its records. So the allowlist is rebuilt every five minutes by resolving each name afresh. You do not need to do anything for these, because they are already open.

Two absences are deliberate rather than oversights:

  • Port 22 is not open. Git over SSH does not work from a build. Use HTTPS remotes for submodules and private dependencies.
  • Sandbox-to-sandbox traffic is dropped. Two of your own builds running at the same time cannot see each other.

Why It Hangs Instead of Failing Fast

This is the part that sends people looking in the wrong place, so it is worth understanding properly.

A refused connection is dropped, not rejected. There is a real difference:

  • A rejected connection gets an answer. The packet comes back saying no, and your tooling reports ECONNREFUSED immediately.
  • A dropped connection gets nothing at all. The packet goes into the void. Your build sits there waiting for a reply that is never coming, until something times out.

We drop rather than reject on purpose, because a reject tells anything running inside the sandbox exactly where the boundary is, one probe at a time. But it means the symptom you see is a build that stalls, not a build that errors. Your package manager retries, hits the same silence, retries again, and eventually the step times out or the whole build hits its limit.

This is also why a framework's own network error often never fires. If a build fetches fonts through next/font, for example, Next's Failed to fetch font ... Please check if the network is available message needs an actual HTTP response to trigger. There is no response. So you get a connect timeout from the underlying fetch instead, with nothing naming the real cause.

Because of that, we never infer this category from the shape of a timeout. A stalled connection could be a slow registry, a dropped packet, or our firewall, and guessing between them would be a confident answer with nothing behind it. We tell you a connection was refused only when the build host's own firewall recorded refusing it.

What You See in Your Logs

Only the host can know a drop happened, because by definition nothing inside the sandbox ever heard about it. So we join two records the host already keeps, the firewall's log of what it dropped for your sandbox's network interface and our resolver's log of what names it answered for that same sandbox, and write the result into your build log:

[KAPSULE_META] egressDenied= host=fonts.googleapis.com ip=172.253.130.95 port=443 count=6
[KAPSULE_META] egressDenied= host=- ip=64.239.109.65 port=443 count=6

The deployment page renders these as a list under Refused during this build, with the number of refused connections.

host=- means our resolver has no record of answering that address for your sandbox, so we show the address and do not invent a name for it. We deliberately do not reverse-resolve: a PTR record tells you what the address owner calls it, which is a different fact from the name your code actually asked for, and showing the wrong one would send you searching your repository for a string that is not in it.

If your build log has no egressDenied lines, then no connection was refused and whatever went wrong was something else. See Troubleshooting Failed Builds for the other failure categories.

Why We Do Not Retry This One

We retry build failures automatically when a retry has a real chance of a different outcome. This one does not qualify. The policy that refused the destination is the same policy on the next run, so an automatic retry would spend your build minutes to arrive at the identical result. Retrying it by hand has the same problem.

The deployment page says so in as many words, and that sentence is a promise about your billing rather than a hedge.

Declaring a Destination You Need

If your build genuinely needs to reach a service of your own, such as your API, your database, or a private package registry, you can declare it. Open the project, go to Connect, and add an egress destination with the hostname and port.

A declaration is checked before it reaches any firewall:

  • Shape. A hostname or an IP literal. Not a URL: no https://, no path, no port glued onto the host. The port is its own field. Each part is refused separately so you can see which one is wrong.
  • Not private or reserved space. Checked when you declare it and again when the build host collects the list, because a public hostname is free to start answering with a private address tomorrow. This covers loopback, RFC-1918, link-local and the cloud metadata endpoint.
  • Not our own infrastructure. Our control plane is public and routable, so a generic safety check would pass it. This arm exists specifically to stop a build sandbox being handed a route to our own services.
  • Ownership decides what happens next. If the host sits under a domain your account has proven it holds, it is approved and goes on the list. If it does not, it is recorded as pending and a person reviews it.

That last rule is not bureaucracy, and the reason is worth stating plainly: the allowlist is a single set on the build host, shared by every sandbox on that machine. A destination approved for one project becomes reachable from every build running there. Per-project egress needs infrastructure we have not built yet, so until it exists, "who authorised this" is answered by proof of ownership or by a human, never by the asking alone. A project can declare up to 25 destinations.

Approval is also not the same thing as the packet flowing. The build hosts collect the list on their own schedule, and the Connect screen reports when a host last fetched it. A status of approved tells you we said yes, not that the firewall has caught up yet.

The Port Caveat, Stated Plainly

Declaring a destination adds its address. It does not open its port.

The forward chain admits TCP 80 and TCP 443. It admits nothing else. So if you declare a PostgreSQL server on 5432, the address goes onto the allowlist and the packets are still dropped by the port rule. The same is true of 3306, 6379 and 27017.

We let you declare these anyway rather than refusing them, because a refusal would delete your ability to record what you need and to be told when it starts working. But the screen warns you, and this article warns you, because the alternative is waiting for a connection that cannot arrive.

If you need a database at build time today, the workable shapes are to reach it over HTTPS through an API or a data proxy, or to move the work out of the build entirely: run migrations and seeding at deploy or runtime rather than while the artifact is being produced.

Fixing It Without Declaring Anything

Most builds that hit this do not actually need the destination. The usual causes, and what to do instead:

  • Fonts fetched at build time. Self-host the font files in your repository. This is faster anyway, and it removes a build-time dependency on somebody else's CDN.
  • Remote content or an API call during static generation. Commit the data, or fetch it at runtime instead of at build time.
  • A private dependency over SSH. Switch the remote to HTTPS with a token in an environment variable.
  • An install script phoning home. Analytics and telemetry in postinstall hooks are a common source of refused connections and are safe to leave refused. If a build hangs on one, setting that package's opt-out environment variable will speed it up.

Related Reading

それでもお困りですか?

こちらまでメールでお問い合わせください support@kapsulehost.com またはKPanelでチャットを開いてください。

KPanel を開く