services: # ────────────────────────────────────────────────────────────────────────── # External infrastructure this stack depends on (NOT part of this repo): # # 1. PostgreSQL ("OmniDB" by default) — reachable on the external Docker # network `postgres-network` (see bottom of this file, external: true). # Configure via: # DB_HOST (default: OmniDB) # DB_PORT (default: 5432) # DB_PASSWORD (required, no default — set in .env) # # 2. MQTT broker — runs directly on the Windows host, unauthenticated, # reachable from containers via the Docker Desktop DNS alias # `host.docker.internal`. Configure via: # MQTT_HOST (default: host.docker.internal) # MQTT_PORT (default: 4545) # # On a fresh clone, on a different machine, or to point at different # infrastructure, override the variables above in a local `.env` file — # no edits to the service blocks below are needed. # # We deliberately do NOT ship a `local-infra` profile with throwaway # Postgres/Mosquitto containers here. Reasoning: a working local Postgres # would need to provision 9 distinct databases (finlytic_assets, # finlytic_news, ... one per service) on first start, which the stock # `postgres` image cannot do via environment variables alone — it needs # an `docker-entrypoint-initdb.d` init script. That's an additional file # outside the scope of this change (compose.yaml / Dockerfiles / # .dockerignore only), and a second, empty, unauthenticated Postgres # sitting next to the real one is a plausible source of "why is my data # missing" confusion for a single-developer repo where the real OmniDB # already holds live data. The DB_HOST/DB_PORT/MQTT_HOST/MQTT_PORT # variables above are the actually load-bearing fix: they remove the # 9x-duplicated hardcoding and make the stack point-elsewhere-capable # without any code edits. If real multi-developer/CI use ever # materializes, revisit with a proper init-script-backed local-infra # profile at that point. # ────────────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────────── # Build-only prerequisite: Chromium + Node + Playwright CLI base layer. # Consumed as `FROM finlytic-playwright-base:1.49.0` by BOTH finlyticnews # and finlyticfundamentals. Compose does not resolve FROM-references across # services, so this MUST be built before the services that depend on it: # # docker compose --profile build-base build finlytic-playwright-base # docker compose build # # Keep PLAYWRIGHT_VERSION in sync with the Microsoft.Playwright NuGet # package version in FinlyticCore/FinlyticCore.csproj. # ────────────────────────────────────────────────────────────────────────── finlytic-playwright-base: profiles: ["build-base"] image: finlytic-playwright-base:1.49.0 build: context: FinlyticNews dockerfile: Dockerfile.playwright-base args: PLAYWRIGHT_VERSION: "1.49.0" finlyticassets: image: finlyticassets build: context: . dockerfile: FinlyticAssets/Dockerfile # unless-stopped: this worker has no required secrets that could be # permanently misconfigured — any crash is expected to be a transient # DB/MQTT hiccup, so it should keep retrying indefinitely (Docker backs # off automatically between attempts). restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_assets;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # Client-side MQTT Username/Password support exists (applied only when set), # but the broker at MQTT_HOST:MQTT_PORT has no users/ACLs configured yet # (verified — unauthenticated). Do NOT uncomment until broker-side users # exist; doing so now would break the current anonymous connection. # Once the broker has matching users, activate via: #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_assets volumes: - ${FINLYTIC_DATA_ROOT:-C:/Users/larsh/Documents/docker/finlytic}/assets/index:/app/assets/index - ${FINLYTIC_DATA_ROOT:-C:/Users/larsh/Documents/docker/finlytic}/assets/logos:/app/assets/logos finlyticnews: image: finlyticnews build: context: . dockerfile: FinlyticNews/Dockerfile restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_news;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_news # No data/summaries mount: the legacy filesystem read of Sentiment's # summary cache was removed and replaced by an MQTT-RPC call to # FinlyticSentiment (verified — no code path reads that directory # anymore). Only the asset index mount remains. volumes: - ${FINLYTIC_DATA_ROOT:-C:/Users/larsh/Documents/docker/finlytic}/assets/index:/app/assets/index:ro finlyticfundamentals: image: finlyticfundamentals build: context: . dockerfile: FinlyticFundamentals/Dockerfile restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_fundamentals;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_fundamentals finlyticsentiment: image: finlyticsentiment build: context: . dockerfile: FinlyticSentiment/Dockerfile restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_sentimental;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_sentiment - Webhooks__German=https://n8n.kleidukos.me/webhook/sentiment/de - Webhooks__English=https://n8n.kleidukos.me/webhook/sentiment/en # No volumes: FinlyticSentiment persists to PostgreSQL only. The # data/summaries mount here was never written to by this service # (Storage:SummariesPath in FinlyticSentiment/appsettings.json is dead # config) and other services now consume Sentiment's data via MQTT-RPC # instead of shared files, so the mount has been dropped. finlytictechnicals: image: finlytictechnicals build: context: . dockerfile: FinlyticTechnicals/Dockerfile restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_ta;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_technicals finlyticengine: image: finlyticengine build: context: . dockerfile: FinlyticEngine/Dockerfile restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_engine;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_engine - Ai__N8nValidationWebhookUrl=https://n8n.kleidukos.me/webhook/trade-validation finlyticsimulation: image: finlyticsimulation build: context: . dockerfile: FinlyticSimulation/Dockerfile restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_simulation;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_simulation finlyticbot: image: finlyticbot build: context: . dockerfile: FinlyticBot/Dockerfile restart: unless-stopped networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_bot;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_bot - Alpaca__KeyId=${ALPACA_KEY_ID:-PK_PAPER_PLACEHOLDER_KEY} - Alpaca__SecretKey=${ALPACA_SECRET_KEY:-SK_PAPER_PLACEHOLDER_SECRET} - Alpaca__IsPaper=true finlyticbackend: image: finlyticbackend build: context: . dockerfile: FinlyticBackend/Dockerfile # on-failure (bounded), NOT unless-stopped: this is the one service that # deliberately throws at startup if JWT_SECRET_KEY / ADMIN_DEFAULT_PASSWORD # are missing or too weak (see Program.cs fail-fast guards). An # unless-stopped policy would crash-loop that misconfiguration forever, # burning CPU/log volume while masking the real problem. A bounded # on-failure still recovers from transient startup races (e.g. DB not # yet reachable) but eventually settles into a visibly "Exited" container # (`docker compose ps`) once the retries are exhausted, surfacing a # persistent config error instead of hiding it. restart: on-failure:5 ports: - "5000:8080" networks: - postgres-network environment: - ConnectionStrings__DefaultConnection=Host=${DB_HOST:-OmniDB};Port=${DB_PORT:-5432};Database=finlytic_backend;Username=admin;Password=${DB_PASSWORD} - MQTT__Host=${MQTT_HOST:-host.docker.internal} - MQTT__Port=${MQTT_PORT:-4545} # See finlyticassets above: broker has no auth configured yet, do not enable. #- MQTT__Username=${MQTT_USERNAME:-admin} #- MQTT__Password=${MQTT_PASSWORD} - MQTT__ClientId=finlytic_backend - JWT__SecretKey=${JWT_SECRET_KEY} - ADMIN__DefaultPassword=${ADMIN_DEFAULT_PASSWORD} volumes: - ${FINLYTIC_DATA_ROOT:-C:/Users/larsh/Documents/docker/finlytic}/assets/index:/app/assets/index - ${FINLYTIC_DATA_ROOT:-C:/Users/larsh/Documents/docker/finlytic}/assets/logos:/app/assets/logos # Honest healthcheck: actually opens a TCP connection to the real Kestrel # port and parses the real HTTP status line from the real GET /health # endpoint (Program.cs, AllowAnonymous, no auth required). The final image # (mcr.microsoft.com/dotnet/aspnet:10.0) has neither curl nor wget # installed (verified) — installing one just for this would add an extra # apt layer, so instead we use bash's built-in /dev/tcp (bash itself IS # present in the base image, verified), invoked directly via exec form so # it does not go through /bin/sh (which is dash on this image and does # NOT support /dev/tcp). healthcheck: test: - CMD - bash - -c - >- exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && head -n1 <&3 | grep -q '200' interval: 30s timeout: 5s retries: 3 start_period: 20s # ────────────────────────────────────────────────────────────────────────── # No HEALTHCHECK on the 8 worker services above (finlyticassets, finlyticnews, # finlyticfundamentals, finlyticsentiment, finlytictechnicals, finlyticengine, # finlyticsimulation, finlyticbot) — and this is deliberate, not an omission: # # - They are Microsoft.NET.Sdk.Worker projects and MUST NOT host an HTTP # server (Rules.md §5), so there is no `GET /health`-style endpoint to # probe, by design. # - Docker already restarts/reports a dead PID 1 via the `restart` policy # above without any HEALTHCHECK — a HEALTHCHECK only adds value if it # can distinguish "process alive but broken" from "process alive and # working", which requires touching something specific to the app. # - The only things reachable from inside these containers without an # app-level probe endpoint are the external DB/MQTT dependencies # themselves (e.g. via bash's /dev/tcp, as used for finlyticbackend # above). But a bare TCP-reachability check to OmniDB/MQTT tests the # network path, not the worker — it would report "healthy" while the # worker is deadlocked, and "unhealthy" during a legitimate external # outage the worker's own retry logic is already handling. That is # placebo/misleading in both directions, not an honest signal. # # Conclusion: no meaningful, non-cosmetic healthcheck is possible here # without adding an HTTP endpoint (forbidden by Rules.md §5). Leaving # HEALTHCHECK unset is the honest choice. # ────────────────────────────────────────────────────────────────────────── networks: postgres-network: external: true