# Docker Compose Setup

**Created:** 2026-03-24  
**Purpose:** Orchestrate Recipe Manager backend and frontend containers

---

## Overview

The `docker-compose.yml` file orchestrates two services:
1. **Backend:** Node.js API server (Express + SQLite)
2. **Frontend:** React SPA served by nginx with API proxy

### Architecture

```
┌─────────────────────────────────────────────┐
│  Host Machine (http://localhost:8080)       │
└────────────────┬────────────────────────────┘
                 │
┌────────────────┴────────────────────────────┐
│  Frontend Container (nginx:alpine)          │
│  - Serves React SPA on port 80              │
│  - Proxies /api/* to backend service        │
│  - Health check: /health endpoint           │
└────────────────┬────────────────────────────┘
                 │ Docker network (recipe-network)
┌────────────────┴────────────────────────────┐
│  Backend Container (node:22-alpine)         │
│  - Express API on port 3000                 │
│  - SQLite database in /app/data volume      │
│  - Runs migrations on startup               │
│  - Health check: GET /api/recipes           │
└────────────────┬────────────────────────────┘
                 │
┌────────────────┴────────────────────────────┐
│  Named Volume: recipe-data                  │
│  - Persists SQLite database                 │
│  - Survives container restarts              │
└─────────────────────────────────────────────┘
```

---

## Services

### Backend Service

**Build context:** Root directory  
**Dockerfile:** `./Dockerfile`  
**Exposed port:** 3000 (optional, can be removed)  
**Internal port:** 3000  
**Volume mount:** `recipe-data:/app/data`  

**Environment variables:**
- `NODE_ENV=production`
- `DATABASE_PATH=/app/data/recipes.db`

**Health check:** `wget http://localhost:3000/api/recipes`  
**Restart policy:** `unless-stopped`  

### Frontend Service

**Build context:** `./frontend`  
**Dockerfile:** `./frontend/Dockerfile`  
**Exposed port:** 8080 → 80  
**Internal port:** 80  

**Dependencies:** Waits for backend health check to pass  
**Health check:** `wget http://localhost/health`  
**Restart policy:** `unless-stopped`  

**Nginx configuration:**
- Serves static files from `/usr/share/nginx/html`
- Proxies `/api/*` requests to `http://backend:3000`
- SPA fallback routing (all routes → `index.html`)
- Gzip compression for text files
- Security headers (X-Frame-Options, etc.)
- Aggressive caching for static assets (1 year)

---

## Usage

### Build and Start

```bash
# Build both services
docker compose build

# Start services in detached mode
docker compose up -d

# View logs
docker compose logs -f

# View logs for specific service
docker compose logs -f backend
docker compose logs -f frontend
```

### Access the Application

- **Frontend:** http://localhost:8080
- **Backend API (direct):** http://localhost:3000/api
- **Health check:** http://localhost:8080/health

### Stop and Remove

```bash
# Stop services
docker compose stop

# Stop and remove containers
docker compose down

# Stop, remove containers, and delete volumes (⚠️ deletes database!)
docker compose down -v
```

### Rebuild After Changes

```bash
# Rebuild specific service
docker compose build backend
docker compose build frontend

# Rebuild and restart
docker compose up -d --build

# Force complete rebuild (no cache)
docker compose build --no-cache
```

---

## Networking

### Internal Communication

Services communicate via the `recipe-network` bridge network:
- Frontend → Backend: `http://backend:3000/api/*`
- Docker DNS resolves service names automatically

### External Access

- Frontend exposed on host port **8080**
- Backend exposed on host port **3000** (optional, can be removed for security)

### API Routing

**From browser:**
```
User → http://localhost:8080/api/recipes
  ↓
nginx (frontend container) → proxy_pass to http://backend:3000/api/recipes
  ↓
Express API (backend container)
```

**Local development (npm run dev):**
```
User → http://localhost:5173/api/recipes
  ↓
Vite dev server → proxy to http://localhost:3000/api/recipes
  ↓
Express API (running separately)
```

---

## Volume Management

### Recipe Data Volume

**Name:** `recipe-data`  
**Mount point:** `/app/data` in backend container  
**Contents:** `recipes.db` (SQLite database)  

