Skip to main content

Docker Configuration Guide

Helix Platform Containerization


Overview

This document explains the Docker containerization strategy for Helix microservices. Each service runs as an independent container built from the monorepo source.

Key Principles

  1. Monorepo for Development - All code in one repository
  2. Containers for Production - Each service deploys independently
  3. Multi-Stage Builds - Optimized image sizes
  4. Conditional Prisma - Generate only needed clients

Base Dockerfile Template

The Dockerfile.template provides the standard build pattern for all Helix services:

Stage 1: Builder

  • Installs all dependencies (including dev)
  • Generates Prisma clients (central + service-specific if exists)
  • Builds service using Nx
  • Bundles shared libraries

Stage 2: Production

  • Only production dependencies
  • Only built service code
  • Prisma clients included
  • Runs as non-root user
  • Health check configured

Building Services

Using Build Arguments

# Build a specific service
docker build \
--build-arg SERVICE_NAME=auth-service \
-t helix/auth-service:latest \
-f Dockerfile.template .

# Build with version tag
docker build \
--build-arg SERVICE_NAME=kira-service \
-t helix/kira-service:1.0.0 \
-f Dockerfile.template .

Building All Services

# Build all services (when implemented)
for service in api-gateway auth-service tenant-service admin-service file-service kira-service vera-service cleverscreen-service; do
docker build \
--build-arg SERVICE_NAME=$service \
-t helix/$service:latest \
-f Dockerfile.template .
done

Building with Nx Affected

# Build only services changed since last deploy
for service in $(npx nx affected:apps --base=main --plain); do
docker build \
--build-arg SERVICE_NAME=$service \
-t helix/$service:$GIT_SHA \
-f Dockerfile.template .
done

Prisma Client Generation

Two Types of Clients

1. Central Database Client (ALL services):

  • Location: prisma/central/schema.prisma
  • Generated: Always
  • Used for: Tenant lookups, auth, audit logs
  • Output: node_modules/@prisma/client-central

2. Service-Specific Client (Optional):

  • Location: apps/{service}/prisma/schema.prisma
  • Generated: Only if schema exists
  • Used for: Product-specific data
  • Output: node_modules/@prisma/client

Which Services Need Which

ServiceCentral ClientService Client
api-gateway
auth-service
tenant-service
admin-service
file-service
kira-service
vera-service
cleverscreen-service

Service-Specific Dockerfiles

Services can have their own Dockerfile for specific requirements:

Example: apps/kira-service/Dockerfile

FROM node:18-alpine AS builder
WORKDIR /app

COPY package*.json ./
COPY nx.json tsconfig.base.json ./
COPY apps/ apps/
COPY libs/ libs/
COPY prisma/ prisma/

RUN npm ci

# Generate BOTH Prisma clients for KIRA
RUN npx prisma generate --schema=prisma/central/schema.prisma
RUN npx prisma generate --schema=apps/kira-service/prisma/schema.prisma

RUN npx nx build kira-service --configuration=production

FROM node:18-alpine
WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY --from=builder /app/dist/apps/kira-service ./
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma

ENV NODE_ENV=production
ENV PORT=3010

EXPOSE 3010

HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "require('http').get('http://localhost:3010/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

USER node

CMD ["node", "main.js"]

Docker Compose for Local Development

Architecture Overview

The docker-compose setup provides a complete local development environment with:

Infrastructure Services (Always Running):

  • PostgreSQL - Multi-database setup (3-tier architecture)
  • Redis - Queue and cache
  • MinIO - S3-compatible local storage

Application Services (Commented Out Until Implemented):

  • API Gateway - Port 3000 (ONLY exposed service)
  • Backend Services - Internal network only (ports 3001-3004, 3010-3012)

3-Tier Database Architecture

The PostgreSQL container creates multiple databases automatically:

Tier 1 & 2 (Central - Used by ALL services):

  • helix_central - Platform metadata (NO PII)
  • helix_tenant_dev_shared - Shared tenant data (WITH PII)

Tier 3 (Service-Specific - ONLY product services):

  • helix_kira_dev - KIRA service database
  • helix_vera_dev - VERA service database
  • helix_cleverscreen_dev - CleverScreen service database

Core services (API Gateway, Auth, Tenant, Admin, File) use ONLY Tier 1 & 2.

