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