266 lines
10 KiB
Markdown
266 lines
10 KiB
Markdown
# Docker Setup for GridPilot
|
||
|
||
This document describes the Docker setup for local development and production deployment of GridPilot.
|
||
|
||
## Quick Start
|
||
|
||
### Development
|
||
|
||
Start all services with hot-reloading:
|
||
```bash
|
||
npm run docker:dev:build
|
||
```
|
||
|
||
This will:
|
||
- Start PostgreSQL database on port 5432
|
||
- Start API on port 3001 (container port 3000, debugger 9229)
|
||
- Start Website on port 3000
|
||
- Enable hot-reloading for both apps
|
||
|
||
Access:
|
||
- Website: http://localhost:3000
|
||
- API: http://localhost:3001
|
||
- Database: localhost:5432
|
||
|
||
### Production
|
||
|
||
Start all services in production mode:
|
||
```bash
|
||
npm run docker:prod:build
|
||
```
|
||
|
||
This will:
|
||
- Build optimized Docker images
|
||
- Start PostgreSQL, Redis, API, Website, and Nginx
|
||
- Enable health checks, auto-restart, and resource limits
|
||
- Configure caching and performance optimizations
|
||
|
||
Access:
|
||
- Nginx (Website + API): http://localhost:80
|
||
|
||
## Available Commands
|
||
|
||
### Development
|
||
- `npm run docker:dev` - Start dev environment (alias of `docker:dev:up`)
|
||
- `npm run docker:dev:up` - Start dev environment
|
||
- `npm run docker:dev:postgres` - Start dev environment with `GRIDPILOT_API_PERSISTENCE=postgres`
|
||
- `npm run docker:dev:inmemory` - Start dev environment with `GRIDPILOT_API_PERSISTENCE=inmemory`
|
||
- `npm run docker:dev:build` - Rebuild and start
|
||
- `npm run docker:dev:restart` - Restart services
|
||
- `npm run docker:dev:ps` - Show service status
|
||
- `npm run docker:dev:down` - Stop services
|
||
- `npm run docker:dev:logs` - View logs
|
||
- `npm run docker:dev:clean` - Stop and remove volumes
|
||
|
||
### Production
|
||
- `npm run docker:prod` - Start prod environment
|
||
- `npm run docker:prod:build` - Rebuild and start
|
||
- `npm run docker:prod:down` - Stop services
|
||
- `npm run docker:prod:logs` - View logs
|
||
- `npm run docker:prod:clean` - Stop and remove volumes
|
||
|
||
### Testing (Docker)
|
||
The goal of Docker-backed tests is to catch *wiring* issues between Website ↔ API (wrong hostnames/ports/env vars, missing CORS for credentialed requests, etc.) in a deterministic environment.
|
||
|
||
- `npm run test:docker:website` - Start a dedicated test stack (ports `3100/3101`) and run Playwright smoke tests against it.
|
||
- Uses [`docker-compose.test.yml`](docker-compose.test.yml:1).
|
||
- Runs the Website in Docker and talks to an **API mock** container.
|
||
- Validates that pages render and that core API fetches succeed (hostname + CORS + routing).
|
||
|
||
Supporting scripts:
|
||
- `npm run docker:test:deps` - Install monorepo deps inside a reusable Docker volume (one-shot).
|
||
- `npm run docker:test:up` - Bring up the test stack.
|
||
- `npm run docker:test:wait` - Wait for `http://localhost:3100` and `http://localhost:3101/health` to be ready.
|
||
- `npm run docker:test:down` - Tear the test stack down (including volumes).
|
||
|
||
## Environment Variables
|
||
|
||
### “Mock vs Real” (Website & API)
|
||
|
||
There is **no** `AUTOMATION_MODE` equivalent for the Website/API runtime.
|
||
|
||
- **Website “mock vs real”** is controlled purely by *which API base URL you point it at* via [`getWebsiteApiBaseUrl()`](apps/website/lib/config/apiBaseUrl.ts:6):
|
||
- Browser calls use `NEXT_PUBLIC_API_BASE_URL`
|
||
- Server/Next.js calls use `API_BASE_URL ?? NEXT_PUBLIC_API_BASE_URL`
|
||
|
||
- **API “mock vs real”** is controlled by API runtime env:
|
||
- Persistence: `GRIDPILOT_API_PERSISTENCE=postgres|inmemory` in [`AppModule`](apps/api/src/app.module.ts:25)
|
||
- Optional bootstrapping: `GRIDPILOT_API_BOOTSTRAP=0|1` in [`AppModule`](apps/api/src/app.module.ts:35)
|
||
|
||
Practical presets:
|
||
- **Website + real API (Docker dev)**: `npm run docker:dev:build` (Website `3000`, API `3001`, Postgres required).
|
||
- Website browser → API: `NEXT_PUBLIC_API_BASE_URL=http://localhost:3001`
|
||
- Website container → API container: `API_BASE_URL=http://api:3000`
|
||
- **Website + mock API (Docker smoke)**: `npm run test:docker:website` (Website `3100`, API mock `3101`).
|
||
- API mock is defined inline in [`docker-compose.test.yml`](docker-compose.test.yml:24)
|
||
- Website browser → API mock: `NEXT_PUBLIC_API_BASE_URL=http://localhost:3101`
|
||
- Website container → API mock container: `API_BASE_URL=http://api:3000`
|
||
|
||
### Website ↔ API Connection
|
||
|
||
The website talks to the API via `fetch()` in [`BaseApiClient`](apps/website/lib/api/base/BaseApiClient.ts:11), and it always includes cookies (`credentials: 'include'`). That means:
|
||
|
||
- The **browser** must be pointed at a host-accessible API URL via `NEXT_PUBLIC_API_BASE_URL`
|
||
- The **server** (Next.js / Node) must be pointed at a container-network API URL via `API_BASE_URL` (when running in Docker)
|
||
|
||
The single source of truth for “what base URL should I use?” is [`getWebsiteApiBaseUrl()`](apps/website/lib/config/apiBaseUrl.ts:6):
|
||
- Browser: reads `NEXT_PUBLIC_API_BASE_URL`
|
||
- Server: reads `API_BASE_URL ?? NEXT_PUBLIC_API_BASE_URL`
|
||
- In Docker/CI/test: throws if missing (no silent localhost fallback)
|
||
|
||
#### Dev Docker defaults (docker-compose.dev.yml)
|
||
- Website: `http://localhost:3000`
|
||
- API: `http://localhost:3001` (maps to container `api:3000`)
|
||
- `NEXT_PUBLIC_API_BASE_URL=http://localhost:3001` (browser → host port)
|
||
- `API_BASE_URL=http://api:3000` (website container → api container)
|
||
|
||
#### Test Docker defaults (docker-compose.test.yml)
|
||
This stack is intended for deterministic smoke tests and uses different host ports to avoid colliding with `docker:dev`:
|
||
|
||
- Website: `http://localhost:3100`
|
||
- API mock: `http://localhost:3101` (maps to container `api:3000`)
|
||
- `NEXT_PUBLIC_API_BASE_URL=http://localhost:3101` (browser → host port)
|
||
- `API_BASE_URL=http://api:3000` (website container → api container)
|
||
|
||
Important: the test stack’s API is a mock server defined inline in [`docker-compose.test.yml`](docker-compose.test.yml:24). It exists to validate Website ↔ API wiring, not domain correctness.
|
||
|
||
#### Troubleshooting
|
||
- If `docker:dev` is running, use `npm run docker:dev:down` before `npm run test:docker:website` to avoid port conflicts.
|
||
- If Docker volumes get stuck, run `npm run docker:test:down` (it uses `--remove-orphans` + `rm -f`).
|
||
|
||
### API “Real vs In-Memory” Mode
|
||
|
||
The API can now be run either:
|
||
- **postgres**: loads [`DatabaseModule`](apps/api/src/domain/database/DatabaseModule.ts:1) (requires Postgres)
|
||
- **inmemory**: does not load `DatabaseModule` (no Postgres required)
|
||
|
||
Control it with:
|
||
- `GRIDPILOT_API_PERSISTENCE=postgres|inmemory` (defaults to `postgres` if `DATABASE_URL` is set, otherwise `inmemory`)
|
||
- Optional: `GRIDPILOT_API_BOOTSTRAP=0` to skip [`BootstrapModule`](apps/api/src/domain/bootstrap/BootstrapModule.ts:1)
|
||
|
||
### Development (.env.development)
|
||
Copy and customize as needed. Default values work out of the box.
|
||
|
||
### Production (.env.production)
|
||
**IMPORTANT**: Update these before deploying:
|
||
- Database credentials (`POSTGRES_PASSWORD`, `DATABASE_URL`)
|
||
- Website/API URLs (`NEXT_PUBLIC_API_BASE_URL`, `NEXT_PUBLIC_SITE_URL`)
|
||
- Vercel KV credentials (`KV_REST_API_URL`, `KV_REST_API_TOKEN`) (required for production email signups/rate limit)
|
||
|
||
## Architecture
|
||
|
||
### Development Setup
|
||
- Hot-reloading enabled via volume mounts
|
||
- Source code changes reflect immediately
|
||
- Database persisted in named volume
|
||
- Debug port exposed for API (9229)
|
||
|
||
### Production Setup
|
||
- Multi-stage builds for optimized images
|
||
- Only production dependencies included
|
||
- Nginx reverse proxy for both services
|
||
- Health checks for all services
|
||
- Auto-restart on failure
|
||
|
||
## Docker Services
|
||
|
||
### API (NestJS)
|
||
- Dev: `apps/api/Dockerfile.dev`
|
||
- Prod: `apps/api/Dockerfile.prod`
|
||
- Port: 3000
|
||
- Debug: 9229 (dev only)
|
||
|
||
### Website (Next.js)
|
||
- Dev: `apps/website/Dockerfile.dev`
|
||
- Prod: `apps/website/Dockerfile.prod`
|
||
- Port: 3001 (dev), 3000 (prod)
|
||
|
||
### Database (PostgreSQL)
|
||
- Image: postgres:15-alpine
|
||
- Port: 5432 (internal)
|
||
- Data: Persisted in Docker volume
|
||
- Optimized with performance tuning parameters
|
||
|
||
### Redis (Production only)
|
||
- Image: redis:7-alpine
|
||
- Port: 6379 (internal)
|
||
- Configured with:
|
||
- LRU eviction policy
|
||
- 512MB max memory
|
||
- AOF persistence
|
||
- Password protection
|
||
|
||
### Nginx (Production only)
|
||
- Reverse proxy for website + API
|
||
- Features:
|
||
- Rate limiting (API: 10r/s, General: 30r/s)
|
||
- Security headers (XSS, CSP, Frame-Options)
|
||
- Gzip compression
|
||
- Static asset caching
|
||
- Connection pooling
|
||
- Request buffering
|
||
- Port: 80, 443
|
||
|
||
## Troubleshooting
|
||
|
||
### Services won't start
|
||
```bash
|
||
# Clean everything and rebuild
|
||
npm run docker:dev:clean
|
||
npm run docker:dev:build
|
||
```
|
||
|
||
### Hot-reloading not working
|
||
Check that volume mounts are correct in docker-compose.dev.yml
|
||
|
||
### Database connection issues
|
||
Ensure DATABASE_URL in .env matches the database service configuration
|
||
|
||
### Check logs
|
||
```bash
|
||
# All services
|
||
npm run docker:dev:logs
|
||
|
||
# Specific service
|
||
docker-compose -f docker-compose.dev.yml logs -f api
|
||
docker-compose -f docker-compose.dev.yml logs -f website
|
||
docker-compose -f docker-compose.dev.yml logs -f db
|
||
```
|
||
|
||
## Tips
|
||
|
||
1. **First time setup**: Use `docker:dev:build` to ensure images are built
|
||
2. **Clean slate**: Use `docker:dev:clean` to remove all data and start fresh
|
||
3. **Production testing**: Test prod setup locally before deploying
|
||
4. **Database access**: Use any PostgreSQL client with credentials from .env file
|
||
5. **Debugging**: Attach debugger to port 9229 for API debugging
|
||
|
||
## Production Deployment
|
||
|
||
Before deploying to production:
|
||
|
||
1. Update `.env.production` with real credentials
|
||
2. Configure SSL certificates in `nginx/ssl/`
|
||
3. Update Nginx configuration for HTTPS
|
||
4. Set proper domain names in environment variables
|
||
5. Consider using Docker secrets for sensitive data
|
||
|
||
## File Structure
|
||
|
||
```
|
||
.
|
||
├── docker-compose.dev.yml # Development orchestration
|
||
├── docker-compose.prod.yml # Production orchestration
|
||
├── .env.development # Dev environment variables
|
||
├── .env.production # Prod environment variables
|
||
├── apps/
|
||
│ ├── api/
|
||
│ │ ├── Dockerfile.dev # API dev image
|
||
│ │ ├── Dockerfile.prod # API prod image
|
||
│ │ └── .dockerignore
|
||
│ └── website/
|
||
│ ├── Dockerfile.dev # Website dev image
|
||
│ ├── Dockerfile.prod # Website prod image
|
||
│ └── .dockerignore
|
||
└── nginx/
|
||
└── nginx.conf # Nginx configuration |