name: liekan services: app: image: liekan:1.2.0 build: context: . dockerfile: Dockerfile.liekan args: LIEKAN_REVISION: ${LIEKAN_REVISION:-development} ports: - '127.0.0.1:3002:3000' environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} APP_SECRET: ${APP_SECRET:?APP_SECRET required} TWO_FACTOR_ENCRYPTION_KEY: ${TWO_FACTOR_ENCRYPTION_KEY:?TWO_FACTOR_ENCRYPTION_KEY required} DISABLE_TELEMETRY: '1' NEXT_TELEMETRY_DISABLED: '1' DEFAULT_LOCALE: zh-CN CLIENT_IP_HEADER: ${CLIENT_IP_HEADER:-} IGNORE_IP: ${IGNORE_IP:-} GEOLITE_DB_PATH: ${GEOLITE_DB_PATH:-} SKIP_LOCATION_HEADERS: ${SKIP_LOCATION_HEADERS:-} LIEKAN_GEO_DIRECTORY: /app/managed-geo LIEKAN_GEO_CHINA_MODE: ${LIEKAN_GEO_CHINA_MODE:-shadow} LIEKAN_ANALYTICS_REDIS_URL: ${LIEKAN_ANALYTICS_REDIS_URL:-redis://analytics-cache:6379} LIEKAN_AI_ENABLED: ${LIEKAN_AI_ENABLED:-false} LIEKAN_AI_ENCRYPTION_KEY: ${LIEKAN_AI_ENCRYPTION_KEY:-} LIEKAN_PUBLIC_URL: ${LIEKAN_PUBLIC_URL:-} LIEKAN_LOG_CREDENTIALS_KEY: ${LIEKAN_LOG_CREDENTIALS_KEY:-} LIEKAN_LOG_PREVIOUS_KEYS: ${LIEKAN_LOG_PREVIOUS_KEYS:-} CLOUDFLARE_OAUTH_CLIENT_ID: ${CLOUDFLARE_OAUTH_CLIENT_ID:-} CLOUDFLARE_OAUTH_CLIENT_SECRET: ${CLOUDFLARE_OAUTH_CLIENT_SECRET:-} volumes: - liekan-geography:/app/managed-geo:ro depends_on: db: condition: service_healthy healthcheck: test: ['CMD', 'node', '-e', "fetch('http://127.0.0.1:3000/api/heartbeat').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] interval: 15s timeout: 5s retries: 10 restart: unless-stopped log-worker: image: liekan:1.2.0 command: ['node', 'dist/liekan-worker.mjs'] environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} APP_SECRET: ${APP_SECRET:?APP_SECRET required} LIEKAN_PUBLIC_URL: ${LIEKAN_PUBLIC_URL:-} LIEKAN_LOG_CREDENTIALS_KEY: ${LIEKAN_LOG_CREDENTIALS_KEY:-} LIEKAN_LOG_PREVIOUS_KEYS: ${LIEKAN_LOG_PREVIOUS_KEYS:-} CLOUDFLARE_OAUTH_CLIENT_ID: ${CLOUDFLARE_OAUTH_CLIENT_ID:-} CLOUDFLARE_OAUTH_CLIENT_SECRET: ${CLOUDFLARE_OAUTH_CLIENT_SECRET:-} DISABLE_TELEMETRY: '1' GEOLITE_DB_PATH: ${GEOLITE_DB_PATH:-} LIEKAN_GEO_DIRECTORY: /app/managed-geo LIEKAN_GEO_UPDATER: '1' LIEKAN_GEO_CHINA_MODE: ${LIEKAN_GEO_CHINA_MODE:-shadow} LIEKAN_GEO_XDB_MANIFEST: ${LIEKAN_GEO_XDB_MANIFEST:-} LIEKAN_GEO_SOURCE_MANIFEST: ${LIEKAN_GEO_SOURCE_MANIFEST:-} GEO_DATABASE_URL: ${GEO_DATABASE_URL:-} MAXMIND_ACCOUNT_ID: ${MAXMIND_ACCOUNT_ID:-} MAXMIND_LICENSE_KEY: ${MAXMIND_LICENSE_KEY:-} volumes: - liekan-geography:/app/managed-geo depends_on: app: condition: service_healthy healthcheck: test: ['CMD', 'node', '-e', "const fs=require('fs');process.exit(Date.now()-fs.statSync('/tmp/liekan-worker-heartbeat').mtimeMs<120000?0:1)"] interval: 30s timeout: 5s retries: 4 restart: unless-stopped analytics-worker: image: liekan:1.2.0 command: ['node', 'dist/liekan-analytics-worker.mjs'] environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} APP_SECRET: ${APP_SECRET:?APP_SECRET required} LIEKAN_ANALYTICS_REDIS_URL: ${LIEKAN_ANALYTICS_REDIS_URL:-redis://analytics-cache:6379} DISABLE_TELEMETRY: '1' cpus: 1.0 mem_limit: 512m depends_on: app: condition: service_healthy healthcheck: test: ['CMD', 'node', '-e', "const fs=require('fs');process.exit(Date.now()-fs.statSync('/tmp/liekan-analytics-worker-heartbeat').mtimeMs<120000?0:1)"] interval: 30s timeout: 5s retries: 4 restart: unless-stopped analytics-cache: image: redis:7.4-alpine command: ['redis-server', '--maxmemory', '256mb', '--maxmemory-policy', 'allkeys-lru', '--save', '', '--appendonly', 'no'] mem_limit: 384m healthcheck: test: ['CMD', 'redis-cli', 'ping'] interval: 10s timeout: 3s retries: 5 restart: unless-stopped db: image: postgres:16-alpine shm_size: '256mb' ports: - '127.0.0.1:15435:5432' environment: POSTGRES_DB: ${POSTGRES_DB:-liekan} POSTGRES_USER: ${POSTGRES_USER:-liekan} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required} volumes: - liekan-data:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"'] interval: 5s timeout: 5s retries: 20 restart: unless-stopped volumes: liekan-data: liekan-geography: networks: default: ipam: config: - subnet: 10.253.30.0/24