Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 522e3e476b | |||
| 64cd39844e | |||
| 5b69281bb8 |
+112
-32
@@ -1,20 +1,30 @@
|
|||||||
# TRM platform — deployment stack
|
# TRM platform — deployment stack (dev environment)
|
||||||
#
|
#
|
||||||
# Deployed via Portainer Repository Stack:
|
# Deployed via Komodo Stack-from-Git:
|
||||||
# Repository: git.dev.microservices.al/trm/deploy
|
# Repository: git.dev.trmtracking.org/trm/deploy
|
||||||
# Compose path: compose.yaml
|
# Compose path: compose.dev.yaml
|
||||||
# Branch: main
|
# Branch: dev
|
||||||
#
|
#
|
||||||
# Images are built and pushed by each service's own Gitea workflow.
|
# Images are built and pushed by each service's own Gitea Actions workflow
|
||||||
# This file references them by tag and runs them as a coordinated stack.
|
# (see each repo's .gitea/workflows/build.yml). This file references the
|
||||||
|
# resulting images by tag and runs them as a coordinated stack.
|
||||||
#
|
#
|
||||||
# Before first deploy on the host: `docker login git.dev.microservices.al`
|
# Before first deploy on the host: `docker login git.dev.trmtracking.org`
|
||||||
# (Portainer can store registry credentials in its UI; configure once.)
|
# (or configure registry credentials in Komodo's Image Registry Account).
|
||||||
#
|
#
|
||||||
# Environment variables are populated from Portainer's stack environment
|
# Environment variables are populated from Komodo's per-stack secrets
|
||||||
# config (or a `.env` file alongside this compose for non-Portainer hosts).
|
# config (or a `.env` file alongside this compose for non-Komodo hosts).
|
||||||
# Defaults are provided via `${VAR:-default}` so the stack starts with no
|
# Defaults are provided via `${VAR:-default}` so the stack starts with no
|
||||||
# explicit configuration on a fresh deploy.
|
# explicit configuration on a fresh deploy.
|
||||||
|
#
|
||||||
|
# Routing — fronted by Traefik on the host (single origin model required by
|
||||||
|
# the SPA's session cookie + Processor WebSocket upgrade; see
|
||||||
|
# react-spa.md and processor-ws-contract.md):
|
||||||
|
# app.dev.trmtracking.org/ → spa (catch-all SPA bundle)
|
||||||
|
# app.dev.trmtracking.org/api/* → directus (REST/GraphQL, /api stripped)
|
||||||
|
# app.dev.trmtracking.org/ws-business → directus (Directus's WS, served at this path natively)
|
||||||
|
# app.dev.trmtracking.org/ws-live → processor (live telemetry WS, /ws-live stripped)
|
||||||
|
# api.dev.trmtracking.org/ → directus (admin UI on its own subdomain)
|
||||||
|
|
||||||
name: trm
|
name: trm
|
||||||
|
|
||||||
@@ -38,10 +48,10 @@ services:
|
|||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# tcp-ingestion — Teltonika telemetry TCP server.
|
# tcp-ingestion — Teltonika telemetry TCP server.
|
||||||
# Built by git.dev.microservices.al/trm/tcp-ingestion's Gitea workflow.
|
# Built by git.dev.trmtracking.org/trm/tcp-ingestion's Gitea workflow.
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
tcp-ingestion:
|
tcp-ingestion:
|
||||||
image: git.dev.microservices.al/trm/tcp-ingestion:${TCP_INGESTION_TAG:-main}
|
image: git.dev.trmtracking.org/trm/tcp-ingestion:${TCP_INGESTION_TAG:-dev}
|
||||||
depends_on:
|
depends_on:
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -92,16 +102,23 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# processor — consumes telemetry from Redis, writes to Postgres.
|
# processor — consumes telemetry from Redis, writes to Postgres, and
|
||||||
# Built by git.dev.microservices.al/trm/processor's Gitea workflow.
|
# (post Phase 1.5) broadcasts live positions over its own WebSocket.
|
||||||
|
# Built by git.dev.trmtracking.org/trm/processor's Gitea workflow.
|
||||||
|
#
|
||||||
|
# Routed by Traefik at app.dev.trmtracking.org/ws-live (the host strips
|
||||||
|
# the prefix; processor hosts its WS server at root). The route is
|
||||||
|
# ready in advance of the live-broadcast feature landing.
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
processor:
|
processor:
|
||||||
image: git.dev.microservices.al/trm/processor:${PROCESSOR_TAG:-main}
|
image: git.dev.trmtracking.org/trm/processor:${PROCESSOR_TAG:-dev}
|
||||||
depends_on:
|
depends_on:
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
expose:
|
||||||
|
- '8081'
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
INSTANCE_ID: ${PROCESSOR_INSTANCE_ID:-processor-1}
|
INSTANCE_ID: ${PROCESSOR_INSTANCE_ID:-processor-1}
|
||||||
@@ -110,12 +127,25 @@ services:
|
|||||||
# to the same shared variable so they cannot drift.
|
# to the same shared variable so they cannot drift.
|
||||||
REDIS_TELEMETRY_STREAM: ${REDIS_TELEMETRY_STREAM:-telemetry:teltonika}
|
REDIS_TELEMETRY_STREAM: ${REDIS_TELEMETRY_STREAM:-telemetry:teltonika}
|
||||||
POSTGRES_URL: postgres://${POSTGRES_USER:-trm}:${POSTGRES_PASSWORD:-trm-pilot-change-me}@postgres:5432/${POSTGRES_DB:-trm}
|
POSTGRES_URL: postgres://${POSTGRES_USER:-trm}:${POSTGRES_PASSWORD:-trm-pilot-change-me}@postgres:5432/${POSTGRES_DB:-trm}
|
||||||
|
LIVE_WS_PORT: ${LIVE_WS_PORT:-8081}
|
||||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- proxy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=proxy"
|
||||||
|
- "traefik.http.routers.trm-processor-live.rule=Host(`app.dev.trmtracking.org`) && PathPrefix(`/ws-live`)"
|
||||||
|
- "traefik.http.routers.trm-processor-live.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.trm-processor-live.tls.certresolver=le"
|
||||||
|
- "traefik.http.routers.trm-processor-live.middlewares=trm-strip-ws-live"
|
||||||
|
- "traefik.http.middlewares.trm-strip-ws-live.stripprefix.prefixes=/ws-live"
|
||||||
|
- "traefik.http.services.trm-processor-live.loadbalancer.server.port=8081"
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# directus — business-plane API, admin UI, and schema authority.
|
# directus — business-plane API, admin UI, and schema authority.
|
||||||
# Built by git.dev.microservices.al/trm/directus's Gitea workflow.
|
# Built by git.dev.trmtracking.org/trm/directus's Gitea workflow.
|
||||||
#
|
#
|
||||||
# Boot pipeline (5 steps; see trm/directus/entrypoint.sh):
|
# Boot pipeline (5 steps; see trm/directus/entrypoint.sh):
|
||||||
# 1. db-init pre-schema → positions hypertable + faulty column
|
# 1. db-init pre-schema → positions hypertable + faulty column
|
||||||
@@ -135,18 +165,23 @@ services:
|
|||||||
# collections via the admin UI here will be DROPPED on the next image
|
# collections via the admin UI here will be DROPPED on the next image
|
||||||
# rebuild — schema-apply enforces the committed snapshot. See
|
# rebuild — schema-apply enforces the committed snapshot. See
|
||||||
# docs/wiki/entities/directus.md "destructive-apply hazard" callout.
|
# docs/wiki/entities/directus.md "destructive-apply hazard" callout.
|
||||||
|
#
|
||||||
|
# Routing: two Traefik routers point at the same backend.
|
||||||
|
# api.dev.trmtracking.org/* → admin UI + direct API access
|
||||||
|
# app.dev.trmtracking.org/api/* → SPA's REST/GraphQL surface (prefix stripped)
|
||||||
|
# app.dev.trmtracking.org/ws-business → SPA's business-plane WebSocket
|
||||||
|
# The /ws-business path is served by Directus natively via
|
||||||
|
# WEBSOCKETS_REST_PATH below; no Traefik rewrite is needed.
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
directus:
|
directus:
|
||||||
image: git.dev.microservices.al/trm/directus:${DIRECTUS_TAG:-main}
|
image: git.dev.trmtracking.org/trm/directus:${DIRECTUS_TAG:-dev}
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
expose:
|
expose:
|
||||||
# Internal-only. The admin UI + API are reachable from other services
|
# Internal-only. The admin UI + API are reachable from other services
|
||||||
# in the stack via service-name DNS (`http://directus:8055`). A reverse
|
# in the stack via service-name DNS (`http://directus:8055`). Traefik
|
||||||
# proxy (Traefik / Caddy / nginx) running on the host or attached to
|
# on the host fronts both public hostnames and forwards to this port.
|
||||||
# the `trm_default` network terminates TLS, applies its own auth /
|
|
||||||
# rate-limit / WAF rules, and forwards to this expose port.
|
|
||||||
#
|
#
|
||||||
# Why not host-publish 8055 directly: the admin UI is a privileged
|
# Why not host-publish 8055 directly: the admin UI is a privileged
|
||||||
# surface (full CRUD + permission policies + Flow execution). Direct
|
# surface (full CRUD + permission policies + Flow execution). Direct
|
||||||
@@ -167,7 +202,7 @@ services:
|
|||||||
# ----- Instance security — REQUIRED, must be unique per environment.
|
# ----- Instance security — REQUIRED, must be unique per environment.
|
||||||
# KEY: any UUID. SECRET: long random string, e.g. `openssl rand -hex 64`.
|
# KEY: any UUID. SECRET: long random string, e.g. `openssl rand -hex 64`.
|
||||||
# Two instances sharing the same KEY/SECRET produce colliding JWTs.
|
# Two instances sharing the same KEY/SECRET produce colliding JWTs.
|
||||||
# Defaults below are placeholders — REPLACE in the Portainer stack env.
|
# Defaults below are placeholders — REPLACE in Komodo's stack secrets.
|
||||||
KEY: ${DIRECTUS_KEY:-REPLACE-ME-WITH-A-UUID}
|
KEY: ${DIRECTUS_KEY:-REPLACE-ME-WITH-A-UUID}
|
||||||
SECRET: ${DIRECTUS_SECRET:-REPLACE-ME-WITH-A-LONG-RANDOM-STRING}
|
SECRET: ${DIRECTUS_SECRET:-REPLACE-ME-WITH-A-LONG-RANDOM-STRING}
|
||||||
|
|
||||||
@@ -179,9 +214,9 @@ services:
|
|||||||
ADMIN_PASSWORD: ${DIRECTUS_ADMIN_PASSWORD:-CHANGE-ON-FIRST-LOGIN}
|
ADMIN_PASSWORD: ${DIRECTUS_ADMIN_PASSWORD:-CHANGE-ON-FIRST-LOGIN}
|
||||||
|
|
||||||
# ----- Public-facing URL (used in emails, OAuth redirects, asset URLs).
|
# ----- Public-facing URL (used in emails, OAuth redirects, asset URLs).
|
||||||
# In real prod set to https://<your-domain>; default localhost is just
|
# Default = the admin canonical hostname so password-reset links land
|
||||||
# for first-deploy smoke testing.
|
# in the admin UI, not the SPA.
|
||||||
PUBLIC_URL: ${DIRECTUS_PUBLIC_URL:-http://localhost:8055}
|
PUBLIC_URL: ${DIRECTUS_PUBLIC_URL:-https://api.dev.trmtracking.org}
|
||||||
|
|
||||||
# ----- Logging -----
|
# ----- Logging -----
|
||||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||||
@@ -192,6 +227,9 @@ services:
|
|||||||
# carries the telemetry firehose). See live-channel-architecture
|
# carries the telemetry firehose). See live-channel-architecture
|
||||||
# in the wiki.
|
# in the wiki.
|
||||||
WEBSOCKETS_ENABLED: 'true'
|
WEBSOCKETS_ENABLED: 'true'
|
||||||
|
# Serve Directus's WS at the path the SPA expects under app.dev,
|
||||||
|
# avoiding a Traefik rewrite (cookies + upgrade headers stay clean).
|
||||||
|
WEBSOCKETS_REST_PATH: /ws-business
|
||||||
|
|
||||||
# ----- Cache / CORS — defaults disabled; enable per environment.
|
# ----- Cache / CORS — defaults disabled; enable per environment.
|
||||||
CACHE_ENABLED: ${DIRECTUS_CACHE_ENABLED:-false}
|
CACHE_ENABLED: ${DIRECTUS_CACHE_ENABLED:-false}
|
||||||
@@ -202,9 +240,35 @@ services:
|
|||||||
# snapshots/ + db-init/ are baked into the image, NOT mounted —
|
# snapshots/ + db-init/ are baked into the image, NOT mounted —
|
||||||
# that's the schema-as-code split.
|
# that's the schema-as-code split.
|
||||||
- directus-uploads:/directus/uploads
|
- directus-uploads:/directus/uploads
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- proxy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=proxy"
|
||||||
|
# Admin UI on its own subdomain (full surface, root path).
|
||||||
|
- "traefik.http.routers.trm-directus-admin.rule=Host(`api.dev.trmtracking.org`)"
|
||||||
|
- "traefik.http.routers.trm-directus-admin.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.trm-directus-admin.tls.certresolver=le"
|
||||||
|
- "traefik.http.routers.trm-directus-admin.service=trm-directus"
|
||||||
|
# SPA-side REST/GraphQL surface — strip /api prefix.
|
||||||
|
- "traefik.http.routers.trm-directus-api.rule=Host(`app.dev.trmtracking.org`) && PathPrefix(`/api`)"
|
||||||
|
- "traefik.http.routers.trm-directus-api.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.trm-directus-api.tls.certresolver=le"
|
||||||
|
- "traefik.http.routers.trm-directus-api.service=trm-directus"
|
||||||
|
- "traefik.http.routers.trm-directus-api.middlewares=trm-strip-api"
|
||||||
|
- "traefik.http.middlewares.trm-strip-api.stripprefix.prefixes=/api"
|
||||||
|
# SPA-side business-plane WebSocket — Directus serves at /ws-business
|
||||||
|
# natively (WEBSOCKETS_REST_PATH above), so no rewrite middleware.
|
||||||
|
- "traefik.http.routers.trm-directus-wsbiz.rule=Host(`app.dev.trmtracking.org`) && PathPrefix(`/ws-business`)"
|
||||||
|
- "traefik.http.routers.trm-directus-wsbiz.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.trm-directus-wsbiz.tls.certresolver=le"
|
||||||
|
- "traefik.http.routers.trm-directus-wsbiz.service=trm-directus"
|
||||||
|
# Single backend service shared by all three routers above.
|
||||||
|
- "traefik.http.services.trm-directus.loadbalancer.server.port=8055"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD-SHELL', 'wget -qO- http://localhost:8055/server/health || exit 1']
|
test: ['CMD-SHELL', 'wget -qO- http://127.0.0.1:8055/server/health || exit 1']
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
# First boot includes Directus's internal migrations (~30–45 s on
|
# First boot includes Directus's internal migrations (~30–45 s on
|
||||||
@@ -214,21 +278,23 @@ services:
|
|||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# spa — React + TypeScript single-page app for end-user operators.
|
# spa — React + TypeScript single-page app for end-user operators.
|
||||||
# Built by git.dev.microservices.al/trm/spa's Gitea workflow.
|
# Built by git.dev.trmtracking.org/trm/spa's Gitea workflow.
|
||||||
#
|
#
|
||||||
# The image is nginx serving a static bundle. The bundle's runtime
|
# The image is nginx serving a static bundle. The bundle's runtime
|
||||||
# config (Directus URL, Processor WS URL, optional Google Maps key,
|
# config (Directus URL, Processor WS URL, optional Google Maps key,
|
||||||
# env label) is read at first paint from /config.json — overridable
|
# env label) is read at first paint from /config.json — overridable
|
||||||
# via the volume mount below without rebuilding the image.
|
# via the volume mount below without rebuilding the image.
|
||||||
#
|
#
|
||||||
# Internal-only on the stack (expose: 80). The reverse proxy that
|
# Routed as the catch-all on app.dev.trmtracking.org. The /api,
|
||||||
# fronts directus also routes the SPA at the public domain root.
|
# /ws-business, and /ws-live PathPrefix routers above bind first;
|
||||||
|
# everything else falls through to this router.
|
||||||
|
#
|
||||||
# Same-origin is non-negotiable: the SPA's auth cookie + the
|
# Same-origin is non-negotiable: the SPA's auth cookie + the
|
||||||
# processor's WebSocket upgrade both rely on browser-attached cookies
|
# processor's WebSocket upgrade both rely on browser-attached cookies
|
||||||
# that only flow within one origin.
|
# that only flow within one origin.
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
spa:
|
spa:
|
||||||
image: git.dev.microservices.al/trm/spa:${SPA_TAG:-main}
|
image: git.dev.trmtracking.org/trm/spa:${SPA_TAG:-dev}
|
||||||
expose:
|
expose:
|
||||||
- '80'
|
- '80'
|
||||||
volumes:
|
volumes:
|
||||||
@@ -238,9 +304,18 @@ services:
|
|||||||
# edit the URLs / env label / optional Google Maps key. Read-only
|
# edit the URLs / env label / optional Google Maps key. Read-only
|
||||||
# mount — the container can't accidentally write to its own config.
|
# mount — the container can't accidentally write to its own config.
|
||||||
- ${SPA_CONFIG_FILE:-./spa-config.json}:/usr/share/nginx/html/config.json:ro
|
- ${SPA_CONFIG_FILE:-./spa-config.json}:/usr/share/nginx/html/config.json:ro
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=proxy"
|
||||||
|
- "traefik.http.routers.trm-spa.rule=Host(`app.dev.trmtracking.org`)"
|
||||||
|
- "traefik.http.routers.trm-spa.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.trm-spa.tls.certresolver=le"
|
||||||
|
- "traefik.http.services.trm-spa.loadbalancer.server.port=80"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD-SHELL', 'wget -qO- http://localhost/ || exit 1']
|
test: ['CMD-SHELL', 'wget -qO- http://127.0.0.1/ || exit 1']
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
@@ -250,3 +325,8 @@ volumes:
|
|||||||
redis-data:
|
redis-data:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
directus-uploads:
|
directus-uploads:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default: {}
|
||||||
|
proxy:
|
||||||
|
external: true
|
||||||
Reference in New Issue
Block a user