**Inspect volume:**
```bash
docker volume inspect recipe-manager_recipe-data
```

**Backup database:**
```bash
# Copy database from container to host
docker compose cp backend:/app/data/recipes.db ./backup-recipes.db

# Or access volume directly
docker run --rm -v recipe-manager_recipe-data:/data -v $(pwd):/backup alpine cp /data/recipes.db /backup/recipes.db
```

**Restore database:**
```bash
# Copy database from host to container
docker compose cp ./backup-recipes.db backend:/app/data/recipes.db

# Restart backend to apply changes
docker compose restart backend
```

---

## Environment Configuration

### Development vs Production

The current `docker-compose.yml` is configured for local testing. For production deployment:

1. **Remove backend port exposure** (line 12-13) — only frontend should be exposed
2. **Add Caddy reverse proxy** for HTTPS (see ARCHITECTURE.md)
3. **Configure environment-specific variables** via `.env` file
4. **Set up Litestream** for database backups

### Environment Variables

Create a `.env` file for customization:

```env
# Backend
NODE_ENV=production
DATABASE_PATH=/app/data/recipes.db

# Frontend (if needed)
COMPOSE_PROJECT_NAME=recipe-manager

# Ports
FRONTEND_PORT=8080
BACKEND_PORT=3000
```

Update `docker-compose.yml` to use variables:

```yaml
ports:
  - "${FRONTEND_PORT:-8080}:80"
```

---

## Troubleshooting

### Backend won't start

**Check logs:**
```bash
docker compose logs backend
```

**Common issues:**
- Migration fails → Check schema.sql syntax
- Port 3000 already in use → Change `BACKEND_PORT` in .env
- Database permission denied → Check volume permissions

**Solution:**
```bash
# Remove containers and volumes, rebuild
docker compose down -v
docker compose up -d --build
```

### Frontend can't reach backend

**Check network:**
```bash
docker compose exec frontend ping backend
```

**Check nginx config:**
```bash
docker compose exec frontend cat /etc/nginx/conf.d/default.conf
```

**Check backend health:**
```bash
docker compose exec backend wget -O- http://localhost:3000/api/recipes
```

### API returns 502 Bad Gateway

**Cause:** Nginx can't reach backend service  
**Check:**
1. Backend container is running: `docker compose ps`
2. Backend health check passing: `docker compose ps` (should show "healthy")
3. Network connectivity: `docker compose exec frontend ping backend`

**Fix:**
```bash
docker compose restart backend
docker compose restart frontend
```

### Database not persisting

**Cause:** Volume not mounted correctly  
**Check:**
```bash
docker volume ls | grep recipe
docker compose exec backend ls -la /app/data
```

**Fix:** Ensure `volumes:` section in docker-compose.yml matches Dockerfile paths

---

## Testing the Setup

### Validation Checklist

```bash
# 1. Validate docker-compose.yml syntax
docker compose config

# 2. Build services (should succeed without errors)
docker compose build

# 3. Start services
docker compose up -d

# 4. Check service status (both should be "healthy")
docker compose ps

# 5. Test backend API directly
curl http://localhost:3000/api/recipes

# 6. Test frontend
curl http://localhost:8080

# 7. Test API through frontend proxy
curl http://localhost:8080/api/recipes

# 8. Test in browser
open http://localhost:8080
```

### Expected Results

✅ Backend container running and healthy  
✅ Frontend container running and healthy  
✅ Database created at `/app/data/recipes.db`  
✅ Migrations applied (recipes, tags, recipe_tags tables exist)  
✅ Frontend accessible at http://localhost:8080  
✅ API returns empty recipes array: `{"success":true,"data":[]}`  
✅ Frontend UI loads without console errors  

---

## Next Steps

After verifying this setup works:

1. **Add Caddy reverse proxy** for HTTPS (production)
2. **Configure Litestream** for database backups
3. **Set up CI/CD** for automated builds
4. **Document deployment** to paje.ca

See `ROADMAP.md` for post-MVP features.

---

## Related Documentation

- `docker-backend.md` — Backend Dockerfile details
- `docker-frontend.md` — Frontend Dockerfile and nginx config
- `ARCHITECTURE.md` — Overall system architecture
- `docs/deployment.md` — Production deployment guide (TBD)
