IP-Locked Delivery Breaks Agent Media Silently
A claimable Cloudinary environment locks media delivery to the public IP that ran the provisioning command. Uploads and transformations still succeed, so the failure surfaces only when a browser on another machine, or a preview deployment, requests the asset. Pass extra addresses at provisioning time, or claim the environment to remove the restriction.
Why this failure is quiet
When an agent runs npx @cloudinary/cloud, it gets a working product environment with no signup and no credentials handed over by a human. The trade for that convenience is a restriction most people never read: delivery is locked to the public IP address the command ran from, and the environment expires after 24 hours unless claimed. Cloudinary’s agent onboarding docs state both conditions up front, and the --ip flag exists precisely because one address is usually not enough.
The restriction applies to delivery only. Every other step in the pipeline behaves normally, which is what makes this a debugging problem rather than an error message. Below, one symptom → cause → check → fix cycle per way this bites.
Symptom: images 404 in the browser, but the agent says the upload worked
Likely cause. The agent and the browser are not the same machine, or not on the same public address. The provisioning command ran wherever the agent runs — a container, a remote dev box, a CI runner, your laptop’s terminal — and that is the address the environment allows. Your browser is a second address, and delivery from an unclaimed environment does not serve it. This is the ordinary case, not an edge case: an agent working in a sandbox while you review the result in Chrome is exactly the setup that breaks.
Check. Compare the two addresses directly rather than assuming. From the machine or container where the agent ran the provisioning command, and again from the machine where the browser is open, request your public address from any IP echo service and diff the results. If the agent runs inside Docker, WSL, a devcontainer or a remote SSH session, run it inside that context — the host’s address is not necessarily the container’s egress address. Then paste a failing delivery URL into a terminal on the agent’s machine with curl -I and compare the status code to the same request from the browser’s machine. Two different results for the same URL confirms it.
Fix. Re-provision with the viewing machine’s public address passed at provisioning time. The --ip flag is repeatable and accepts up to three addresses, so the agent’s egress address, your workstation and one more can all be allowed on the same environment. Decide the list before running the command: the flag is a provisioning-time input, not something you toggle afterwards on an environment that already exists. If you are scripting the provisioning step inside an agent session, the same pre-flight discipline applies as with provisioning any third-party service from inside an agent session — gather the inputs the service will not let you change later, before you make the call.
Symptom: every log line is green, the page is broken
Likely cause. Uploads and transformations are not restricted. The upload API accepts the asset, the transformation URL builds and the derived asset is generated on request, all against an environment that will nonetheless refuse to deliver bytes to an address it does not recognise. So the agent’s transcript reads as an unbroken run of successes, the asset appears in the media library, and the only place the failure appears is a browser rendering the page.
This inverts the usual debugging instinct. You look at the last thing that failed, and nothing failed. The temptation is to suspect the transformation string, the upload preset, or the framework’s image component — three things that are all working.
Check. Stop reading the agent’s output and go to the delivery URL directly. Take one asset’s public URL out of the rendered HTML and request it with curl -I from the machine whose browser shows the broken image. You are looking for a non-200 on a URL the agent has already confirmed exists. Then request the same URL from the agent’s machine. If the agent’s machine gets a 200 and yours does not, the pipeline is fine and the allowlist is the problem — no amount of re-uploading will change it.
Fix. Treat delivery as a separate verification step from upload, permanently. An agent that reports “uploaded and transformed” has verified two of three stages; make the third explicit by having it fetch one delivery URL from a machine that is not its own and check the status code. This is the same class of problem as testing systems whose failure modes are not deterministic: the check has to assert the observable outcome, not the intermediate steps that led to it.
Symptom: it works locally, images are missing from the preview deployment
Likely cause. The deployment host is a third address, and nobody counts it. You allowed the agent’s address and your workstation, saw images render in local dev, pushed a branch, and the preview build on Vercel, Netlify, Cloudflare or a staging box requests the same assets from an address that was never on the list. Worse, that address is often not stable — a build runner’s egress IP can differ between builds, so a hostname-based allowlist is not what you have here.
This is where the three-address ceiling starts to bind. Agent egress, developer workstation and preview host is already three, and a second developer or a second CI region has nowhere to go.
Check. Build the address list before you need it, not after. Run a public-IP lookup as a step inside the preview build itself and print it to the build log — that is the address that matters, and it is the one you cannot guess from your desk. Run the same build twice and compare the printed value; if it changes between runs, the host’s egress is not a fixed address and no allowlist entry will hold. Where the environment runs also determines the answer, which is one of the practical differences between serverless and long-running hosts for agent workloads.
Fix. If the preview host’s address is stable and you have a free slot, add it at provisioning time along with the other two. If it is not stable, or you have run out of slots, stop patching the allowlist — the restriction is not designed to survive a real deployment target, and the next section is the answer.
Symptom: you keep re-provisioning to add one more address
Likely cause. You are treating claiming as paperwork to do later. It is not. Claiming the environment removes the delivery restriction outright — the IP allowlist stops applying, rather than growing by one. Every re-provision to add an address is work that claiming makes unnecessary, and it comes with a second cost: the unclaimed environment expires after 24 hours anyway, so the address list you carefully assembled has a deadline attached to it. The same 24-hour clock and the same claim-or-lose-it shape apply to any resource an agent creates on your behalf.
Check. Find the claim URL. The provisioning command prints it, so it is in the agent’s transcript or its terminal scrollback; the ./.env file it wrote alongside it tells you which environment you are looking at. Then check whether it has been used — visit it, or confirm in the Cloudinary console that the product environment is attached to an account. Note the time the provisioning command ran and subtract it from now: if you are more than 24 hours out and never claimed, the environment is gone and the debugging you are doing is on a dead cloud.
Fix. Claim it. This is a human step by design — it requires a person with an account, and it is the point where the environment stops being disposable, gains an owner, and delivers to anyone. Cloudinary’s claimable cloud documentation covers the provisioning and claim flow, and the agent getting-started guide covers where it fits in an agent’s setup sequence. Practically, this means an agent’s setup output should end with the claim URL surfaced prominently rather than buried in a log — it is the difference between a working integration and one that vanishes overnight.
The rule to take away
--ip is a stopgap for the hours between provisioning and claiming. It is capped at three addresses because it is not meant to be an access-control system, and any real setup — agent, developer, preview, second developer — exceeds three quickly. If you find yourself managing the list, you have found the moment to claim.
The general shape is worth remembering beyond Cloudinary: when a service grants an agent something without a human in the loop, the grant is narrowed somewhere, and the narrowing is rarely on the path the agent tests. Here it is the viewing address. Elsewhere it might be a rate cap or a verification step, as with unauthenticated account-creation endpoints, where the credentials returned do not work until a human confirms the account by email. Find the narrowing before you build on top of it.
Sources
- Cloudinary's agent onboarding docs cloudinary.com
- Cloudinary's claimable cloud documentation cloudinary.com
- the agent getting-started guide cloudinary.com
See also
-
How to measure a coding assistant's effect on an engineering team: what acceptance metrics miss, why DORA is safe to publish, and when to capture a baseline.
-
How credit metering behaves under agent-driven work: one credit spans three axes, the window is rolling 30 days, and the limit arrives as errors.
-
What model routing does, what the decision costs, why heuristic routing beats classifier routing at small margins, and why fallback routing comes first.
-
Containers, microVMs, WebAssembly and hosted sandboxes compared on startup latency, blast radius, credential exposure and operational cost.