Setup initial project
This commit is contained in:
commit
db79af06f7
119 changed files with 20761 additions and 0 deletions
331
docs/instructions/docker.md
Normal file
331
docs/instructions/docker.md
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
# Docker & Docker Compose Best Practices — MeshiTrack
|
||||
|
||||
> Instruction file for containerization and local development environment.
|
||||
|
||||
## Docker Compose Architecture
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌───────────────┐ ┌──────────┐
|
||||
│ web:3000 │────→│ api:3001 │────→│ mongodb │
|
||||
│ (Next.js) │ │ (NestJS) │ │ :27017 │
|
||||
└─────────────┘ └───────┬───────┘ └──────────┘
|
||||
│
|
||||
┌───────▼───────┐
|
||||
│ keycloak │
|
||||
│ :8080 │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
## docker-compose.yml
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:7
|
||||
container_name: meshitrack-mongodb
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '27017:27017'
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: meshitrack
|
||||
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:-devpassword}
|
||||
MONGO_INITDB_DATABASE: meshitrack
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
- ./mongo/init-replica.js:/docker-entrypoint-initdb.d/init-replica.js:ro
|
||||
command: ['--replSet', 'rs0', '--bind_ip_all']
|
||||
healthcheck:
|
||||
test: ['CMD', 'mongosh', '--eval', "db.adminCommand('ping')"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:24.0
|
||||
container_name: meshitrack-keycloak
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '8080:8080'
|
||||
environment:
|
||||
KC_DB: dev-mem
|
||||
KEYCLOAK_ADMIN: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:-admin}
|
||||
command: start-dev --import-realm
|
||||
volumes:
|
||||
- ./keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json:ro
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
'CMD-SHELL',
|
||||
"exec 3<>/dev/tcp/127.0.0.1/8080; echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3; cat <&3 | grep -q '200'",
|
||||
]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
api:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile.api
|
||||
target: development
|
||||
container_name: meshitrack-api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '3001:3001'
|
||||
depends_on:
|
||||
mongodb:
|
||||
condition: service_healthy
|
||||
keycloak:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
PORT: 3001
|
||||
MONGODB_URI: mongodb://meshitrack:${MONGO_PASSWORD:-devpassword}@mongodb:27017/meshitrack?authSource=admin&replicaSet=rs0
|
||||
KEYCLOAK_URL: http://keycloak:8080
|
||||
KEYCLOAK_REALM: meshitrack
|
||||
KEYCLOAK_CLIENT_ID: meshitrack-api
|
||||
volumes:
|
||||
- ../packages/api/src:/app/packages/api/src:ro # Hot reload
|
||||
- ../packages/shared/src:/app/packages/shared/src:ro
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile.web
|
||||
target: development
|
||||
container_name: meshitrack-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '3000:3000'
|
||||
depends_on:
|
||||
- api
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
NEXT_PUBLIC_API_URL: http://localhost:3001/api/v1
|
||||
NEXT_PUBLIC_KEYCLOAK_URL: http://localhost:8080
|
||||
NEXT_PUBLIC_KEYCLOAK_REALM: meshitrack
|
||||
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID: meshitrack-web
|
||||
volumes:
|
||||
- ../packages/web/src:/app/packages/web/src:ro
|
||||
|
||||
# Dev-only services
|
||||
mongo-express:
|
||||
image: mongo-express
|
||||
container_name: meshitrack-mongo-express
|
||||
ports:
|
||||
- '8081:8081'
|
||||
depends_on:
|
||||
mongodb:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
ME_CONFIG_MONGODB_ADMINUSERNAME: meshitrack
|
||||
ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD:-devpassword}
|
||||
ME_CONFIG_MONGODB_URL: mongodb://meshitrack:${MONGO_PASSWORD:-devpassword}@mongodb:27017/
|
||||
profiles:
|
||||
- dev
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
```
|
||||
|
||||
## Dockerfile Best Practices
|
||||
|
||||
### Multi-stage builds
|
||||
|
||||
```dockerfile
|
||||
# docker/Dockerfile.api
|
||||
|
||||
# ---- Base ----
|
||||
FROM node:20-alpine AS base
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
|
||||
# ---- Dependencies ----
|
||||
FROM base AS dependencies
|
||||
COPY package.json package-lock.json ./
|
||||
COPY packages/api/package.json ./packages/api/
|
||||
COPY packages/shared/package.json ./packages/shared/
|
||||
RUN npm ci --workspace=packages/shared --workspace=packages/api
|
||||
|
||||
# ---- Build ----
|
||||
FROM dependencies AS build
|
||||
COPY packages/shared/ ./packages/shared/
|
||||
COPY packages/api/ ./packages/api/
|
||||
COPY tsconfig.base.json ./
|
||||
RUN npm run build --workspace=packages/shared
|
||||
RUN npm run build --workspace=packages/api
|
||||
|
||||
# ---- Production ----
|
||||
FROM base AS production
|
||||
COPY --from=build /app/packages/api/dist ./packages/api/dist
|
||||
COPY --from=build /app/packages/shared/dist ./packages/shared/dist
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/packages/api/package.json ./packages/api/
|
||||
COPY --from=build /app/packages/shared/package.json ./packages/shared/
|
||||
COPY --from=build /app/package.json ./
|
||||
|
||||
ENV NODE_ENV=production
|
||||
EXPOSE 3001
|
||||
CMD ["node", "packages/api/dist/main.js"]
|
||||
|
||||
# ---- Development ----
|
||||
FROM dependencies AS development
|
||||
COPY packages/shared/ ./packages/shared/
|
||||
COPY packages/api/ ./packages/api/
|
||||
COPY tsconfig.base.json ./
|
||||
RUN npm run build --workspace=packages/shared
|
||||
EXPOSE 3001
|
||||
CMD ["npm", "run", "dev", "--workspace=packages/api"]
|
||||
```
|
||||
|
||||
### Key principles
|
||||
|
||||
1. **Layer ordering**: Copy `package.json` files first, then `npm ci`, then source code. This ensures dependency layers are cached unless `package.json` changes.
|
||||
|
||||
2. **Multi-stage targets**: Use `--target=development` for dev (with hot reload), `--target=production` for deploy (minimal image).
|
||||
|
||||
3. **Alpine images**: Use `node:20-alpine` for smaller images (~180MB vs ~1GB).
|
||||
|
||||
4. **.dockerignore**: Always include to prevent sending unnecessary files to the build context:
|
||||
```
|
||||
node_modules
|
||||
.git
|
||||
.next
|
||||
dist
|
||||
*.md
|
||||
docs/
|
||||
.env*
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### .env.example (committed to repo)
|
||||
|
||||
```env
|
||||
# MongoDB
|
||||
MONGO_PASSWORD=devpassword
|
||||
|
||||
# Keycloak
|
||||
KC_ADMIN_PASSWORD=admin
|
||||
|
||||
# API
|
||||
API_PORT=3001
|
||||
MONGODB_URI=mongodb://meshitrack:devpassword@localhost:27017/meshitrack?authSource=admin
|
||||
|
||||
# Web
|
||||
NEXT_PUBLIC_API_URL=http://localhost:3001/api/v1
|
||||
NEXT_PUBLIC_KEYCLOAK_URL=http://localhost:8080
|
||||
NEXT_PUBLIC_KEYCLOAK_REALM=meshitrack
|
||||
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=meshitrack-web
|
||||
|
||||
# LLM (Phase 6)
|
||||
LLM_PROVIDER_TYPE=noop
|
||||
# OPENAI_API_KEY=sk-...
|
||||
# LLM_MONTHLY_BUDGET_USD=20.00
|
||||
```
|
||||
|
||||
### .env (gitignored, developer-specific)
|
||||
|
||||
Never commit `.env`. Each developer copies `.env.example` to `.env` and fills in secrets.
|
||||
|
||||
### In Docker Compose, use variable substitution
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
MONGO_PASSWORD: ${MONGO_PASSWORD:-devpassword} # Fallback for dev
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
Every service should have a health check so `depends_on: condition: service_healthy` works:
|
||||
|
||||
```yaml
|
||||
# MongoDB
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# API (requires /api/v1/health endpoint in code)
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:3001/api/v1/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
```
|
||||
|
||||
## Volume Management
|
||||
|
||||
### Named volumes for persistence
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
mongo-data: # Survives container recreations
|
||||
```
|
||||
|
||||
### Bind mounts for hot reload in development
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ../packages/api/src:/app/packages/api/src:ro # Read-only for safety
|
||||
```
|
||||
|
||||
Use `:ro` (read-only) for source code mounts — the container shouldn't modify your source files.
|
||||
|
||||
## MongoDB Replica Set for Transactions
|
||||
|
||||
MongoDB transactions require a replica set. For local development, initialize a single-node replica:
|
||||
|
||||
```javascript
|
||||
// docker/mongo/init-replica.js
|
||||
// This runs on first start via /docker-entrypoint-initdb.d/
|
||||
try {
|
||||
rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'mongodb:27017' }] });
|
||||
} catch (e) {
|
||||
if (e.codeName !== 'AlreadyInitialized') throw e;
|
||||
}
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
# Start with dev tools (mongo-express)
|
||||
docker compose --profile dev up -d
|
||||
|
||||
# View logs
|
||||
docker compose logs -f api
|
||||
|
||||
# Rebuild after dependency changes
|
||||
docker compose build --no-cache api
|
||||
|
||||
# Reset database
|
||||
docker compose down -v # -v removes volumes
|
||||
docker compose up -d
|
||||
|
||||
# Enter a running container
|
||||
docker compose exec api sh
|
||||
docker compose exec mongodb mongosh -u meshitrack -p devpassword
|
||||
|
||||
# Backup MongoDB
|
||||
docker compose exec mongodb mongodump --uri="mongodb://meshitrack:devpassword@localhost:27017/meshitrack?authSource=admin" --archive | gzip > backup-$(date +%Y%m%d).gz
|
||||
```
|
||||
|
||||
## Production Considerations
|
||||
|
||||
- Use separate `docker-compose.prod.yml` with:
|
||||
- `target: production` for API and web builds
|
||||
- No bind mounts
|
||||
- No dev services (mongo-express)
|
||||
- Proper resource limits
|
||||
- External volumes for MongoDB data
|
||||
- Log drivers configured
|
||||
- Consider adding:
|
||||
- **Traefik** or **nginx** as reverse proxy with TLS
|
||||
- **Watchtower** for auto-updating container images
|
||||
- Automated backup cron container for MongoDB
|
||||
Loading…
Add table
Add a link
Reference in a new issue