diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4199e08 --- /dev/null +++ b/.dockerignore @@ -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 diff --git a/Dockerfile.service b/Dockerfile.service new file mode 100644 index 0000000..58945bf --- /dev/null +++ b/Dockerfile.service @@ -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"] diff --git a/deploy/docker/docker-compose.ssl.yml b/deploy/docker/docker-compose.ssl.yml index a8c98bf..8874cc9 100644 --- a/deploy/docker/docker-compose.ssl.yml +++ b/deploy/docker/docker-compose.ssl.yml @@ -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 diff --git a/deploy/docker/docker-compose.voice.yml b/deploy/docker/docker-compose.voice.yml index 46c6ddf..dedd0fa 100644 --- a/deploy/docker/docker-compose.voice.yml +++ b/deploy/docker/docker-compose.voice.yml @@ -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 diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index fad42d0..296d95f 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -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: diff --git a/it0-web-admin/.dockerignore b/it0-web-admin/.dockerignore new file mode 100644 index 0000000..7031841 --- /dev/null +++ b/it0-web-admin/.dockerignore @@ -0,0 +1,15 @@ +node_modules +.next +out +coverage +.git +.gitignore +.vscode +.idea +*.md +.env +.env.* +!.env.example +.DS_Store +Thumbs.db +*.log diff --git a/it0-web-admin/Dockerfile b/it0-web-admin/Dockerfile new file mode 100644 index 0000000..1fe4836 --- /dev/null +++ b/it0-web-admin/Dockerfile @@ -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"] diff --git a/it0-web-admin/next.config.js b/it0-web-admin/next.config.js index 4817751..6bf5a8d 100644 --- a/it0-web-admin/next.config.js +++ b/it0-web-admin/next.config.js @@ -1,5 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + output: 'standalone', experimental: { serverActions: true, },