Add comprehensive Docker networking documentation
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]>
This commit is contained in:
1 parent
3f6dcab01f
commit
982b70a2ca
1 file changed
+388
@@ -0,0 +1,388 @@
|
||||
# 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:
|
||||
|
||||
```typescript
|
||||
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) → uses `localhost: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:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- NODE_ENV=production # Triggers production API URL
|
||||
- VITE_API_URL=... # Optional override
|
||||
```
|
||||
|
||||
### 3. Environment Configuration (.env.example)
|
||||
|
||||
```bash
|
||||
# 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
|
||||
```bash
|
||||
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
|
||||
```bash
|
||||
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
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
```typescript
|
||||
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)**
|
||||
```yaml
|
||||
frontend:
|
||||
environment:
|
||||
- VITE_API_URL=http://172.17.0.1:8000 # Default Docker gateway
|
||||
```
|
||||
|
||||
**Option 2: Pass --add-host flag**
|
||||
```bash
|
||||
docker compose run --add-host=host.docker.internal:host-gateway frontend
|
||||
```
|
||||
|
||||
**Option 3: Use network_mode: host**
|
||||
```yaml
|
||||
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:
|
||||
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```bash
|
||||
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?
|
||||
```bash
|
||||
curl http://localhost:8000/health
|
||||
```
|
||||
|
||||
**Check 2:** Is it listening on port 8000?
|
||||
```bash
|
||||
lsof -i :8000
|
||||
# Should show: uvicorn LISTEN
|
||||
```
|
||||
|
||||
**Check 3:** Is Docker container healthy?
|
||||
```bash
|
||||
docker compose ps
|
||||
# Should show: frontend Up (healthy)
|
||||
```
|
||||
|
||||
**Check 4:** Test from inside container
|
||||
```bash
|
||||
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:
|
||||
|
||||
1. **Rebuild frontend**
|
||||
```bash
|
||||
docker compose up --build frontend
|
||||
```
|
||||
|
||||
2. **Check logs**
|
||||
```bash
|
||||
docker compose logs -f frontend
|
||||
# Look for connection errors
|
||||
```
|
||||
|
||||
3. **On Linux, use manual gateway**
|
||||
```bash
|
||||
# Edit docker-compose.yml
|
||||
# Change VITE_API_URL to http://172.17.0.1:8000
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
4. **Hardcode the URL (Debug only)**
|
||||
```bash
|
||||
# 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**
|
||||
```bash
|
||||
# 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**
|
||||
```yaml
|
||||
frontend:
|
||||
environment:
|
||||
- VITE_API_URL=http://api.example.com:8000
|
||||
```
|
||||
|
||||
**Option 3: Modify App.tsx** (hardcoding - not recommended)
|
||||
```typescript
|
||||
const API_BASE_URL = 'http://your-api:8000';
|
||||
```
|
||||
|
||||
### Multi-Machine Setup
|
||||
|
||||
If API is on a different machine:
|
||||
|
||||
```yaml
|
||||
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's `localhost`
|
||||
|
||||
This creates a transparent bridge from containers to the host.
|
||||
|
||||
### Alternative Approaches (Not Used Here)
|
||||
|
||||
1. **Docker network with backend container** - Would require FastAPI in Docker too
|
||||
2. **Use external network** - More complex setup
|
||||
3. **Use host network mode** - Breaks container isolation
|
||||
4. **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
|
||||
|
||||
```bash
|
||||
# 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! ✨
|
||||
Reference in new issue
Block a user