A beginner-friendly guide to using different API URLs for local development and Docker Compose, with clear environment file examples.
Keeping local development and Docker Compose in sync is mostly about the right API base URL. This guide shows the minimum you need to switch between localhost and Docker without surprises.
Architecture at a glance
Local
┌──────────┐ http://localhost:3000 http://localhost:8080/api
│ Browser │ ───────────────────────────────▶ Web app ───────────▶ Backend
└──────────┘
Docker Compose (containers on same network)
┌──────────┐ http://localhost:3000 http://backend:8080/api
│ Browser │ ───────────────────────────────▶ Web app ─────────────▶ Backend
└──────────┘Why two endpoints?
- Local dev: The web app runs on
http://localhost:3000and calls the API athttp://localhost:8080/api. - Docker Compose: Containers use the internal service name, so the web app calls
http://backend:8080/api.
1) Environment files for local vs. Docker
Create separate env files so you never mix the URLs:
Local override (not baked into images): web-app/.env.local
WEB_APP_API_URL=http://localhost:8080
WEB_APP_API_VER=/api
NEXT_PUBLIC_BACKEND_API_BASE=http://localhost:8080/apiDocker/default (used in containers): web-app/.env
WEB_APP_API_URL=http://backend:8080
WEB_APP_API_VER=/api
NEXT_PUBLIC_BACKEND_API_BASE=http://backend:8080/apiAdd .env.local to .dockerignore so local-only values never get baked into images.
2) App code picks the right base
In app/page.tsx, read the public env and fall back to localhost for safety:
const API_BASE =
process.env.NEXT_PUBLIC_BACKEND_API_BASE ?? 'http://localhost:8080/api';This keeps local dev working even if the file is missing.
3) Dockerfile and compose wiring
Pass the public env through the Docker build and runtime:
Dockerfile
ARG NEXT_PUBLIC_BACKEND_API_BASE
ENV NEXT_PUBLIC_BACKEND_API_BASE=${NEXT_PUBLIC_BACKEND_API_BASE}docker-compose.yml
web-app:
build:
context: ./web-app
args:
WEB_APP_API_URL: ${WEB_APP_API_URL:-http://backend:8080}
WEB_APP_API_VER: ${WEB_APP_API_VER:-/api}
NEXT_PUBLIC_BACKEND_API_BASE: ${NEXT_PUBLIC_BACKEND_API_BASE:-http://backend:8080/api}
environment:
NEXT_PUBLIC_BACKEND_API_BASE: ${NEXT_PUBLIC_BACKEND_API_BASE:-http://backend:8080/api}4) Local dev commands
From web-app/:
npm install
rm -rf .next # only if you changed env files
npm run dev # picks up .env.local automaticallyMake sure the backend is running at http://localhost:8080/api (or update NEXT_PUBLIC_BACKEND_API_BASE).
5) Docker Compose commands
Use the Compose values (http://backend:8080/api):
docker compose up --build web-app
# on Apple Silicon (if no multi-arch images):
DOCKER_DEFAULT_PLATFORM=linux/arm64 docker compose up --build web-app6) Avoiding the wrong env in containers
- Keep
.env.localin.dockerignore. - Rebuild after env changes:
docker compose up --build web-app. - Check your shell exports; none should override
NEXT_PUBLIC_BACKEND_API_BASEwhen running locally.
7) Quick checklist
- Local:
.env.local→ localhost API,npm run dev. - Docker:
.env+ compose build args/env →http://backend:8080/api. - After changes: rebuild containers.
- Connectivity:
curl http://backend:8080/api/report-runsfrom inside a container;curl http://localhost:8080/api/report-runslocally.
FAQ
- Why do we need two different API URLs?
- On your laptop the browser talks to http://localhost:8080/api, but inside Docker each container uses service names on the Compose network, so the web app reaches the backend at http://backend:8080/api instead.
- What happens if I forget to rebuild after changing env files?
- The container will keep using the old values baked into its image. Run `docker compose up --build web-app` to rebuild so the new env is picked up.
Welcome to The infinite monkey theorem
Somewhere a monkey just typed Shakespeare in TypeScript. Be the first to read the masterpieces (and the hilarious misfires) landing on the blog.

