Hardcoded localhost URL
The classic “works on my machine.” A localhost address that was fine locally points nowhere once the app is deployed.
The symptom
The app runs perfectly on your laptop, but after deploying, network requests
fail — “connection refused,” a spinner that never resolves, or a page that
loads but can't reach its API. Somewhere a URL like
http://localhost:3000 is baked in.
Why it happens
localhost means “this same machine.” On your laptop, your
frontend and backend are both on it, so http://localhost:8000
works. In production they're separate — and in a browser,
localhost means the visitor's computer, not your
server. The address that worked locally now points at the wrong place, or
nowhere.
This shows up in two forms:
1. Frontend calling a hardcoded API URL
A React/Vite/Next.js app with fetch("http://localhost:8000/api")
hardcoded. In a visitor's browser that tries to reach a server on
their machine, which fails.
Fix — read the API base URL from config instead of hardcoding it:
// Instead of:
fetch("http://localhost:8000/api/items");
// Read the base URL from runtime config (set as a dashboard variable):
const API = window.__APP_CONFIG__.API_URL;
fetch(`${API}/api/items`);
Where that value comes from depends on the app. A Vite SPA can't read
dashboard variables at build time, so get it at runtime: set
API_URL in the dashboard and read window.__APP_CONFIG__
(the full-stack recipe
shows the setup). In Next.js, call the API from server code reading
process.env. And if your frontend and API are the same app, skip
the URL entirely — use a relative path: fetch("/api/items").
2. Backend binding to localhost
A server that listens on localhost (or 127.0.0.1)
only accepts connections from inside its own container, so no outside traffic
ever reaches it — the URL just times out.
// Wrong — only reachable from inside the container:
app.listen(3000, "localhost");
// Right — reachable, and on the assigned port:
app.listen(process.env.PORT, "0.0.0.0");
Your app must listen on 0.0.0.0 and read its port from the
PORT environment variable — never localhost, never a
hardcoded port. Dockhold assigns PORT at runtime; an app that
ignores it can't receive traffic.
The rule
Nothing about where your app runs should be hardcoded. Bind servers
to 0.0.0.0:$PORT, and read every external URL from an
environment variable (or use a relative path when it's the same app). Set
those variables in the dashboard so production gets the right values.