# Docker Setup

> Docker configuration, Dockerfiles, and best practices

## 📋 Overview

Izri uses Docker for consistent development and production environments. Each service has its own optimized Dockerfile using multi-stage builds to minimize image size and improve security.

## 🐳 Docker Images

### Service Images

| Service | Base Image | Size (approx) | Purpose |
|---------|-----------|---------------|---------|
| **web** | node:20-bookworm-slim | ~350MB | React Router frontend |
| **api** | node:20-alpine | ~200MB | Hono + tRPC backend |
| **ai-service** | python:3.11-slim | ~800MB | FastAPI AI service |
| **postgres** | postgres:15 | ~400MB | Database |
| **redis** | redis:7-alpine | ~30MB | Cache |

## 📦 Web Application Dockerfile

**Location**: `docker/web/Dockerfile`

### Multi-Stage Build

```dockerfile
# Stage 1: Base image with pnpm
FROM node:20-bookworm-slim AS base
RUN corepack enable && corepack prepare pnpm@9.12.2 --activate

# Stage 2: Install dependencies
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
COPY apps/web/package.json apps/web/package.json
COPY packages/database/package.json packages/database/package.json
COPY packages/shared/package.json packages/shared/package.json
RUN pnpm install -r --frozen-lockfile

# Stage 3: Build application
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN apt-get update -y && apt-get install -y openssl
RUN pnpm --filter @izri/database generate
WORKDIR /app/apps/web
RUN pnpm build

# Stage 4: Production runner
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN apt-get update -y && apt-get install -y openssl
RUN addgroup --system --gid 1001 nodejs \
  && adduser --system --uid 1001 reactuser

COPY --from=builder --chown=reactuser:nodejs /app/apps/web/build ./apps/web/build
COPY --from=builder --chown=reactuser:nodejs /app/apps/web/public ./apps/web/public
COPY --from=deps --chown=reactuser:nodejs /app/node_modules ./node_modules

USER reactuser
EXPOSE 3000
ENV PORT=3000 HOST=0.0.0.0
CMD ["pnpm", "start"]
```

### Key Features

**Multi-stage builds**:

- Separate stages for dependencies, building, and runtime
- Only production artifacts in final image
- Smaller image size (~350MB vs ~1GB)

**Security**:

- Non-root user (`reactuser`)
- Minimal base image
- No dev dependencies in production

**Caching optimization**:

- Dependencies cached separately
- Workspace manifests copied first
- Layers ordered by change frequency

### Building the Web Image

```bash
# From project root
docker build -f docker/web/Dockerfile -t izri-web .

# With build args
docker build \
  --build-arg NODE_ENV=production \
  -f docker/web/Dockerfile \
  -t izri-web:latest \
  .
```

## 🔧 API Server Dockerfile

**Location**: `docker/api/Dockerfile`

### Optimized for Node.js

```dockerfile
FROM node:20-alpine AS base
RUN corepack enable && corepack prepare pnpm@9.12.2 --activate

FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
COPY apps/api/package.json apps/api/package.json
COPY packages/database/package.json packages/database/package.json
COPY packages/shared/package.json packages/shared/package.json
COPY packages/trpc/package.json packages/trpc/package.json
COPY packages/auth/package.json packages/auth/package.json
RUN pnpm install -r --frozen-lockfile

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm --filter @izri/database generate
RUN pnpm --filter @izri/shared build
RUN pnpm --filter @izri/database build
RUN pnpm --filter @izri/trpc build
RUN pnpm --filter @izri/auth build
RUN pnpm --filter @izri/api build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs \
  && adduser --system --uid 1001 nodejs

COPY --from=builder /app/apps/api/dist ./apps/api/dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/packages/database/lib ./packages/database/lib
COPY --from=builder /app/packages/shared/lib ./packages/shared/lib
COPY --from=builder /app/packages/trpc/lib ./packages/trpc/lib
COPY --from=builder /app/packages/auth/lib ./packages/auth/lib

USER nodejs
EXPOSE 4000
ENV PORT=4000
CMD ["node", "apps/api/dist/index.js"]
```

### Key Features

**Alpine-based**:

- Smaller base image (~200MB final)
- Essential libraries only (`libc6-compat`)
- Security-focused minimal attack surface

**Workspace dependencies**:

- Builds all required packages in order
- Copies only compiled `lib/` directories
- Maintains package.json for resolution

### Building the API Image

```bash
docker build -f docker/api/Dockerfile -t izri-api .

# Check image size
docker images izri-api
```

## 🤖 AI Service Dockerfile

**Location**: `docker/ai-service/Dockerfile`

### Python FastAPI Service

```dockerfile
FROM python:3.11-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements first for caching
COPY packages/ai-service/requirements.txt ./packages/ai-service/requirements.txt
RUN pip install --no-cache-dir -r packages/ai-service/requirements.txt

# Copy service code
COPY packages/ai-service ./packages/ai-service

# Non-root user
RUN useradd --create-home --shell /bin/bash app
USER app

EXPOSE 8000
CMD ["uvicorn", "packages.ai-service.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

### Key Features

**Python best practices**:

- Slim base image
- Separate requirements install for caching
- No cache directory (`--no-cache-dir`)

**Security**:

- Non-root user (`app`)
- Minimal system packages
- Clean apt lists

### Building the AI Service Image

```bash
docker build -f docker/ai-service/Dockerfile -t izri-ai .

# Run standalone
docker run -p 8000:8000 \
  -e OPENAI_API_KEY=your-key \
  izri-ai
```

## 🔨 Build Commands

### Build All Services

```bash
# From project root
docker compose build

