fix: add Dockerfiles and fix docker-compose build configuration

- Add shared Dockerfile.service for all 7 NestJS microservices using
  multi-stage build with pnpm workspace support
- Add Dockerfile for web-admin (Next.js standalone output)
- Add .dockerignore files for root and web-admin
- Fix docker-compose.yml: use monorepo root as build context with
  SERVICE_NAME build arg instead of per-service Dockerfiles
- Fix postgres/redis missing network config (services couldn't reach them)
- Use .env variables for DB credentials instead of hardcoded values
- Add JWT_REFRESH_SECRET and REDIS_URL to services that were missing them
- Add DB init script volume mount for postgres
- Remove deprecated version: '3.8' from all compose files
- Add output: 'standalone' to next.config.js for optimized Docker builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-19 04:31:23 -08:00
parent 8116f17fd0
commit 9120f4927e
8 changed files with 267 additions and 53 deletions

46
.dockerignore Normal file
View File

@ -0,0 +1,46 @@
node_modules
.next
dist
build
out
coverage
.turbo
.git
.gitignore
.claude
.vscode
.idea
# Flutter (not needed for backend builds)
it0_app
# Docker
deploy
docker-compose*.yml
Dockerfile*
.dockerignore
# Docs
docs
*.md
# Environment
.env
.env.*
!.env.example
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Models
models
# Python cache
__pycache__
*.pyc
venv
.venv

79
Dockerfile.service Normal file
View File

@ -0,0 +1,79 @@
# Shared multi-stage Dockerfile for all NestJS microservices
# Usage: docker build --build-arg SERVICE_NAME=auth-service --build-arg SERVICE_PORT=3001 -f Dockerfile.service .
# ===== Build Stage =====
FROM node:18-alpine AS builder
RUN corepack enable
WORKDIR /app
# Copy workspace configuration and lockfile (for dependency caching)
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json tsconfig.base.json ./
# Copy all package.json files first (Docker layer cache: deps reinstall only when these change)
COPY packages/shared/common/package.json packages/shared/common/
COPY packages/shared/database/package.json packages/shared/database/
COPY packages/shared/events/package.json packages/shared/events/
COPY packages/shared/proto/package.json packages/shared/proto/
COPY packages/shared/testing/package.json packages/shared/testing/
COPY packages/services/auth-service/package.json packages/services/auth-service/
COPY packages/services/agent-service/package.json packages/services/agent-service/
COPY packages/services/ops-service/package.json packages/services/ops-service/
COPY packages/services/inventory-service/package.json packages/services/inventory-service/
COPY packages/services/monitor-service/package.json packages/services/monitor-service/
COPY packages/services/comm-service/package.json packages/services/comm-service/
COPY packages/services/audit-service/package.json packages/services/audit-service/
# Install all dependencies (cached unless package.json changes)
RUN pnpm install --frozen-lockfile
# Copy source code and tsconfig files
COPY packages/shared/ packages/shared/
COPY packages/services/ packages/services/
# Build all shared libraries + target service
ARG SERVICE_NAME
RUN pnpm turbo build --filter='./packages/shared/*' --filter=@it0/${SERVICE_NAME}
# ===== Production Stage =====
FROM node:18-alpine
RUN corepack enable
WORKDIR /app
# Copy workspace config and all package.jsons (required for pnpm workspace resolution)
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
COPY --from=builder /app/packages/shared/common/package.json packages/shared/common/
COPY --from=builder /app/packages/shared/database/package.json packages/shared/database/
COPY --from=builder /app/packages/shared/events/package.json packages/shared/events/
COPY --from=builder /app/packages/shared/proto/package.json packages/shared/proto/
COPY --from=builder /app/packages/shared/testing/package.json packages/shared/testing/
COPY --from=builder /app/packages/services/auth-service/package.json packages/services/auth-service/
COPY --from=builder /app/packages/services/agent-service/package.json packages/services/agent-service/
COPY --from=builder /app/packages/services/ops-service/package.json packages/services/ops-service/
COPY --from=builder /app/packages/services/inventory-service/package.json packages/services/inventory-service/
COPY --from=builder /app/packages/services/monitor-service/package.json packages/services/monitor-service/
COPY --from=builder /app/packages/services/comm-service/package.json packages/services/comm-service/
COPY --from=builder /app/packages/services/audit-service/package.json packages/services/audit-service/
# Install production dependencies only
RUN pnpm install --frozen-lockfile --prod
# Copy built shared libraries (dist/ produced by tsc)
COPY --from=builder /app/packages/shared/common/dist packages/shared/common/dist/
COPY --from=builder /app/packages/shared/database/dist packages/shared/database/dist/
COPY --from=builder /app/packages/shared/events/dist packages/shared/events/dist/
COPY --from=builder /app/packages/shared/proto/dist packages/shared/proto/dist/
# Copy target service build output
ARG SERVICE_NAME
COPY --from=builder /app/packages/services/${SERVICE_NAME}/dist packages/services/${SERVICE_NAME}/dist/
WORKDIR /app/packages/services/${SERVICE_NAME}
ARG SERVICE_PORT=3000
EXPOSE ${SERVICE_PORT}
CMD ["node", "dist/main"]

View File

@ -1,5 +1,3 @@
version: '3.8'
# SSL overlay — adds Nginx reverse proxy + Certbot for Let's Encrypt # SSL overlay — adds Nginx reverse proxy + Certbot for Let's Encrypt
# Usage: docker compose -f docker-compose.yml -f docker-compose.ssl.yml up -d # Usage: docker compose -f docker-compose.yml -f docker-compose.ssl.yml up -d

View File

@ -1,5 +1,3 @@
version: '3.8'
# GPU-enabled voice service overlay # GPU-enabled voice service overlay
# Usage: docker compose -f docker-compose.yml -f docker-compose.voice.yml up voice-service # Usage: docker compose -f docker-compose.yml -f docker-compose.voice.yml up voice-service

View File

@ -1,22 +1,23 @@
version: '3.8'
services: services:
# ===== Infrastructure ===== # ===== Infrastructure =====
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine
environment: environment:
POSTGRES_USER: it0 POSTGRES_USER: ${POSTGRES_USER:-it0}
POSTGRES_PASSWORD: it0_dev POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-it0_dev}
POSTGRES_DB: it0 POSTGRES_DB: ${POSTGRES_DB:-it0}
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./scripts/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U it0"] test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-it0}"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
networks:
- it0-network
redis: redis:
image: redis:7-alpine image: redis:7-alpine
@ -27,22 +28,31 @@ services:
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
networks:
- it0-network
# ===== API Gateway ===== # ===== API Gateway =====
api-gateway: api-gateway:
build: ../../packages/gateway build:
context: ../../packages/gateway
ports: ports:
- "8000:8000" - "8000:8000"
- "8001:8001" - "8001:8001"
depends_on: depends_on:
- auth-service auth-service:
- agent-service condition: service_started
- ops-service agent-service:
- inventory-service condition: service_started
- monitor-service ops-service:
- comm-service condition: service_started
- audit-service inventory-service:
- redis condition: service_started
monitor-service:
condition: service_started
comm-service:
condition: service_started
audit-service:
condition: service_started
healthcheck: healthcheck:
test: ["CMD", "kong", "health"] test: ["CMD", "kong", "health"]
interval: 10s interval: 10s
@ -51,19 +61,25 @@ services:
networks: networks:
- it0-network - it0-network
# ===== Backend Services ===== # ===== Backend Services (shared Dockerfile.service) =====
auth-service: auth-service:
build: ../../packages/services/auth-service build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: auth-service
SERVICE_PORT: 3001
ports: ports:
- "3001:3001" - "3001:3001"
environment: environment:
- DB_HOST=postgres - DB_HOST=postgres
- DB_PORT=5432 - DB_PORT=5432
- DB_USERNAME=it0 - DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=it0_dev - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=it0 - DB_DATABASE=${POSTGRES_DB:-it0}
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret}
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-dev-jwt-refresh-secret}
- AUTH_SERVICE_PORT=3001 - AUTH_SERVICE_PORT=3001
depends_on: depends_on:
postgres: postgres:
@ -74,15 +90,20 @@ services:
- it0-network - it0-network
agent-service: agent-service:
build: ../../packages/services/agent-service build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: agent-service
SERVICE_PORT: 3002
ports: ports:
- "3002:3002" - "3002:3002"
environment: environment:
- DB_HOST=postgres - DB_HOST=postgres
- DB_PORT=5432 - DB_PORT=5432
- DB_USERNAME=it0 - DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=it0_dev - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=it0 - DB_DATABASE=${POSTGRES_DB:-it0}
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- AGENT_ENGINE_TYPE=claude_code_cli - AGENT_ENGINE_TYPE=claude_code_cli
@ -96,15 +117,20 @@ services:
- it0-network - it0-network
ops-service: ops-service:
build: ../../packages/services/ops-service build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: ops-service
SERVICE_PORT: 3003
ports: ports:
- "3003:3003" - "3003:3003"
environment: environment:
- DB_HOST=postgres - DB_HOST=postgres
- DB_PORT=5432 - DB_PORT=5432
- DB_USERNAME=it0 - DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=it0_dev - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=it0 - DB_DATABASE=${POSTGRES_DB:-it0}
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- OPS_SERVICE_PORT=3003 - OPS_SERVICE_PORT=3003
depends_on: depends_on:
@ -116,15 +142,21 @@ services:
- it0-network - it0-network
inventory-service: inventory-service:
build: ../../packages/services/inventory-service build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: inventory-service
SERVICE_PORT: 3004
ports: ports:
- "3004:3004" - "3004:3004"
environment: environment:
- DB_HOST=postgres - DB_HOST=postgres
- DB_PORT=5432 - DB_PORT=5432
- DB_USERNAME=it0 - DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=it0_dev - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=it0 - DB_DATABASE=${POSTGRES_DB:-it0}
- REDIS_URL=redis://redis:6379
- VAULT_MASTER_KEY=${VAULT_MASTER_KEY:-dev-vault-key} - VAULT_MASTER_KEY=${VAULT_MASTER_KEY:-dev-vault-key}
- INVENTORY_SERVICE_PORT=3004 - INVENTORY_SERVICE_PORT=3004
depends_on: depends_on:
@ -134,15 +166,20 @@ services:
- it0-network - it0-network
monitor-service: monitor-service:
build: ../../packages/services/monitor-service build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: monitor-service
SERVICE_PORT: 3005
ports: ports:
- "3005:3005" - "3005:3005"
environment: environment:
- DB_HOST=postgres - DB_HOST=postgres
- DB_PORT=5432 - DB_PORT=5432
- DB_USERNAME=it0 - DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=it0_dev - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=it0 - DB_DATABASE=${POSTGRES_DB:-it0}
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- MONITOR_SERVICE_PORT=3005 - MONITOR_SERVICE_PORT=3005
depends_on: depends_on:
@ -152,15 +189,20 @@ services:
- it0-network - it0-network
comm-service: comm-service:
build: ../../packages/services/comm-service build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: comm-service
SERVICE_PORT: 3006
ports: ports:
- "3006:3006" - "3006:3006"
environment: environment:
- DB_HOST=postgres - DB_HOST=postgres
- DB_PORT=5432 - DB_PORT=5432
- DB_USERNAME=it0 - DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=it0_dev - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=it0 - DB_DATABASE=${POSTGRES_DB:-it0}
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- TWILIO_ACCOUNT_SID=${TWILIO_ACCOUNT_SID} - TWILIO_ACCOUNT_SID=${TWILIO_ACCOUNT_SID}
- TWILIO_AUTH_TOKEN=${TWILIO_AUTH_TOKEN} - TWILIO_AUTH_TOKEN=${TWILIO_AUTH_TOKEN}
@ -175,15 +217,21 @@ services:
- it0-network - it0-network
audit-service: audit-service:
build: ../../packages/services/audit-service build:
context: ../..
dockerfile: Dockerfile.service
args:
SERVICE_NAME: audit-service
SERVICE_PORT: 3007
ports: ports:
- "3007:3007" - "3007:3007"
environment: environment:
- DB_HOST=postgres - DB_HOST=postgres
- DB_PORT=5432 - DB_PORT=5432
- DB_USERNAME=it0 - DB_USERNAME=${POSTGRES_USER:-it0}
- DB_PASSWORD=it0_dev - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev}
- DB_DATABASE=it0 - DB_DATABASE=${POSTGRES_DB:-it0}
- REDIS_URL=redis://redis:6379
- AUDIT_SERVICE_PORT=3007 - AUDIT_SERVICE_PORT=3007
depends_on: depends_on:
postgres: postgres:
@ -192,15 +240,16 @@ services:
- it0-network - it0-network
voice-service: voice-service:
build: ../../packages/services/voice-service build:
context: ../../packages/services/voice-service
ports: ports:
- "3008:3008" - "3008:3008"
environment: environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- AGENT_SERVICE_URL=http://agent-service:3002 - AGENT_SERVICE_URL=http://agent-service:3002
- WHISPER_MODEL=large-v3 - WHISPER_MODEL=${WHISPER_MODEL:-large-v3}
- KOKORO_MODEL=kokoro-82m - KOKORO_MODEL=${KOKORO_MODEL:-kokoro-82m}
- DEVICE=cpu - DEVICE=${VOICE_DEVICE:-cpu}
depends_on: depends_on:
- agent-service - agent-service
networks: networks:
@ -208,7 +257,8 @@ services:
# ===== Frontend ===== # ===== Frontend =====
web-admin: web-admin:
build: ../../it0-web-admin build:
context: ../../it0-web-admin
ports: ports:
- "3000:3000" - "3000:3000"
environment: environment:

View File

@ -0,0 +1,15 @@
node_modules
.next
out
coverage
.git
.gitignore
.vscode
.idea
*.md
.env
.env.*
!.env.example
.DS_Store
Thumbs.db
*.log

27
it0-web-admin/Dockerfile Normal file
View File

@ -0,0 +1,27 @@
# Next.js standalone production Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# ===== Production =====
FROM node:18-alpine
WORKDIR /app
ENV NODE_ENV=production
# Copy standalone server output (includes node_modules subset)
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

View File

@ -1,5 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: 'standalone',
experimental: { experimental: {
serverActions: true, serverActions: true,
}, },