docker-compose.yml
Rama main · réplica de lectura del repositorio original.
name: tokems
x-build-args: &build-args
BUILD_SHA: ${BUILD_SHA:-unknown}
BUILD_TIME: ${BUILD_TIME:-unknown}
BUILD_MIGRATION: ${BUILD_MIGRATION:-unknown}
BUILD_MIGRATION_HASH: ${BUILD_MIGRATION_HASH:-unknown}
x-build: &app-build
context: .
dockerfile: Dockerfile
args: *build-args
x-logging: &default-logging
driver: json-file
options:
max-size: 10m
max-file: '3'
x-node-runtime: &node-runtime
init: true
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
logging: *default-logging
x-app-environment: &app-environment
NODE_ENV: production
DEPLOYMENT_MODE: ${DEPLOYMENT_MODE:-production}
DATABASE_URL: ${DOCKER_DATABASE_URL:-postgresql://conference:conference@postgres:5432/conference}
REDIS_URL: ${DOCKER_REDIS_URL:-redis://redis:6379}
S3_ENDPOINT: ${DOCKER_S3_ENDPOINT:-http://minio:9000}
S3_PUBLIC_ENDPOINT: ${S3_PUBLIC_ENDPOINT:-http://localhost:19000}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-conference}}
S3_SECRET_KEY: ${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-conference-secret}}
S3_BUCKET: ${S3_BUCKET:-conference-assets}
S3_REGION: ${S3_REGION:-us-east-1}
DATABASE_POOL_SIZE: ${DATABASE_POOL_SIZE:-10}
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must contain at least 32 random characters}
TOKEMS_AGENT_ACCESS_ENABLED: ${TOKEMS_AGENT_ACCESS_ENABLED:-false}
TOKEMS_AGENT_WRITES_ENABLED: ${TOKEMS_AGENT_WRITES_ENABLED:-false}
TOKEMS_AGENT_CRITICAL_ACTIONS_ENABLED: ${TOKEMS_AGENT_CRITICAL_ACTIONS_ENABLED:-false}
AGENT_ACCESS_TOKEN_SECRET: ${AGENT_ACCESS_TOKEN_SECRET:-}
AGENT_ACCESS_TOKEN_PREVIOUS_SECRET: ${AGENT_ACCESS_TOKEN_PREVIOUS_SECRET:-}
INVOICE_DOWNLOAD_SIGNING_SECRET: ${INVOICE_DOWNLOAD_SIGNING_SECRET:-}
CUSTOMER_OTP_PEPPER: ${CUSTOMER_OTP_PEPPER:?CUSTOMER_OTP_PEPPER must contain at least 32 random characters}
CUSTOMER_SESSION_SECRET: ${CUSTOMER_SESSION_SECRET:?CUSTOMER_SESSION_SECRET must contain at least 32 random characters}
NOTIFICATION_PAYLOAD_ENCRYPTION_SECRET: ${NOTIFICATION_PAYLOAD_ENCRYPTION_SECRET:?NOTIFICATION_PAYLOAD_ENCRYPTION_SECRET must contain at least 32 random characters}
CUSTOMER_OTP_MODE: ${CUSTOMER_OTP_MODE:-provider}
CUSTOMER_OTP_ORG_DAILY_LIMIT: ${CUSTOMER_OTP_ORG_DAILY_LIMIT:-10000}
CUSTOMER_OTP_PLATFORM_HOURLY_LIMIT: ${CUSTOMER_OTP_PLATFORM_HOURLY_LIMIT:-8}
ALLOW_INSECURE_LOCAL_AUTH: ${ALLOW_INSECURE_LOCAL_AUTH:-false}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_USER_ID: ${ADMIN_USER_ID:-aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa}
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@tokems.local}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-}
SEED_DEMO_DATA: ${SEED_DEMO_DATA:-false}
PUBLIC_ORGANIZATION_SLUG: ${PUBLIC_ORGANIZATION_SLUG:-geo-conference}
PUBLIC_ORIGIN: ${PUBLIC_ORIGIN:?PUBLIC_ORIGIN must be the external scheme and host}
ADMIN_ORIGIN: ${ADMIN_ORIGIN:?ADMIN_ORIGIN must be the external admin scheme and host}
PAYMENT_PUBLIC_ORIGIN: ${PAYMENT_PUBLIC_ORIGIN:-}
PAYMENT_PUBLIC_BASE_PATH: ${PAYMENT_PUBLIC_BASE_PATH:-/pay/hui}
PAYMENT_PUBLIC_URL: ${PAYMENT_PUBLIC_URL:-}
PUBLIC_WEB_URL: ${PUBLIC_ORIGIN}
ADMIN_WEB_URL: ${ADMIN_ORIGIN}
PUBLIC_SITE_URL: ${PUBLIC_ORIGIN}
PUBLIC_API_URL: ${PUBLIC_ORIGIN}
PARTNER_ATTRIBUTION_SECRET: ${PARTNER_ATTRIBUTION_SECRET:-}
PARTNER_PAYOUT_DATA_SECRET: ${PARTNER_PAYOUT_DATA_SECRET:-}
PAYOUT_PUBLIC_URL: ${PAYOUT_PUBLIC_URL:-${PUBLIC_ORIGIN}}
TRUST_PROXY: ${TRUST_PROXY:-127.0.0.1,::1,172.16.0.0/12}
API_RATE_LIMIT_PER_MINUTE: ${API_RATE_LIMIT_PER_MINUTE:-5000}
PAYMENT_WEBHOOK_SECRET: ${PAYMENT_WEBHOOK_SECRET:-}
INTEGRATION_ENCRYPTION_KEY: ${INTEGRATION_ENCRYPTION_KEY:-}
INTEGRATION_ENCRYPTION_KEY_VERSION: ${INTEGRATION_ENCRYPTION_KEY_VERSION:-1}
INTEGRATION_ENCRYPTION_PREVIOUS_KEYS: ${INTEGRATION_ENCRYPTION_PREVIOUS_KEYS:-}
ENABLE_LOCAL_PAYMENT_SIMULATION: ${ENABLE_LOCAL_PAYMENT_SIMULATION:-false}
LOCAL_PAYMENT_SIMULATION_MOBILES: ${LOCAL_PAYMENT_SIMULATION_MOBILES:-}
AI_API_URL: ${AI_API_URL:-}
AI_API_KEY: ${AI_API_KEY:-}
AI_MODEL: ${AI_MODEL:-conference-copywriter-local-v1}
HTML_TEMPLATE_IMPORT_ENABLED: ${HTML_TEMPLATE_IMPORT_ENABLED:-true}
HTML_TEMPLATE_IMPORT_ORG_ALLOWLIST: ${HTML_TEMPLATE_IMPORT_ORG_ALLOWLIST:-}
HTML_TEMPLATE_ORG_ASSET_BYTES: ${HTML_TEMPLATE_ORG_ASSET_BYTES:-2147483648}
HTML_TEMPLATE_ORG_ASSET_COUNT: ${HTML_TEMPLATE_ORG_ASSET_COUNT:-10000}
HTML_TEMPLATE_AI_MAPPING_ENABLED: ${HTML_TEMPLATE_AI_MAPPING_ENABLED:-false}
HTML_TEMPLATE_AI_ORG_MINUTE_LIMIT: ${HTML_TEMPLATE_AI_ORG_MINUTE_LIMIT:-5}
HTML_TEMPLATE_AI_ORG_DAILY_LIMIT: ${HTML_TEMPLATE_AI_ORG_DAILY_LIMIT:-100}
NOTIFICATION_WEBHOOK_URL: ${NOTIFICATION_WEBHOOK_URL:-http://notification-sink:4080/notifications}
NOTIFICATION_WEBHOOK_TOKEN: ${NOTIFICATION_WEBHOOK_TOKEN:?NOTIFICATION_WEBHOOK_TOKEN is required}
OUTBOX_POLL_INTERVAL_MS: ${OUTBOX_POLL_INTERVAL_MS:-2000}
WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-5}
INVENTORY_RELEASE_INTERVAL_MS: ${INVENTORY_RELEASE_INTERVAL_MS:-30000}
SMS_RECEIPT_INTERVAL_MS: ${SMS_RECEIPT_INTERVAL_MS:-30000}
INVOICE_ASYNC_EXPORT_THRESHOLD: ${INVOICE_ASYNC_EXPORT_THRESHOLD:-50000}
BUILD_SHA: ${BUILD_SHA:-unknown}
BUILD_TIME: ${BUILD_TIME:-unknown}
BUILD_MIGRATION: ${BUILD_MIGRATION:-unknown}
BUILD_MIGRATION_HASH: ${BUILD_MIGRATION_HASH:-unknown}
services:
postgres:
image: ${POSTGRES_IMAGE:-pgvector/pgvector:pg16}
restart: unless-stopped
environment:
POSTGRES_DB: conference
POSTGRES_USER: conference
POSTGRES_PASSWORD: conference
ports:
- '127.0.0.1:${POSTGRES_PORT:-15432}:5432'
volumes:
- tokems-postgres:/var/lib/postgresql/data
healthcheck:
test: [CMD-SHELL, 'pg_isready -U conference -d conference']
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
logging: *default-logging
redis:
image: redis:7.4-alpine
command: [redis-server, --appendonly, 'yes']
restart: unless-stopped
ports:
- '127.0.0.1:${REDIS_PORT:-16379}:6379'
volumes:
- tokems-redis:/data
healthcheck:
test: [CMD, redis-cli, ping]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
logging: *default-logging
minio:
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
command: [server, /data, --console-address, ':9001']
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-conference}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-conference-secret}
ports:
- '127.0.0.1:${MINIO_API_PORT:-19000}:9000'
- '127.0.0.1:${MINIO_CONSOLE_PORT:-19001}:9001'
volumes:
- tokems-minio:/data
healthcheck:
test: [CMD, curl, --fail, http://127.0.0.1:9000/minio/health/live]
interval: 5s
timeout: 3s
retries: 30
start_period: 10s
logging: *default-logging
minio-init:
image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727
restart: 'no'
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-conference}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-conference-secret}
S3_BUCKET: ${S3_BUCKET:-conference-assets}
depends_on:
minio:
condition: service_healthy
entrypoint:
- /bin/sh
- -ec
- |
mc alias set local http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD"
mc mb --ignore-existing "local/$$S3_BUCKET"
mc anonymous set none "local/$$S3_BUCKET"
logging: *default-logging
mailpit:
image: axllent/mailpit@sha256:5a49a77c5bdbe7c5474450b4f46348d09949df3695257729c93a30369382d4f6
restart: unless-stopped
ports:
- '127.0.0.1:${MAILPIT_SMTP_PORT:-1025}:1025'
- '127.0.0.1:${MAILPIT_WEB_PORT:-8025}:8025'
healthcheck:
test: [CMD, wget, --quiet, --spider, http://127.0.0.1:8025/livez]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
logging: *default-logging
notification-sink:
<<: *node-runtime
image: tokems-notification-sink:local
build:
<<: *app-build
target: notification-sink
environment:
# Container listen port stays fixed; only the host mapping below is env-driven.
NOTIFICATION_SINK_PORT: 4080
NOTIFICATION_SINK_TOKEN: ${NOTIFICATION_WEBHOOK_TOKEN:?NOTIFICATION_WEBHOOK_TOKEN is required}
ports:
- '127.0.0.1:${NOTIFICATION_SINK_HOST_PORT:-${NOTIFICATION_SINK_PORT:-4080}}:4080'
healthcheck:
test:
- CMD
- node
- -e
- >-
fetch('http://127.0.0.1:4080/health').then((response) => {
if (!response.ok) process.exit(1)
}).catch(() => process.exit(1))
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
db-init:
image: tokems-api:local
build:
<<: *app-build
target: api
args:
<<: *build-args
NUXT_PUBLIC_API_BASE: /api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
VITE_API_BASE: /api/v1
environment: *app-environment
command:
- /bin/sh
- -ec
- >-
node node_modules/@conference/database/dist/migrate.js &&
if [ "$$SEED_DEMO_DATA" = "true" ]; then
node node_modules/@conference/database/dist/seed.js;
fi
depends_on:
postgres:
condition: service_healthy
restart: 'no'
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
logging: *default-logging
api:
<<: *node-runtime
image: tokems-api:local
build:
<<: *app-build
target: api
args:
<<: *build-args
NUXT_PUBLIC_API_BASE: /api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
VITE_API_BASE: /api/v1
environment:
<<: *app-environment
BATCH_PURCHASE_CREATION_ENABLED: ${BATCH_PURCHASE_CREATION_ENABLED:-true}
API_PORT: 4100
API_BIND_ADDRESS: 0.0.0.0
API_INTERNAL_CONTAINER: 'true'
expose:
- '4100'
depends_on:
db-init:
condition: service_completed_successfully
redis:
condition: service_healthy
minio-init:
condition: service_completed_successfully
healthcheck:
test:
- CMD
- node
- -e
- >-
fetch('http://127.0.0.1:4100/api/v1/health').then(async (response) => {
const body = await response.json();
if (!response.ok || body.status !== 'ok' || body.database?.ok !== true) process.exit(1)
}).catch(() => process.exit(1))
interval: 5s
timeout: 4s
retries: 30
start_period: 10s
worker:
<<: *node-runtime
tmpfs:
- /tmp:size=${WORKER_TMPFS_SIZE:-512m},mode=1777
image: tokems-worker:local
build:
<<: *app-build
target: worker
args:
<<: *build-args
NUXT_PUBLIC_API_BASE: /api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
VITE_API_BASE: /api/v1
environment: *app-environment
depends_on:
db-init:
condition: service_completed_successfully
redis:
condition: service_healthy
notification-sink:
condition: service_healthy
healthcheck:
test: [CMD, node, -e, 'process.kill(1, 0)']
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
web:
<<: *node-runtime
image: tokems-web:local
build:
<<: *app-build
target: web
args:
<<: *build-args
NUXT_PUBLIC_API_BASE: /api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
VITE_API_BASE: /api/v1
environment:
# Conference site must stay at domain root; never inherit payment base path.
NUXT_APP_BASE_URL: /
NUXT_API_INTERNAL_BASE: http://api:4100/api/v1
NUXT_PUBLIC_API_BASE: /api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
NUXT_PUBLIC_PAYMENT_ORIGIN: ${PAYMENT_PUBLIC_ORIGIN:-}
NUXT_PUBLIC_PAYMENT_BASE_PATH: ${PAYMENT_PUBLIC_BASE_PATH:-/pay/hui}
NUXT_PUBLIC_CONFERENCE_ORIGIN: ${PUBLIC_ORIGIN}
NUXT_PUBLIC_PAYMENT_SURFACE: 'false'
BUILD_SHA: ${BUILD_SHA:-unknown}
BUILD_TIME: ${BUILD_TIME:-unknown}
BUILD_MIGRATION: ${BUILD_MIGRATION:-unknown}
BUILD_MIGRATION_HASH: ${BUILD_MIGRATION_HASH:-unknown}
HOST: 0.0.0.0
PORT: 3000
expose:
- '3000'
depends_on:
api:
condition: service_healthy
healthcheck:
test:
- CMD
- node
- -e
- >-
fetch('http://127.0.0.1:3000/version.json').then((response) => {
if (!response.ok) process.exit(1)
}).catch(() => process.exit(1))
interval: 5s
timeout: 4s
retries: 30
start_period: 10s
payment-web:
<<: *node-runtime
image: tokems-web:local
build:
<<: *app-build
target: web
args:
<<: *build-args
NUXT_PUBLIC_API_BASE: /api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
VITE_API_BASE: /api/v1
environment:
NUXT_APP_BASE_URL: ${PAYMENT_PUBLIC_BASE_PATH:-/pay/hui}/
NUXT_API_INTERNAL_BASE: http://api:4100/api/v1
NUXT_PUBLIC_API_BASE: ${PAYMENT_PUBLIC_BASE_PATH:-/pay/hui}/api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
NUXT_PUBLIC_PAYMENT_ORIGIN: ${PAYMENT_PUBLIC_ORIGIN:-}
NUXT_PUBLIC_PAYMENT_BASE_PATH: ${PAYMENT_PUBLIC_BASE_PATH:-/pay/hui}
NUXT_PUBLIC_CONFERENCE_ORIGIN: ${PUBLIC_ORIGIN}
NUXT_PUBLIC_PAYMENT_SURFACE: 'true'
BUILD_SHA: ${BUILD_SHA:-unknown}
BUILD_TIME: ${BUILD_TIME:-unknown}
BUILD_MIGRATION: ${BUILD_MIGRATION:-unknown}
BUILD_MIGRATION_HASH: ${BUILD_MIGRATION_HASH:-unknown}
HOST: 0.0.0.0
PORT: 3000
expose:
- '3000'
depends_on:
api:
condition: service_healthy
healthcheck:
test:
- CMD
- node
- -e
- >-
fetch('http://127.0.0.1:3000${PAYMENT_PUBLIC_BASE_PATH:-/pay/hui}/version.json').then((response) => {
if (!response.ok) process.exit(1)
}).catch(() => process.exit(1))
interval: 5s
timeout: 4s
retries: 30
start_period: 10s
admin:
image: tokems-admin:local
build:
<<: *app-build
target: admin
args:
<<: *build-args
NUXT_PUBLIC_API_BASE: /api/v1
NUXT_PUBLIC_ORGANIZATION_SLUG: ${NUXT_PUBLIC_ORGANIZATION_SLUG:-geo-conference}
VITE_API_BASE: /api/v1
VITE_ADMIN_BASE: /admin/
VITE_SIMPLE_AUTH: ${VITE_SIMPLE_AUTH:-false}
restart: unless-stopped
read_only: true
tmpfs:
- /var/cache/nginx:size=32m
- /var/run:size=4m
- /tmp:size=16m,mode=1777
security_opt:
- no-new-privileges:true
expose:
- '8080'
depends_on:
api:
condition: service_healthy
healthcheck:
test: [CMD, wget, --quiet, --spider, http://127.0.0.1:8080/healthz]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
logging: *default-logging
gateway:
image: tokems-gateway:local
build:
<<: *app-build
target: gateway
restart: unless-stopped
read_only: true
tmpfs:
- /var/cache/nginx:size=32m
- /var/run:size=4m
- /tmp:size=16m,mode=1777
security_opt:
- no-new-privileges:true
ports:
- '${GATEWAY_BIND_ADDRESS:-127.0.0.1}:${GATEWAY_PORT:-8088}:8080'
depends_on:
api:
condition: service_healthy
web:
condition: service_healthy
payment-web:
condition: service_healthy
admin:
condition: service_healthy
healthcheck:
test: [CMD, wget, --quiet, --spider, http://127.0.0.1:8080/healthz]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
logging: *default-logging
volumes:
tokems-postgres:
name: tokems-postgres
tokems-redis:
name: tokems-redis
tokems-minio:
name: tokems-minio