Docs /deployment/docker

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 .dockerignore file
  • 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


Ready for orchestration? Continue with Docker Compose โ†’

Reading this with an agent? /docs/deployment/docker.md serves the raw markdown.

All docs