CORS in Docker — Why localhost Works but Container Does Not
When you run your frontend and backend in separate Docker containers, they cannot reach each other via localhost. Container networking has its own namespace — localhost inside a container refers to the container itself, not your host machine.
The Docker networking problem
# This works locally (no Docker):
fetch('http://localhost:8000/api') # frontend on :3000 → backend on :8000
# This breaks in Docker:
# frontend container: localhost = the frontend container
# backend container: localhost = the backend container
# They are different machines
Fix 1 — Use service names in docker-compose
Docker Compose creates a network where containers can reach each other by their service name. Use the service name instead of localhost:
# docker-compose.yml
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://backend:8000 # use service name
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- CORS_ORIGIN=http://localhost:3000 # browser still uses localhost
The browser calls the backend using http://localhost:8000 (the host port mapping). The backend must allow the origin http://localhost:3000 for CORS.
Fix 2 — For host machine access, use host.docker.internal
# From inside a container, reach the host machine:
fetch('http://host.docker.internal:8000/api')
# Or in docker-compose:
extra_hosts:
- "host.docker.internal:host-gateway"
Fix 3 — Nginx reverse proxy for both services
The cleanest setup: Nginx in front of both. Same origin = no CORS needed:
# nginx.conf inside the Nginx container
server {
listen 80;
location / {
proxy_pass http://frontend:3000;
}
location /api/ {
proxy_pass http://backend:8000/;
# No CORS needed — same origin from browser's perspective
}
}
CORS config for Docker environment
// Express backend — allow the host port the browser uses
app.use(cors({
origin: [
'http://localhost:3000', // browser access via host port mapping
'http://frontend:3000', // container-to-container (if needed)
],
credentials: true,
}));
Test your Docker CORS config →