Ctrl K
ComunidadGuías…

docker-compose.yml

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Self-host / prod stack. Boot the core with:  docker compose --profile full up --build
# Convert a model (job):       docker compose run --rm converter /data/model.ifc /data/model.frag
#
# Resource limits (opt-in): every service below has a commented-out `deploy.resources.limits`
# block keyed off env vars (AEC_*_MEM_LIMIT / AEC_*_CPUS — see .env.example for sizing guidance).
# They are OFF by default on purpose: a hard memory cap that is too low will OOM-kill a legitimate
# large-IFC conversion mid-run. Uncomment and set values sized for YOUR host before enabling.
# `deploy.resources.limits` is honored by `docker compose up` (v2); no Swarm required.
services:
  postgres:
    image: postgres:17
    environment:
      POSTGRES_USER: "${POSTGRES_USER:-bim}"
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-bim}"
      POSTGRES_DB: "${POSTGRES_DB:-bim}"
    ports: ["5432:5432"]
    volumes: ["postgres-data:/var/lib/postgresql/data"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U bim"]
      interval: 5s
      timeout: 3s
      retries: 10
    # deploy:                              # opt-in cap — uncomment + size for your host
    #   resources:
    #     limits:
    #       memory: "${AEC_PG_MEM_LIMIT:-2g}"

  minio:
    # Pinned. An unpinned `minio/minio` resolves to :latest, so two developers -- or a developer and
    # CI -- can silently run different object stores, and a breaking upstream release arrives without
    # a commit. Dependabot's docker ecosystem keeps the pins fresh via PRs.
    image: minio/minio:RELEASE.2025-04-22T22-12-26Z
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: "${S3_ACCESS_KEY:-minioadmin}"
      MINIO_ROOT_PASSWORD: "${S3_SECRET_KEY:-minioadmin}"
    ports: ["9000:9000", "9001:9001"]
    volumes: ["minio-data:/data"]
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 5s
      timeout: 3s
      retries: 10
    # deploy:                              # opt-in cap — uncomment + size for your host
    #   resources:
    #     limits:
    #       memory: "${AEC_MINIO_MEM_LIMIT:-1g}"

  api:
    image: massing-api
    build:
      context: .
      dockerfile: services/api/Dockerfile
    depends_on:
      postgres: { condition: service_healthy }
      minio: { condition: service_healthy }
    environment: &api-env
      DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER:-bim}:${POSTGRES_PASSWORD:-bim}@postgres:5432/${POSTGRES_DB:-bim}"
      S3_ENDPOINT: http://minio:9000
      S3_ACCESS_KEY: "${S3_ACCESS_KEY:-minioadmin}"
      S3_SECRET_KEY: "${S3_SECRET_KEY:-minioadmin}"
      S3_BUCKET: "${S3_BUCKET:-aec-bim}"
      AEC_RBAC: "${AEC_RBAC:-0}"                       # set 1 to enforce project roles
      AEC_API_KEY: "${AEC_API_KEY:-}"                  # bearer token = admin (set in prod)
      AEC_AUTH_SECRET: "${AEC_AUTH_SECRET:-}"          # signs login tokens (falls back to AEC_API_KEY)
      AEC_CORS_ORIGINS: "${AEC_CORS_ORIGINS:-http://localhost:5173}"
      UVICORN_WORKERS: "${UVICORN_WORKERS:-4}"        # multi-worker (Postgres) so a reconvert can't freeze the API
      # JOB-WORKER-SPLIT: the heavy job kinds (COBie export, bundles, generative runs) are full-model
      # IFC parses. Run inside this container they compete for CPU and memory with every request it is
      # serving, so the compose stack runs them in the `worker` service below instead. Set this to
      # `inline` to go back to one container doing both — the code default is inline precisely so a
      # deployment that is NOT using this file is unaffected.
      AEC_JOB_WORKER: "${AEC_JOB_WORKER:-off}"
      APS_CLIENT_ID: "${APS_CLIENT_ID:-}"              # optional paid Autodesk RVT bridge
      APS_CLIENT_SECRET: "${APS_CLIENT_SECRET:-}"
    ports: ["8000:8000"]
    volumes: ["ifc-data:/app/ifc"]   # uploaded source IFCs persist across restarts
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
      interval: 5s
      timeout: 3s
      retries: 20
    profiles: ["full"]
    # deploy:                              # opt-in cap — uncomment + size for your host
    #   resources:                         # keep generous: UVICORN_WORKERS=4 + an in-process reconvert
    #     limits:
    #       memory: "${AEC_API_MEM_LIMIT:-4g}"
    #       cpus: "${AEC_API_CPUS:-2}"

  # JOB-WORKER-SPLIT — the durable queue, in its own container. Same image and same environment as
  # the API (the `&api-env` anchor above, so the two cannot drift apart), different command.
  #
  # Scale it independently of request serving, which is the entire point:
  #   docker compose --profile full up -d --scale worker=3
  # That is safe without any further change because job claim is a compare-and-swap and `pid_lock`
  # takes a Postgres advisory lock — two workers cannot both win one job, and a mutating job still
  # serialises against a concurrent API edit on the same project.
  #
  # No ports and no healthcheck: it serves nothing, so there is nothing to probe. "Is the queue
  # moving?" is a question about the oldest `queued` row, not about this container being up — a
  # wedged worker and an idle one look identical from outside.
  worker:
    image: massing-api
    build:
      context: .
      dockerfile: services/api/Dockerfile
    depends_on:
      postgres: { condition: service_healthy }
      minio: { condition: service_healthy }
    environment:
      <<: *api-env
      AEC_JOB_WORKER: "inline"      # this process IS the worker; never `off` or it does nothing at all
    command: ["python", "-m", "aec_api.worker"]
    # The image bakes a HEALTHCHECK that curls localhost:8000/health. This service binds no port and
    # serves nothing, so that probe can never pass and the container sits permanently `unhealthy` —
    # which is worse than no signal, because it trains whoever is watching to ignore the column. The
    # comment above claimed there was no healthcheck; there was, inherited from the image.
    healthcheck: { disable: true }
    volumes: ["ifc-data:/app/ifc"]   # same uploaded-IFC volume as the API — jobs re-parse those files
    profiles: ["full"]
    # deploy:                        # opt-in cap — size for your host; this is where the heavy parses
    #   resources:                   # now live, so it wants the generous limit the API no longer needs
    #     limits:
    #       memory: "${AEC_WORKER_MEM_LIMIT:-4g}"
    #       cpus: "${AEC_WORKER_CPUS:-2}"

  # one-shot demo seeder (api must be enabled, so pass both profiles):
  #   docker compose --profile full --profile seed run --rm seed
  # DEV/TEST ONLY — never run against production. The script refuses if projects already exist
  # (see docs/PRODUCTION_CHECKLIST.md); --force overrides only for labs.
  seed:
    image: massing-api
    depends_on:
      api: { condition: service_healthy }
    command: ["python", "/app/services/api/seed_demo.py", "--url", "http://api:8000", "--user", "gc"]
    profiles: ["seed"]

  web:
    build:
      context: .
      dockerfile: apps/web/Dockerfile
    depends_on:
      api: { condition: service_started }
    ports: ["${WEB_PORT:-8080}:80"]
    profiles: ["full"]
    # deploy:                              # opt-in cap — uncomment + size for your host
    #   resources:                         # nginx serving static assets — modest footprint
    #     limits:
    #       memory: "${AEC_WEB_MEM_LIMIT:-256m}"

  converter:
    build:
      context: .
      dockerfile: services/converter/Dockerfile
    volumes: ["./data:/data"]            # drop IFCs here; .frag written here
    profiles: ["tools"]                  # job-style: `docker compose run --rm converter ...`
    # deploy:                              # opt-in cap — uncomment + size for your host
    #   resources:                         # IFC→Fragments is memory-hungry: size well above your
    #     limits:                          # largest model and against AEC_PUBLISH_WORKERS (PR #70),
    #       memory: "${AEC_CONVERTER_MEM_LIMIT:-8g}"   # since parallel jobs each hold a model in RAM.
    #       cpus: "${AEC_CONVERTER_CPUS:-4}"

volumes:
  postgres-data:
  minio-data:
  ifc-data: