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
# 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
# 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
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
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
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
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
# 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
# Force rebuild (no layer caching)
docker compose build --no-cache
# Specific service
docker compose build --no-cache api
Parallel Builds
# Build all services in parallel
docker compose build --parallel
๐๏ธ Development vs Production
Development Dockerfile
Location: docker/web/Dockerfile.dev
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
# 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:
# โ
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:
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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:
# 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
.dockerignorefile - Multi-stage builds
Slow Builds
Problem: Builds take too long
Solutions:
# 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:
# 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)
# 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
- Production: Production deployment guide
- Environment: Environment configuration
- Monitoring: Monitoring and logging
Ready for orchestration? Continue with Docker Compose โ