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.

Related