# Build specific service
docker compose build web
docker compose build api
docker compose build ai-service
```

### Build with No Cache

```bash
# Force rebuild (no layer caching)
docker compose build --no-cache

# Specific service
docker compose build --no-cache api
```

### Parallel Builds

```bash
# Build all services in parallel
docker compose build --parallel
```

## 🏗️ Development vs Production

### Development Dockerfile

**Location**: `docker/web/Dockerfile.dev`

```dockerfile
FROM node:20-bookworm-slim

RUN corepack enable && corepack prepare pnpm@9.12.2 --activate

WORKDIR /app

# Install dependencies
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
RUN pnpm install

# Copy source (use volumes in docker-compose)
COPY . .

EXPOSE 5173
CMD ["pnpm", "dev:web"]
```

**Key differences**:

- Single stage (no optimization needed)
- Includes dev dependencies
- Uses volumes for hot reload
- Runs dev server instead of production build

### Using Development Images

```bash
# docker-compose.dev.yml
services:
  web:
    build:
      context: .
      dockerfile: docker/web/Dockerfile.dev
    volumes:
      - ./apps/web:/app/apps/web
      - /app/node_modules
    environment:
      - NODE_ENV=development
```

## 🎯 Optimization Strategies

### 1. Layer Caching

**Order layers by change frequency**:

```dockerfile
# ✅ Good: Least frequently changed first
COPY package.json ./
RUN pnpm install
COPY src ./src
RUN pnpm build

# ❌ Bad: Everything changes together
COPY . .
RUN pnpm install && pnpm build
```

### 2. Multi-Stage Builds

**Remove build artifacts**:

```dockerfile
# Builder stage has all dev dependencies
FROM node:20 AS builder
RUN pnpm install
RUN pnpm build

# Runtime stage only has production needs
FROM node:20-alpine AS runner
COPY --from=builder /app/dist ./dist
```

### 3. Minimize Image Size

```dockerfile
# Use alpine variants
FROM node:20-alpine  # ~120MB vs node:20 ~900MB

# Remove unnecessary files
RUN rm -rf /var/lib/apt/lists/*

# Use --no-cache-dir with pip
RUN pip install --no-cache-dir -r requirements.txt

# Copy only what's needed
COPY --from=builder /app/dist ./dist
# Don't copy src, tests, etc.
```

### 4. Security Hardening

```dockerfile
# Non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser
USER appuser

# Read-only filesystem (when possible)
CMD ["node", "--frozen-intrinsics", "index.js"]

# No shell access
ENTRYPOINT ["node"]
CMD ["index.js"]
```

## 🔍 Inspecting Images

### Image Details

```bash
# List images
docker images

# Inspect image
docker inspect izri-api

# View layers
docker history izri-api

# Check size per layer
docker history izri-api --no-trunc --format "{{.Size}}\t{{.CreatedBy}}"
```

### Analyzing Image Size

```bash
# Use dive tool
brew install dive
dive izri-api

# Or docker built-in
docker image inspect izri-api --format='{{.Size}}' | numfmt --to=iec
```

## 🧪 Testing Images Locally

### Run Individual Services

```bash
# Web app
docker run -p 3000:3000 \
  -e DATABASE_URL=postgresql://postgres:password@host.docker.internal:5433/izri \
  izri-web

# API server
docker run -p 4000:4000 \
  -e DATABASE_URL=postgresql://postgres:password@host.docker.internal:5433/izri \
  izri-api

# AI service
docker run -p 8000:8000 \
  -e OPENAI_API_KEY=your-key \
  izri-ai
```

### Interactive Shell

```bash
# Start container with shell
docker run -it --entrypoint /bin/sh izri-api

# Exec into running container
docker exec -it <container-id> /bin/sh
```

### Health Checks

```bash
# Check API health
curl http://localhost:4000/health

# Check AI service
curl http://localhost:8000/health
```

## 🛠️ Troubleshooting

### Build Fails: Dependencies

**Problem**: `pnpm install` fails

**Solution**:

```bash
# Clear Docker build cache
docker builder prune

# Rebuild without cache
docker compose build --no-cache
```

### Image Too Large

**Problem**: Image is >1GB

**Solutions**:

- Use Alpine base images
- Remove dev dependencies in final stage
- Use `.dockerignore` file
- Multi-stage builds

### Slow Builds

**Problem**: Builds take too long

**Solutions**:

```bash
# Enable BuildKit
export DOCKER_BUILDKIT=1

# Use layer caching
docker build --cache-from izri-api:latest

# Parallel builds
docker compose build --parallel
```

### Permission Errors

**Problem**: Files owned by root

**Solution**:

```dockerfile
# Set correct ownership
COPY --chown=appuser:appgroup /app/dist ./dist

# Or change after copy
RUN chown -R appuser:appgroup /app
```

## 📝 .dockerignore

**Location**: `.dockerignore` (project root)

```dockerignore
# Node
node_modules
npm-debug.log
yarn-error.log
.pnpm-store

# Build outputs
dist
build
.turbo
*.tsbuildinfo

# Development
.env.local
.env.development

# IDE
.vscode
.idea

# Git
.git
.gitignore

# Tests
coverage
*.test.ts
__tests__

# Documentation
docs
*.md
```

## 🔗 Related Documentation

- **Docker Compose**: [Multi-service orchestration](./docker-compose.md)
- **Production**: [Production deployment guide](./production.md)
- **Environment**: [Environment configuration](./environment-setup.md)
- **Monitoring**: [Monitoring and logging](./monitoring.md)

---

*Ready for orchestration? Continue with [Docker Compose](./docker-compose.md) →*
