I Dockerized my app and one button broke β€” thanks, localhost
DEV Community

I Dockerized my app and one button broke - thanks, localhost

The setup

My side project, TestFlow Agent, is three little services:

  • a mock API (the thing under test)
  • a backend that generates and runs test flows
  • a React frontend

One of the headline features is a "Run Enrollment Flow" button: the backend fires a real sequence of requests at the mock API and reports back "enrollment created, status ACTIVE." Locally, it worked every single time. Then I added a one-command Docker setup so anyone could try it without juggling three terminals.

docker compose up --build, open the app, click around… analyze works, generate works, and then - "Run Enrollment Flow" fails. Only that button. The one that talks between services.

The hunt

The frontend calls the backend fine (both ports are published to the host). Generation is pure computation - fine. The only thing different about "Run Flow" is that the backend itself makes an outbound HTTP call to the mock API. I opened the backend and there it was:

const baseUrl = ' http://localhost:4000 ' ; // πŸ‘ˆ Natively, localhost:4000 is the mock API - same machine.

But inside the backend container, localhost means the container itself. There's no mock API on port 4000 in there. The backend was cheerfully calling itself and getting nothing. Classic "works on my machine," except the machine changed out from under it the moment it went into a container.

The fix

In Docker Compose, services reach each other by service name, not localhost. So I made the target configurable and pointed it at the service name inside the compose network - while keeping localhost as the default so native runs don't change at all:

// backend/routes/analysisRoutes.js
const baseUrl = process . env . MOCK_API_URL || ' http://localhost:4000 ' ;
# docker-compose.yml
backend :
  environment :
    - MOCK_API_URL=http://mock-api:4000 # reach the mock by its service name
  depends_on :
    - mock-api

πŸ”— PR: https://github.com/sshankar07/test-flow-agent/pull/3

Before / after

  • Before: Run Flow in Docker β†’ backend calls localhost:4000 β†’ nothing there β†’ failure.
  • After: Run Flow in Docker β†’ backend calls mock-api:4000 β†’ cross-container SUCCESS. I watched it create an enrollment (ENR-4C554E55, status ACTIVE) from inside the compose stack.

What I took away

  • Hardcoded localhost is a time bomb the day you containerize. It's invisible until the network boundary moves.
  • The fix isn't "hardcode the service name" either - it's make it configurable with a sane default, so both native and Docker runs work with zero edits.
  • Container-to-container = service-name DNS; host-to-container = published ports. Mixing them up is the root of a huge share of "it works locally but not in Docker" bugs.

Small diff, satisfying fix, and a reminder that the oldest bugs are the ones that keep showing up in new outfits.

Repo (open source, MIT): https://github.com/sshankar07/test-flow-agent

Comments

No comments yet. Start the discussion.