Port Exposure Strategy ⚠️

CRITICAL: Only the API Gateway is exposed to the host machine.

# CORRECT - API Gateway (exposed)
api-gateway:
ports:
- "3000:3000" # Accessible from host

# CORRECT - Backend services (internal only)
auth-service:
expose:
- "3001" # Docker network only
# ports: # Commented out for production
# - "3001:3001" # Uncomment for debugging

Why?

  • Frontend/clients connect ONLY to API Gateway (localhost:3000)
  • API Gateway proxies requests to backend services via Docker network
  • Backend services cannot be accessed directly from host (security)
  • Mimics production architecture where services are behind load balancer

Starting the Stack

# Start infrastructure only (PostgreSQL, Redis, MinIO)
docker-compose up -d

# View logs
docker-compose logs -f

# View logs for specific service
docker-compose logs -f postgres

# Check service status
docker-compose ps

Stopping the Stack

# Stop all services (preserves data)
docker-compose down

# Stop and remove volumes (CLEAN SLATE - deletes all data)
docker-compose down -v

# Stop specific service
docker-compose stop postgres

Database Initialization

The scripts/init-db.sh script automatically creates all databases on first startup:

# Databases created automatically:
✓ helix_central
✓ helix_tenant_dev_shared
✓ helix_kira_dev
✓ helix_vera_dev
✓ helix_cleverscreen_dev

Verify databases:

docker-compose exec postgres psql -U helix -c "\l"

Resetting Local Environment

To start completely fresh:

# 1. Stop and remove everything
docker-compose down -v

# 2. Restart (databases recreated)
docker-compose up -d

# 3. Verify
docker-compose ps
docker-compose logs postgres | grep "database system is ready"

Debugging Backend Services

When services are implemented, you can access them directly for debugging:

# In docker-compose.yml, uncomment the ports: section
auth-service:
expose:
- "3001"
ports: # ← Uncomment this
- "3001:3001"

Then restart:

docker-compose up -d auth-service
curl http://localhost:3001/health

Remember to comment out again before committing!

Accessing Infrastructure Tools

PostgreSQL:

# Connect via psql
docker-compose exec postgres psql -U helix -d helix_central

# Or use your favorite GUI tool:
# Host: localhost
# Port: 5432
# User: helix
# Password: helix_dev_password

Redis:

# Connect via redis-cli
docker-compose exec redis redis-cli

# Or use Redis Commander/RedisInsight:
# Host: localhost
# Port: 6379

MinIO Console:

# Web UI:
http://localhost:9001

# Credentials:
# Username: helix
# Password: helix_dev_password

# API Endpoint:
http://localhost:9000

Services Included

Infrastructure (Exposed Ports):

  • PostgreSQL - Port 5432
  • Redis - Port 6379
  • MinIO API - Port 9000
  • MinIO Console - Port 9001

Application Services (When Uncommented):

  • API Gateway - Port 3000 (ONLY exposed app port)
  • Auth Service - Internal only (3001)
  • Tenant Service - Internal only (3002)
  • Admin Service - Internal only (3003)
  • File Service - Internal only (3004)
  • KIRA Service - Internal only (3010)
  • VERA Service - Internal only (3011)
  • CleverScreen Service - Internal only (3012)

Network Architecture

All services communicate via the helix-network Docker bridge network:

Frontend (localhost:3001)

API Gateway (localhost:3000)

Backend Services (Docker network)
├─ auth-service:3001
├─ tenant-service:3002
├─ admin-service:3003
├─ file-service:3004
├─ kira-service:3010
├─ vera-service:3011
└─ cleverscreen-service:3012

Uncommenting Services

As services are implemented (HLX-6 through HLX-13), uncomment them in docker-compose.yml:

# Find the service block
# api-gateway: ← Remove the # from all lines in the block

# Then restart
docker-compose up -d api-gateway

# Verify
curl http://localhost:3000/health

Troubleshooting

Database won't start:

# Check logs
docker-compose logs postgres

# Recreate
docker-compose down -v
docker-compose up -d postgres

Service can't connect to database:

# Verify database is healthy
docker-compose ps postgres

# Check database exists
docker-compose exec postgres psql -U helix -c "\l"

