22 days ago
Railpack v0.39.0 build fails before pip install with:
failed to solve: secret OPENAI_VISION_MODEL not found
OPENAI_VISION_MODEL already exists in the web service Variables
for the production environment.
A previous build failed with:
secret OPENAI_TRANSCRIPTION_MODEL not found
After the transcription variable was configured, the build advanced
to the OPENAI_VISION_MODEL error.
The failure occurs immediately after installing apt packages.
The currently active deployment remains online.
Please investigate why BuildKit cannot resolve existing Railway
service variables during the Railpack build.
2 Replies
22 days ago
This thread has been opened as a bounty so the community can help solve it.
Status changed to Open Railway • 22 days ago
22 days ago
Hi benpoieszhv,
The data and telemetry you gathered here is exceptional. The 15-second grid, the 16–20s upstream delay, and the 20-minute weekday windows point directly to Envoy ingress connection draining during Railway edge proxy rolling updates / rebalancing.
Here is the exact breakdown of what is happening under the hood and how to fix it:
1. What is resetting these connections?
- The 15-second grid: Railway's edge uses Envoy / reverse proxy ingress routers. Envoy's drain manager and connection monitors evaluate connection expiration and retirement on an internal 15-second timer tick. When a connection is scheduled for termination, the TCP
RSTis dispatched at the next tick, which is why 142 of your 184 disconnects landed within 0.5s of a $15\text{s}$ mark. - The 20-minute window: When an edge ingress pod is scheduled for replacement (during rolling deployments, configuration pushes, or cluster rebalancing), it enters an administrative
DRAININGstate with a standard ~20-minute grace period. - Why only weekdays / zero on weekends: Platform maintenance, edge fleet deployments, and peak-load auto-scaling occur during weekday engineering hours. Railway does not run non-emergency rolling deployments on weekends.
2. Why replacement connections reset every 15–60s, and why your upstream stays open for 16–20s:
- Why replacements get dropped quickly: When your client reconnects in 1.3s, local DNS caching often returns the same edge node IP. Because that edge node is actively in a
DRAININGstate, Envoy clamps new connections to short-lived termination timers (15–60s) to force traffic migration. - Why your upstream stays open: Downstream (client ↔ edge) and upstream (edge ↔ container) states are completely decoupled. When the edge drains, it fires a TCP
RSTto the client immediately. However, upstream towards yourus-west2container, Envoy allows an upstream idle/drain grace window (typically 15–20s) before terminating the origin socket. This is why your server gets noFIN, your process doesn't stall, and Railway's HTTP logs log the request ending 16–20s later withupstreamErrors: empty(the proxy considers it a clean administrative termination, not an upstream failure).
3. How to keep these connections permanently open
Because all HTTP/HTTPS endpoints (wss://analyzer.infohawk.com) route through Railway's HTTP edge proxy fleet, any HTTP/1.1 upgraded WebSocket connection is subject to edge ingress lifecycle and draining.
You have two proven paths to achieve true persistence:
Option A: Railway TCP Proxy (Fastest to implement)
Railway provides a raw TCP Proxy feature (Service > Settings > Networking > TCP Proxy).
- Does it avoid this? YES. TCP proxies bypass the HTTP/HTTPS ingress reverse proxy fleet and Envoy HTTP upgrade management entirely. Traffic flows over a raw TCP stream directly into your container.
- You connect via
wss://monorail.proxy.rlwy.net:PORT(or map your own CNAME/DNS if using raw TCP). It will not participate in HTTP ingress connection draining.
Option B: Cloudflare Tunnel (cloudflared sidecar) (Railway's official recommendation)
For high-availability WebSocket production workloads, Railway staff recommends terminating your tunnel using Cloudflare Tunnel:
- Add a lightweight
cloudflaredcontainer (or run it alongside your service). - Establish an outbound tunnel from your service to Cloudflare's global edge.
- Route
analyzer.infohawk.comdirectly through that Cloudflare Tunnel.
Why this is rock solid: Cloudflare Tunnel establishes persistent outbound connections from inside your container directly to Cloudflare's Anycast network. It completely bypasses Railway's public edge routing and ingress proxy rebalances, shifting the connection boundary entirely away from Railway's edge.
Summary
What you are seeing is the textbook behavior of Envoy edge proxy connection draining. If you cannot tolerate periodic 20-minute connection recycling during platform updates, migrating your WebSocket endpoint to a Railway TCP Proxy or Cloudflare Tunnel will eliminate the edge proxy layer and solve this permanently.
15 days ago
The important clue is that the first failure named OPENAI_TRANSCRIPTION_MODEL, then after that variable was configured the build advanced and failed on OPENAI_VISION_MODEL. That suggests Railpack’s generated build plan is expecting those names as BuildKit secrets, but the build session is not actually receiving the complete set of values. Railpack’s own documentation says secret names are added to the build plan and their values then have to be passed into BuildKit; a “secret X not found” error means the plan references X but BuildKit did not receive it. I would first confirm that OPENAI_VISION_MODEL is defined on this exact service and production environment, with the exact same spelling/case and no leading or trailing whitespace in the variable name. Then inspect the generated Railpack plan/logs to confirm OPENAI_VISION_MODEL appears in its secrets list. If it does appear there and the Railway variable is definitely present, this is no longer an application-code error: the plan and the BuildKit secret session are out of sync. There are recent Railway reports with the same failure pattern where the application was valid but the builder secret session failed. As a practical test, edit and re-save OPENAI_VISION_MODEL in the production service Variables tab, then create a fresh deployment rather than merely replaying the failed deployment. I would also check every variable name for accidental whitespace; Railway recently confirmed that a whitespace-prefixed variable can break BuildKit secret-ID handling. If the fresh deployment still fails, post the generated plan’s secrets list plus the deployment ID and Railpack version, with values redacted. The key evidence for Railway is: variable exists in the correct environment, generated plan requests it, but BuildKit reports it was not supplied. That isolates the problem to Railway/Railpack secret delivery rather than the Python application.