Created NETWORKING_GUIDE.md explaining the host.docker.internal solution: - Problem: Docker frontend can't reach localhost:8000 on host - Solution: Use host.docker.internal for automatic DNS mapping - Implementation details for App.tsx, docker-compose.yml, .env - Step-by-step usage guide (3-terminal setup) - Verification procedures and troubleshooting - Platform-specific notes (macOS, Windows, Linux) - Development workflow (Docker vs npm dev) - Technical background and alternatives - Advanced configuration options - Quick reference cheat sheet Benefits: - Frontend automatically selects correct API URL - No manual configuration needed - Works on Docker Desktop (macOS/Windows) - Easy development workflow - Production-ready setup Co-authored-by: Copilot <[email protected]>
9.1 KiB
Docker Frontend-to-Backend Networking Guide
Problem Solved ✅
The React frontend running inside a Docker container needed to connect to the FastAPI backend running on the host machine at localhost:8000.
The Challenge: Inside a Docker container, localhost refers to the container itself, not your host machine.
The Solution: Use host.docker.internal - Docker Desktop's special DNS name that automatically maps to the host machine.
How It Works
Network Architecture
Your Host Machine Docker Container
┌──────────────────────────┐ ┌──────────────────────┐
│ │ │ │
│ FastAPI │◄─────────────┤ React Frontend │
│ localhost:8000 │ │ host.docker │
│ │ │ .internal:8000 │
│ Consumer │ │ │
│ (Kafka listener) │ └──────────────────────┘
│ │
└──────────────────────────┘
The Magic
Docker Desktop automatically provides host.docker.internal as a DNS entry that resolves to your host machine's localhost. This allows containers to reach services running on the host.
Implementation Details
1. Frontend Code (App.tsx)
The frontend intelligently selects the API URL based on environment:
const API_BASE_URL = process.env.NODE_ENV === 'production'
? 'http://host.docker.internal:8000' // Docker containers
: 'http://localhost:8000'; // Local development
Benefits:
npm run dev(development mode) → useslocalhost:8000✓- Docker (production mode) → uses
host.docker.internal:8000✓ - No hardcoding, automatic detection ✓
2. Docker Configuration (docker-compose.yml)
Frontend service explicitly sets the environment:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
environment:
- NODE_ENV=production # Triggers production API URL
- VITE_API_URL=... # Optional override
3. Environment Configuration (.env.example)
# Frontend can optionally override with environment variable
VITE_API_URL=http://host.docker.internal:8000
NODE_ENV=production
How to Use
Standard Setup (3 Terminals)
Terminal 1: Start infrastructure + frontend in Docker
docker compose up
Output shows:
frontend | ✓ Ready on http://0.0.0.0:3000
kafka | [Ready]
redis | [Ready]
zookeeper | [Ready]
Terminal 2: Start API on your machine
source venv/bin/activate
uvicorn api.main:app --reload --port 8000
Output shows:
Uvicorn running on http://127.0.0.1:8000
Terminal 3: Start consumer on your machine
cd consumer && python pipeline.py
Output shows:
Connected to Kafka broker at kafka:9092
Consumer ready...
Browser: Open http://localhost:3000
The frontend automatically connects to your API at http://host.docker.internal:8000 and works seamlessly!
Verification
Check Frontend Can Reach API
# From inside the Docker container
docker exec $(docker ps -q -f name=payguard-frontend) \
wget --spider -q http://host.docker.internal:8000
# Should complete without error (exit code 0)
Manual Test
# Start API first
uvicorn api.main:app --reload
# Then in another terminal, test the endpoint
curl http://localhost:8000/health
# Should return: {"status": "healthy", ...}
From Frontend
The frontend automatically tests connectivity:
const generateToken = async () => {
try {
const response = await axios.post(`${API_BASE_URL}/token`, {
user_id: auth.userId,
});
// Connection succeeded!
setAuth(prev => ({...prev, token: ...}));
} catch (error) {
// Connection failed
setError("Could not connect to API at " + API_BASE_URL);
}
};
Platform Compatibility
| Platform | Support | Notes |
|---|---|---|
| macOS | ✅ Full | Docker Desktop includes host.docker.internal |
| Windows | ✅ Full | Docker Desktop includes host.docker.internal |
| Linux | ⚠️ Manual | May need configuration (see below) |
Linux-Specific Setup
On Linux, host.docker.internal is not automatically available. Options:
Option 1: Use Docker gateway (Recommended)
frontend:
environment:
- VITE_API_URL=http://172.17.0.1:8000 # Default Docker gateway
Option 2: Pass --add-host flag
docker compose run --add-host=host.docker.internal:host-gateway frontend
Option 3: Use network_mode: host
frontend:
network_mode: "host"
# Then remove ports and depends_on conditions
Development Workflow
Running Frontend Locally (npm dev mode)
If you want to develop the frontend with hot-reloading:
# Stop the Docker frontend
docker compose stop frontend
# Start the local dev server
cd frontend
npm run dev
# This uses localhost:8000 automatically
The dev server will hot-reload as you edit code.
Running Frontend in Docker (production build)
docker compose up frontend
# Uses host.docker.internal:8000
# Production-optimized build, no hot-reload
Troubleshooting
"Cannot connect to API" Error
Check 1: Is the API running?
curl http://localhost:8000/health
Check 2: Is it listening on port 8000?
lsof -i :8000
# Should show: uvicorn LISTEN
Check 3: Is Docker container healthy?
docker compose ps
# Should show: frontend Up (healthy)
Check 4: Test from inside container
docker exec $(docker ps -q -f name=payguard-frontend) \
wget --verbose http://host.docker.internal:8000
# Should succeed with 200 status
API is running but frontend still fails
Try these in order:
-
Rebuild frontend
docker compose up --build frontend -
Check logs
docker compose logs -f frontend # Look for connection errors -
On Linux, use manual gateway
# Edit docker-compose.yml # Change VITE_API_URL to http://172.17.0.1:8000 docker compose up --build -
Hardcode the URL (Debug only)
# Edit frontend/.env file VITE_API_URL=http://host.docker.internal:8000 docker compose up --build
Advanced Configuration
Custom API URL
If you need a different API location:
Option 1: Environment variable
# Create frontend/.env (or .env.local)
VITE_API_URL=http://custom-api.example.com:8000
# Then restart
docker compose up --build frontend
Option 2: Modify docker-compose.yml
frontend:
environment:
- VITE_API_URL=http://api.example.com:8000
Option 3: Modify App.tsx (hardcoding - not recommended)
const API_BASE_URL = 'http://your-api:8000';
Multi-Machine Setup
If API is on a different machine:
frontend:
environment:
- VITE_API_URL=http://192.168.1.100:8000 # Replace with your API machine
Technical Background
Why host.docker.internal Works
Docker Desktop runs a lightweight Linux VM that contains your containers. Docker Desktop includes special DNS resolution:
- In containers:
host.docker.internal→192.168.65.2(Docker Desktop VM gateway) - In Docker Desktop VM:
192.168.65.2→ your host machine'slocalhost
This creates a transparent bridge from containers to the host.
Alternative Approaches (Not Used Here)
- Docker network with backend container - Would require FastAPI in Docker too
- Use external network - More complex setup
- Use host network mode - Breaks container isolation
- API in Docker, frontend on host - Opposite of current setup
The current approach (host.docker.internal) is the best balance for development:
- ✅ Frontend uses production Docker build
- ✅ API/Consumer use local development with hot-reloading
- ✅ Easy to iterate and debug
- ✅ No complex networking configuration
Summary
| Aspect | Solution |
|---|---|
| Frontend location | Docker container (port 3000) |
| API location | Your host machine (port 8000) |
| Connection method | host.docker.internal:8000 |
| Auto-detection | Yes (based on NODE_ENV) |
| Configuration needed | None (automatic!) |
| Works on macOS/Windows | Yes ✓ |
| Works on Linux | Yes (with one env var) |
Quick Reference
# Everything you need
# Terminal 1: Start Docker infrastructure + frontend
docker compose up
# Terminal 2: Start API
source venv/bin/activate
uvicorn api.main:app --reload
# Terminal 3: Start consumer
cd consumer && python pipeline.py
# Browser: Open this URL
http://localhost:3000
Frontend automatically finds API at host.docker.internal:8000.
No additional configuration needed! ✨