# Test connection from service
docker-compose exec auth-service nc -zv postgres 5432

Services can't communicate:

# Verify network
docker network inspect helixcore_helix-network

# Test DNS resolution
docker-compose exec api-gateway ping auth-service

MinIO bucket doesn't exist:

# Create bucket manually
docker-compose exec minio mc mb /data/helix-dev

Image Optimization

Expected Sizes

Service TypeSize Range
Core services (no product DB)80-100 MB
Product services (with DB)100-120 MB
Services with AI libraries200-300 MB
Services with heavy deps300-400 MB

Optimization Techniques

  1. Multi-stage builds - Separate build and runtime
  2. Alpine base image - ~5MB vs 50MB
  3. Production deps only - Exclude devDependencies
  4. Tree-shaking - Remove unused code
  5. Layer caching - Faster rebuilds
  6. npm cache clean - Remove install artifacts

Layer Caching Strategy

Optimize build speed with proper layer ordering:

# 1. Package files (rarely change)
COPY package*.json ./
RUN npm ci

# 2. Schemas (change less than code)
COPY prisma/ prisma/
RUN npx prisma generate

# 3. Source code (changes frequently)
COPY apps/ apps/
COPY libs/ libs/

# 4. Build (always fresh)
RUN npx nx build $SERVICE_NAME

Benefits:

  • Package layer cached until dependencies change
  • Prisma generation cached until schemas change
  • Source changes don't invalidate earlier layers

Security Best Practices

In Dockerfile

# Use specific version tags
FROM node:18.19-alpine # Not :latest

# Run as non-root user
USER node

# Health checks
HEALTHCHECK --interval=30s CMD ...

# No secrets in build
# Use runtime environment variables instead

In docker-compose.yml

# Use secrets for sensitive data
secrets:
db_password:
file: ./secrets/db_password.txt

services:
postgres:
environment:
POSTGRES_PASSWORD: /run/secrets/db_password

Environment Variables

Each service requires environment configuration:

# Service
SERVICE_NAME=auth-service
SERVICE_MODE=api
PORT=3001
NODE_ENV=development

# Database
CENTRAL_DATABASE_URL=postgresql://helix:password@postgres-central:5432/helix_central

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# WorkOS
WORKOS_API_KEY=your_api_key
WORKOS_CLIENT_ID=your_client_id

Health Checks

All services include health check endpoints:

Dockerfile Health Check

HEALTHCHECK --interval=30s --timeout=3s --start-period=40s \
CMD node -e "require('http').get('http://localhost:${PORT}/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

Service Implementation

@Get('health')
async health() {
return {
status: 'healthy',
service: 'auth-service',
timestamp: new Date().toISOString()
};
}

Troubleshooting

Build Fails

# Check Nx build works locally
npx nx build auth-service

# Check Prisma generation
npx prisma generate --schema=prisma/central/schema.prisma

# Clear Nx cache
npx nx reset

Container Won't Start

# Check logs
docker logs helix-auth-service

# Check health
docker inspect --format='{{.State.Health.Status}}' helix-auth-service

# Enter container for debugging
docker exec -it helix-auth-service sh

Image Too Large

# Check image layers
docker history helix/auth-service:latest

# Check what's in the image
docker run --rm helix/auth-service:latest ls -lah

# Ensure .dockerignore is properly configured

CI/CD Integration

GitHub Actions Example

name: Build and Push Docker Images

on:
push:
branches: [main]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
service: ${{ fromJson(needs.affected.outputs.services) }}

steps:
- uses: actions/checkout@v4

- name: Build Docker Image
run: |
docker build \
--build-arg SERVICE_NAME=${{ matrix.service }} \
-t $ECR_REGISTRY/${{ matrix.service }}:${{ github.sha }} \
-f Dockerfile.template .

Best Practices

DO ✅

  • Use multi-stage builds
  • Pin Node.js version
  • Run as non-root user
  • Include health checks
  • Optimize layer caching
  • Use .dockerignore
  • Generate only needed Prisma clients

DON'T ❌

  • Use :latest tag in production
  • Include secrets in images
  • Run as root user
  • Skip health checks
  • Ignore image size
  • Copy entire node_modules
  • Generate unused Prisma clients

Reference


Last Updated: 2025-10-21
Maintained By: CleverChain Platform Team