chore: centralize docker-compose variables into .env
All checks were successful
Deploy to Staging / Build Images (push) Successful in 39s
Deploy to Staging / Deploy to Staging (push) Successful in 52s
Deploy to Staging / Verify Staging (push) Successful in 9s
Deploy to Staging / Notify Staging Ready (push) Successful in 8s
Deploy to Staging / Notify Staging Failure (push) Has been skipped

Stripe Price IDs were hardcoded and duplicated across 4 compose files.
Log levels were hardcoded per-overlay instead of using generate-log-config.sh.
This refactors all environment-specific variables into a single .env file
that CI/CD generates from Gitea repo variables + generate-log-config.sh.

- Add .env.example template with documented variables
- Replace hardcoded values with ${VAR:-default} substitution in base compose
- Simplify prod overlay from 90 to 32 lines (remove redundant env blocks)
- Add YAML anchors to blue-green overlay (eliminate blue/green duplication)
- Remove redundant OCR env block from staging overlay
- Change generate-log-config.sh to output to stdout (pipe into .env)
- Update staging/production CI/CD to generate .env with Stripe + log vars
- Remove dangerous pk_live_ default from VITE_STRIPE_PUBLISHABLE_KEY

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-02-16 19:57:36 -06:00
parent 07c3d8511d
commit 853a075e8b
8 changed files with 158 additions and 237 deletions

36
.env.example Normal file
View File

@@ -0,0 +1,36 @@
# MotoVaultPro Environment Configuration
# Copy to .env and fill in environment-specific values
# Generated .env files should NOT be committed to version control
#
# Local dev: No .env needed -- base docker-compose.yml defaults are sandbox values
# Staging/Production: CI/CD generates .env from Gitea variables + generate-log-config.sh
# ===========================================
# Stripe Price IDs (environment-specific)
# ===========================================
# Sandbox defaults used for local development
STRIPE_PRO_MONTHLY_PRICE_ID=price_1T1ZHMJXoKkh5RcKwKSSGIlR
STRIPE_PRO_YEARLY_PRICE_ID=price_1T1ZHnJXoKkh5RcKWlG2MPpX
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=price_1T1ZIBJXoKkh5RcKu2jyhqBN
STRIPE_ENTERPRISE_YEARLY_PRICE_ID=price_1T1ZIQJXoKkh5RcK34YXiJQm
# ===========================================
# Stripe Publishable Key (baked into frontend at build time)
# ===========================================
# VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...
# ===========================================
# Log Levels (generated by scripts/ci/generate-log-config.sh)
# ===========================================
# Run: ./scripts/ci/generate-log-config.sh DEBUG >> .env
#
# BACKEND_LOG_LEVEL=debug
# TRAEFIK_LOG_LEVEL=DEBUG
# POSTGRES_LOG_STATEMENT=all
# POSTGRES_LOG_MIN_DURATION=0
# REDIS_LOGLEVEL=debug
# ===========================================
# Grafana
# ===========================================
# GRAFANA_ADMIN_PASSWORD=admin

View File

@@ -99,6 +99,7 @@ jobs:
docker-compose.yml docker-compose.yml
docker-compose.blue-green.yml docker-compose.blue-green.yml
docker-compose.prod.yml docker-compose.prod.yml
.env.example
sparse-checkout-cone-mode: false sparse-checkout-cone-mode: false
fetch-depth: 1 fetch-depth: 1
@@ -115,11 +116,20 @@ jobs:
mkdir -p "$DEPLOY_PATH/secrets/app" mkdir -p "$DEPLOY_PATH/secrets/app"
cp "$GITHUB_WORKSPACE/secrets/app/google-wif-config.json" "$DEPLOY_PATH/secrets/app/" cp "$GITHUB_WORKSPACE/secrets/app/google-wif-config.json" "$DEPLOY_PATH/secrets/app/"
- name: Generate logging configuration - name: Generate environment configuration
run: | run: |
cd "$DEPLOY_PATH" cd "$DEPLOY_PATH"
{
echo "# Generated by CI/CD - DO NOT EDIT"
echo "STRIPE_PRO_MONTHLY_PRICE_ID=${{ vars.STRIPE_PRO_MONTHLY_PRICE_ID }}"
echo "STRIPE_PRO_YEARLY_PRICE_ID=${{ vars.STRIPE_PRO_YEARLY_PRICE_ID }}"
echo "STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=${{ vars.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID }}"
echo "STRIPE_ENTERPRISE_YEARLY_PRICE_ID=${{ vars.STRIPE_ENTERPRISE_YEARLY_PRICE_ID }}"
echo "VITE_STRIPE_PUBLISHABLE_KEY=${{ vars.VITE_STRIPE_PUBLISHABLE_KEY }}"
echo "GRAFANA_ADMIN_PASSWORD=${{ secrets.GRAFANA_ADMIN_PASSWORD }}"
} > .env
chmod +x scripts/ci/generate-log-config.sh chmod +x scripts/ci/generate-log-config.sh
./scripts/ci/generate-log-config.sh "$LOG_LEVEL" ./scripts/ci/generate-log-config.sh "$LOG_LEVEL" >> .env
- name: Login to registry - name: Login to registry
run: | run: |

View File

@@ -124,11 +124,20 @@ jobs:
mkdir -p "$DEPLOY_PATH/secrets/app" mkdir -p "$DEPLOY_PATH/secrets/app"
cp "$GITHUB_WORKSPACE/secrets/app/google-wif-config.json" "$DEPLOY_PATH/secrets/app/" cp "$GITHUB_WORKSPACE/secrets/app/google-wif-config.json" "$DEPLOY_PATH/secrets/app/"
- name: Generate logging configuration - name: Generate environment configuration
run: | run: |
cd "$DEPLOY_PATH" cd "$DEPLOY_PATH"
{
echo "# Generated by CI/CD - DO NOT EDIT"
echo "STRIPE_PRO_MONTHLY_PRICE_ID=${{ vars.STRIPE_PRO_MONTHLY_PRICE_ID }}"
echo "STRIPE_PRO_YEARLY_PRICE_ID=${{ vars.STRIPE_PRO_YEARLY_PRICE_ID }}"
echo "STRIPE_ENTERPRISE_MONTHLY_PRICE_ID=${{ vars.STRIPE_ENTERPRISE_MONTHLY_PRICE_ID }}"
echo "STRIPE_ENTERPRISE_YEARLY_PRICE_ID=${{ vars.STRIPE_ENTERPRISE_YEARLY_PRICE_ID }}"
echo "VITE_STRIPE_PUBLISHABLE_KEY=${{ vars.VITE_STRIPE_PUBLISHABLE_KEY }}"
echo "GRAFANA_ADMIN_PASSWORD=${{ secrets.GRAFANA_ADMIN_PASSWORD }}"
} > .env
chmod +x scripts/ci/generate-log-config.sh chmod +x scripts/ci/generate-log-config.sh
./scripts/ci/generate-log-config.sh "$LOG_LEVEL" ./scripts/ci/generate-log-config.sh "$LOG_LEVEL" >> .env
- name: Login to registry - name: Login to registry
run: | run: |

View File

@@ -11,6 +11,63 @@
# Shared services (from base compose): # Shared services (from base compose):
# mvp-traefik, mvp-postgres, mvp-redis # mvp-traefik, mvp-postgres, mvp-redis
# ========================================
# Extension fields (YAML anchors for DRY)
# ========================================
x-frontend-env: &frontend-env
VITE_API_BASE_URL: /api
VITE_AUTH0_DOMAIN: ${VITE_AUTH0_DOMAIN:-motovaultpro.us.auth0.com}
VITE_AUTH0_CLIENT_ID: ${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3}
VITE_AUTH0_AUDIENCE: ${VITE_AUTH0_AUDIENCE:-https://api.motovaultpro.com}
SECRETS_DIR: /run/secrets
x-frontend-volumes: &frontend-volumes
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
- ./secrets/app/google-maps-map-id.txt:/run/secrets/google-maps-map-id:ro
x-frontend-healthcheck: &frontend-healthcheck
test: ["CMD-SHELL", "curl -sf http://localhost:3000 || exit 1"]
interval: 5s
timeout: 5s
retries: 3
start_period: 10s
x-backend-env: &backend-env
NODE_ENV: production
CONFIG_PATH: /app/config/production.yml
SECRETS_DIR: /run/secrets
LOG_LEVEL: ${BACKEND_LOG_LEVEL:-debug}
DATABASE_HOST: mvp-postgres
REDIS_HOST: mvp-redis
STRIPE_PRO_MONTHLY_PRICE_ID: ${STRIPE_PRO_MONTHLY_PRICE_ID:-price_1T1ZHMJXoKkh5RcKwKSSGIlR}
STRIPE_PRO_YEARLY_PRICE_ID: ${STRIPE_PRO_YEARLY_PRICE_ID:-price_1T1ZHnJXoKkh5RcKWlG2MPpX}
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: ${STRIPE_ENTERPRISE_MONTHLY_PRICE_ID:-price_1T1ZIBJXoKkh5RcKu2jyhqBN}
STRIPE_ENTERPRISE_YEARLY_PRICE_ID: ${STRIPE_ENTERPRISE_YEARLY_PRICE_ID:-price_1T1ZIQJXoKkh5RcK34YXiJQm}
x-backend-volumes: &backend-volumes
- ./config/app/production.yml:/app/config/production.yml:ro
- ./config/shared/production.yml:/app/config/shared.yml:ro
- ./secrets/app/postgres-password.txt:/run/secrets/postgres-password:ro
- ./secrets/app/auth0-client-secret.txt:/run/secrets/auth0-client-secret:ro
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
- ./secrets/app/google-maps-map-id.txt:/run/secrets/google-maps-map-id:ro
- ./secrets/app/resend-api-key.txt:/run/secrets/resend-api-key:ro
- ./secrets/app/auth0-management-client-id.txt:/run/secrets/auth0-management-client-id:ro
- ./secrets/app/auth0-management-client-secret.txt:/run/secrets/auth0-management-client-secret:ro
- ./secrets/app/stripe-secret-key.txt:/run/secrets/stripe-secret-key:ro
- ./secrets/app/stripe-webhook-secret.txt:/run/secrets/stripe-webhook-secret:ro
- ./data/documents:/app/data/documents
- ./data/backups:/app/data/backups
x-backend-healthcheck: &backend-healthcheck
test:
- CMD-SHELL
- node -e "require('http').get('http://localhost:3001/health', r => process.exit(r.statusCode===200?0:1)).on('error', () => process.exit(1))"
interval: 5s
timeout: 5s
retries: 5
start_period: 180s
services: services:
# ======================================== # ========================================
# BLUE Stack - Frontend # BLUE Stack - Frontend
@@ -19,25 +76,13 @@ services:
image: ${FRONTEND_IMAGE:-git.motovaultpro.com/egullickson/frontend:latest} image: ${FRONTEND_IMAGE:-git.motovaultpro.com/egullickson/frontend:latest}
container_name: mvp-frontend-blue container_name: mvp-frontend-blue
restart: unless-stopped restart: unless-stopped
environment: environment: *frontend-env
VITE_API_BASE_URL: /api volumes: *frontend-volumes
VITE_AUTH0_DOMAIN: ${VITE_AUTH0_DOMAIN:-motovaultpro.us.auth0.com}
VITE_AUTH0_CLIENT_ID: ${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3}
VITE_AUTH0_AUDIENCE: ${VITE_AUTH0_AUDIENCE:-https://api.motovaultpro.com}
SECRETS_DIR: /run/secrets
volumes:
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
- ./secrets/app/google-maps-map-id.txt:/run/secrets/google-maps-map-id:ro
networks: networks:
- frontend - frontend
depends_on: depends_on:
- mvp-backend-blue - mvp-backend-blue
healthcheck: healthcheck: *frontend-healthcheck
test: ["CMD-SHELL", "curl -sf http://localhost:3000 || exit 1"]
interval: 5s
timeout: 5s
retries: 3
start_period: 10s
deploy: deploy:
resources: resources:
limits: limits:
@@ -55,50 +100,15 @@ services:
image: ${BACKEND_IMAGE:-git.motovaultpro.com/egullickson/backend:latest} image: ${BACKEND_IMAGE:-git.motovaultpro.com/egullickson/backend:latest}
container_name: mvp-backend-blue container_name: mvp-backend-blue
restart: unless-stopped restart: unless-stopped
environment: environment: *backend-env
NODE_ENV: production volumes: *backend-volumes
CONFIG_PATH: /app/config/production.yml
SECRETS_DIR: /run/secrets
DATABASE_HOST: mvp-postgres
REDIS_HOST: mvp-redis
# Production Variables
#STRIPE_PRO_MONTHLY_PRICE_ID: prod_Toj6BG9Z9JwREl
#STRIPE_PRO_YEARLY_PRICE_ID: prod_Toj8oo0RpVBQmB
#STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: prod_Toj8xGEui9jl6j
#STRIPE_ENTERPRISE_YEARLY_PRICE_ID: prod_Toj9A7A773xrdn
# Sandbox Variables
STRIPE_PRO_MONTHLY_PRICE_ID: price_1T1ZHMJXoKkh5RcKwKSSGIlR
STRIPE_PRO_YEARLY_PRICE_ID: price_1T1ZHnJXoKkh5RcKWlG2MPpX
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: price_1T1ZIBJXoKkh5RcKu2jyhqBN
STRIPE_ENTERPRISE_YEARLY_PRICE_ID: price_1T1ZIQJXoKkh5RcK34YXiJQm
volumes:
- ./config/app/production.yml:/app/config/production.yml:ro
- ./config/shared/production.yml:/app/config/shared.yml:ro
- ./secrets/app/postgres-password.txt:/run/secrets/postgres-password:ro
- ./secrets/app/auth0-client-secret.txt:/run/secrets/auth0-client-secret:ro
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
- ./secrets/app/google-maps-map-id.txt:/run/secrets/google-maps-map-id:ro
- ./secrets/app/resend-api-key.txt:/run/secrets/resend-api-key:ro
- ./secrets/app/auth0-management-client-id.txt:/run/secrets/auth0-management-client-id:ro
- ./secrets/app/auth0-management-client-secret.txt:/run/secrets/auth0-management-client-secret:ro
- ./secrets/app/stripe-secret-key.txt:/run/secrets/stripe-secret-key:ro
- ./secrets/app/stripe-webhook-secret.txt:/run/secrets/stripe-webhook-secret:ro
- ./data/documents:/app/data/documents
- ./data/backups:/app/data/backups
networks: networks:
- backend - backend
- database - database
depends_on: depends_on:
- mvp-postgres - mvp-postgres
- mvp-redis - mvp-redis
healthcheck: healthcheck: *backend-healthcheck
test:
- CMD-SHELL
- node -e "require('http').get('http://localhost:3001/health', r => process.exit(r.statusCode===200?0:1)).on('error', () => process.exit(1))"
interval: 5s
timeout: 5s
retries: 5
start_period: 180s
deploy: deploy:
resources: resources:
limits: limits:
@@ -116,25 +126,13 @@ services:
image: ${FRONTEND_IMAGE:-git.motovaultpro.com/egullickson/frontend:latest} image: ${FRONTEND_IMAGE:-git.motovaultpro.com/egullickson/frontend:latest}
container_name: mvp-frontend-green container_name: mvp-frontend-green
restart: unless-stopped restart: unless-stopped
environment: environment: *frontend-env
VITE_API_BASE_URL: /api volumes: *frontend-volumes
VITE_AUTH0_DOMAIN: ${VITE_AUTH0_DOMAIN:-motovaultpro.us.auth0.com}
VITE_AUTH0_CLIENT_ID: ${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3}
VITE_AUTH0_AUDIENCE: ${VITE_AUTH0_AUDIENCE:-https://api.motovaultpro.com}
SECRETS_DIR: /run/secrets
volumes:
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
- ./secrets/app/google-maps-map-id.txt:/run/secrets/google-maps-map-id:ro
networks: networks:
- frontend - frontend
depends_on: depends_on:
- mvp-backend-green - mvp-backend-green
healthcheck: healthcheck: *frontend-healthcheck
test: ["CMD-SHELL", "curl -sf http://localhost:3000 || exit 1"]
interval: 5s
timeout: 5s
retries: 3
start_period: 10s
deploy: deploy:
resources: resources:
limits: limits:
@@ -152,51 +150,15 @@ services:
image: ${BACKEND_IMAGE:-git.motovaultpro.com/egullickson/backend:latest} image: ${BACKEND_IMAGE:-git.motovaultpro.com/egullickson/backend:latest}
container_name: mvp-backend-green container_name: mvp-backend-green
restart: unless-stopped restart: unless-stopped
environment: environment: *backend-env
NODE_ENV: production volumes: *backend-volumes
CONFIG_PATH: /app/config/production.yml
SECRETS_DIR: /run/secrets
DATABASE_HOST: mvp-postgres
REDIS_HOST: mvp-redis
# Production Variables
#STRIPE_PRO_MONTHLY_PRICE_ID: prod_Toj6BG9Z9JwREl
#STRIPE_PRO_YEARLY_PRICE_ID: prod_Toj8oo0RpVBQmB
#STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: prod_Toj8xGEui9jl6j
#STRIPE_ENTERPRISE_YEARLY_PRICE_ID: prod_Toj9A7A773xrdn
# Sandbox Variables
STRIPE_PRO_MONTHLY_PRICE_ID: price_1T1ZHMJXoKkh5RcKwKSSGIlR
STRIPE_PRO_YEARLY_PRICE_ID: price_1T1ZHnJXoKkh5RcKWlG2MPpX
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: price_1T1ZIBJXoKkh5RcKu2jyhqBN
STRIPE_ENTERPRISE_YEARLY_PRICE_ID: price_1T1ZIQJXoKkh5RcK34YXiJQm
volumes:
- ./config/app/production.yml:/app/config/production.yml:ro
- ./config/shared/production.yml:/app/config/shared.yml:ro
- ./secrets/app/postgres-password.txt:/run/secrets/postgres-password:ro
- ./secrets/app/auth0-client-secret.txt:/run/secrets/auth0-client-secret:ro
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
- ./secrets/app/google-maps-map-id.txt:/run/secrets/google-maps-map-id:ro
- ./secrets/app/resend-api-key.txt:/run/secrets/resend-api-key:ro
- ./secrets/app/auth0-management-client-id.txt:/run/secrets/auth0-management-client-id:ro
- ./secrets/app/auth0-management-client-secret.txt:/run/secrets/auth0-management-client-secret:ro
- ./secrets/app/stripe-secret-key.txt:/run/secrets/stripe-secret-key:ro
- ./secrets/app/stripe-webhook-secret.txt:/run/secrets/stripe-webhook-secret:ro
- ./data/documents:/app/data/documents
- ./data/backups:/app/data/backups
networks: networks:
- backend - backend
- database - database
depends_on: depends_on:
- mvp-postgres - mvp-postgres
- mvp-redis - mvp-redis
healthcheck: healthcheck: *backend-healthcheck
test:
- CMD-SHELL
- node -e "require('http').get('http://localhost:3001/health', r => process.exit(r.statusCode===200?0:1)).on('error', () => process.exit(1))"
interval: 5s
timeout: 5s
retries: 5
start_period: 180s
deploy: deploy:
resources: resources:
limits: limits:

View File

@@ -6,18 +6,14 @@
# #
# This file removes development-only configurations: # This file removes development-only configurations:
# - Database port exposure (PostgreSQL, Redis) # - Database port exposure (PostgreSQL, Redis)
# - Development-specific settings # - Traefik dashboard auth middleware
#
# Environment-specific values (log levels, Stripe IDs) are driven by .env
# generated by CI/CD from Gitea variables + scripts/ci/generate-log-config.sh
services: services:
# Traefik - Production log level and dashboard auth # Traefik - Dashboard auth middleware
mvp-traefik: mvp-traefik:
environment:
# Traefik log levels: TRACE | DEBUG | INFO | WARN | ERROR
LOG_LEVEL: error
command:
- --configFile=/etc/traefik/traefik.yml
# Traefik log levels: TRACE | DEBUG | INFO | WARN | ERROR
- --log.level=ERROR
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.motovaultpro.local`)" - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.motovaultpro.local`)"
@@ -26,64 +22,10 @@ services:
- "traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080" - "traefik.http.services.traefik-dashboard.loadbalancer.server.port=8080"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$10$$foobar" - "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$10$$foobar"
# Backend - Production log level # PostgreSQL - Remove dev ports
mvp-backend:
environment:
NODE_ENV: production
CONFIG_PATH: /app/config/production.yml
SECRETS_DIR: /run/secrets
# Pino log levels: trace | debug | info | warn | error | fatal
LOG_LEVEL: error
DATABASE_HOST: mvp-postgres
REDIS_HOST: mvp-redis
# Production Variables
#STRIPE_PRO_MONTHLY_PRICE_ID: prod_Toj6BG9Z9JwREl
#STRIPE_PRO_YEARLY_PRICE_ID: prod_Toj8oo0RpVBQmB
#STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: prod_Toj8xGEui9jl6j
#STRIPE_ENTERPRISE_YEARLY_PRICE_ID: prod_Toj9A7A773xrdn
# Sandbox Variables
STRIPE_PRO_MONTHLY_PRICE_ID: price_1T1ZHMJXoKkh5RcKwKSSGIlR
STRIPE_PRO_YEARLY_PRICE_ID: price_1T1ZHnJXoKkh5RcKWlG2MPpX
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: price_1T1ZIBJXoKkh5RcKu2jyhqBN
STRIPE_ENTERPRISE_YEARLY_PRICE_ID: price_1T1ZIQJXoKkh5RcK34YXiJQm
# OCR - Production log level + engine config
mvp-ocr:
environment:
# Python log levels: DEBUG | INFO | WARNING | ERROR | CRITICAL
LOG_LEVEL: error
REDIS_HOST: mvp-redis
REDIS_PORT: 6379
REDIS_DB: 1
# OCR engine configuration (Google Vision primary, PaddleOCR fallback)
OCR_PRIMARY_ENGINE: google_vision
OCR_FALLBACK_ENGINE: paddleocr
OCR_CONFIDENCE_THRESHOLD: "0.6"
OCR_FALLBACK_THRESHOLD: "0.6"
GOOGLE_VISION_KEY_PATH: /run/secrets/google-wif-config.json
VISION_MONTHLY_LIMIT: "1000"
# Vertex AI / Gemini configuration (maintenance schedule extraction)
VERTEX_AI_PROJECT: motovaultpro
VERTEX_AI_LOCATION: us-central1
GEMINI_MODEL: gemini-2.5-flash
# PostgreSQL - Remove dev ports, production log level
mvp-postgres: mvp-postgres:
ports: [] ports: []
environment:
POSTGRES_DB: motovaultpro
POSTGRES_USER: postgres
POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password
POSTGRES_INITDB_ARGS: --encoding=UTF8
LOG_LEVEL: error
# PostgreSQL log statements: none | ddl | mod | all
POSTGRES_LOG_STATEMENT: none
# Minimum query duration to log: -1 (disabled) | 0 (all) | N (ms threshold)
POSTGRES_LOG_MIN_DURATION_STATEMENT: -1
PGDATA: /var/lib/postgresql/data/pgdata
# Redis - Remove dev ports, production log level # Redis - Remove dev ports
mvp-redis: mvp-redis:
ports: [] ports: []
# Redis log levels: debug | verbose | notice | warning
command: redis-server --appendonly yes --loglevel warning

View File

@@ -63,27 +63,6 @@ services:
mvp-ocr: mvp-ocr:
image: ${OCR_IMAGE:-git.motovaultpro.com/egullickson/ocr:latest} image: ${OCR_IMAGE:-git.motovaultpro.com/egullickson/ocr:latest}
container_name: mvp-ocr-staging container_name: mvp-ocr-staging
environment:
# Python log levels: DEBUG | INFO | WARNING | ERROR | CRITICAL
LOG_LEVEL: debug
REDIS_HOST: mvp-redis
REDIS_PORT: 6379
REDIS_DB: 1
# OCR engine configuration (Google Vision primary, PaddleOCR fallback)
OCR_PRIMARY_ENGINE: google_vision
OCR_FALLBACK_ENGINE: paddleocr
OCR_CONFIDENCE_THRESHOLD: "0.6"
OCR_FALLBACK_THRESHOLD: "0.6"
GOOGLE_VISION_KEY_PATH: /run/secrets/google-wif-config.json
VISION_MONTHLY_LIMIT: "1000"
# Vertex AI / Gemini configuration (maintenance schedule extraction)
VERTEX_AI_PROJECT: motovaultpro
VERTEX_AI_LOCATION: us-central1
GEMINI_MODEL: gemini-2.5-flash
volumes:
- ./secrets/app/auth0-ocr-client-id.txt:/run/secrets/auth0-ocr-client-id:ro
- ./secrets/app/auth0-ocr-client-secret.txt:/run/secrets/auth0-ocr-client-secret:ro
- ./secrets/app/google-wif-config.json:/run/secrets/google-wif-config.json:ro
# ======================================== # ========================================
# PostgreSQL (Staging - Separate Database) # PostgreSQL (Staging - Separate Database)

View File

@@ -11,8 +11,9 @@ services:
command: command:
- --configFile=/etc/traefik/traefik.yml - --configFile=/etc/traefik/traefik.yml
environment: environment:
# Traefik log levels: TRACE | DEBUG | INFO | WARN | ERROR # Traefik natively reads TRAEFIK_LOG_LEVEL (maps to --log.level)
LOG_LEVEL: debug # Levels: TRACE | DEBUG | INFO | WARN | ERROR
TRAEFIK_LOG_LEVEL: ${TRAEFIK_LOG_LEVEL:-DEBUG}
CLOUDFLARE_DNS_API_TOKEN_FILE: /run/secrets/cloudflare-dns-token CLOUDFLARE_DNS_API_TOKEN_FILE: /run/secrets/cloudflare-dns-token
ports: ports:
- "80:80" - "80:80"
@@ -60,7 +61,7 @@ services:
VITE_AUTH0_CLIENT_ID: ${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3} VITE_AUTH0_CLIENT_ID: ${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3}
VITE_AUTH0_AUDIENCE: ${VITE_AUTH0_AUDIENCE:-https://api.motovaultpro.com} VITE_AUTH0_AUDIENCE: ${VITE_AUTH0_AUDIENCE:-https://api.motovaultpro.com}
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api} VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api}
VITE_STRIPE_PUBLISHABLE_KEY: ${VITE_STRIPE_PUBLISHABLE_KEY:-pk_live_51Sr2yQJk87CpWj04YNBIaUWUtnJjeVTgk5NqHdpjqxgsbjy3dMKkIsqhjcpSkCzp3KvLi23BGgxhwV021EnEW3H400HhPYVyfN} VITE_STRIPE_PUBLISHABLE_KEY: ${VITE_STRIPE_PUBLISHABLE_KEY}
container_name: mvp-frontend container_name: mvp-frontend
restart: unless-stopped restart: unless-stopped
environment: environment:
@@ -115,20 +116,15 @@ services:
CONFIG_PATH: /app/config/production.yml CONFIG_PATH: /app/config/production.yml
SECRETS_DIR: /run/secrets SECRETS_DIR: /run/secrets
# Pino log levels: trace | debug | info | warn | error | fatal # Pino log levels: trace | debug | info | warn | error | fatal
LOG_LEVEL: debug LOG_LEVEL: ${BACKEND_LOG_LEVEL:-debug}
# Service references # Service references
DATABASE_HOST: mvp-postgres DATABASE_HOST: mvp-postgres
REDIS_HOST: mvp-redis REDIS_HOST: mvp-redis
# Production Variables # Stripe Price IDs (override via .env for staging/production)
#STRIPE_PRO_MONTHLY_PRICE_ID: prod_Toj6BG9Z9JwREl STRIPE_PRO_MONTHLY_PRICE_ID: ${STRIPE_PRO_MONTHLY_PRICE_ID:-price_1T1ZHMJXoKkh5RcKwKSSGIlR}
#STRIPE_PRO_YEARLY_PRICE_ID: prod_Toj8oo0RpVBQmB STRIPE_PRO_YEARLY_PRICE_ID: ${STRIPE_PRO_YEARLY_PRICE_ID:-price_1T1ZHnJXoKkh5RcKWlG2MPpX}
#STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: prod_Toj8xGEui9jl6j STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: ${STRIPE_ENTERPRISE_MONTHLY_PRICE_ID:-price_1T1ZIBJXoKkh5RcKu2jyhqBN}
#STRIPE_ENTERPRISE_YEARLY_PRICE_ID: prod_Toj9A7A773xrdn STRIPE_ENTERPRISE_YEARLY_PRICE_ID: ${STRIPE_ENTERPRISE_YEARLY_PRICE_ID:-price_1T1ZIQJXoKkh5RcK34YXiJQm}
# Sandbox Variables
STRIPE_PRO_MONTHLY_PRICE_ID: price_1T1ZHMJXoKkh5RcKwKSSGIlR
STRIPE_PRO_YEARLY_PRICE_ID: price_1T1ZHnJXoKkh5RcKWlG2MPpX
STRIPE_ENTERPRISE_MONTHLY_PRICE_ID: price_1T1ZIBJXoKkh5RcKu2jyhqBN
STRIPE_ENTERPRISE_YEARLY_PRICE_ID: price_1T1ZIQJXoKkh5RcK34YXiJQm
volumes: volumes:
# Configuration files (K8s ConfigMap equivalent) # Configuration files (K8s ConfigMap equivalent)
- ./config/app/production.yml:/app/config/production.yml:ro - ./config/app/production.yml:/app/config/production.yml:ro
@@ -197,7 +193,7 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
# Python log levels: DEBUG | INFO | WARNING | ERROR | CRITICAL # Python log levels: DEBUG | INFO | WARNING | ERROR | CRITICAL
LOG_LEVEL: debug LOG_LEVEL: ${BACKEND_LOG_LEVEL:-debug}
REDIS_HOST: mvp-redis REDIS_HOST: mvp-redis
REDIS_PORT: 6379 REDIS_PORT: 6379
REDIS_DB: 1 REDIS_DB: 1
@@ -244,11 +240,11 @@ services:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password
POSTGRES_INITDB_ARGS: --encoding=UTF8 POSTGRES_INITDB_ARGS: --encoding=UTF8
LOG_LEVEL: debug LOG_LEVEL: ${BACKEND_LOG_LEVEL:-debug}
# PostgreSQL log statements: none | ddl | mod | all # PostgreSQL log statements: none | ddl | mod | all
POSTGRES_LOG_STATEMENT: all POSTGRES_LOG_STATEMENT: ${POSTGRES_LOG_STATEMENT:-all}
# Minimum query duration to log: -1 (disabled) | 0 (all) | N (ms threshold) # Minimum query duration to log: -1 (disabled) | 0 (all) | N (ms threshold)
POSTGRES_LOG_MIN_DURATION_STATEMENT: 0 POSTGRES_LOG_MIN_DURATION_STATEMENT: ${POSTGRES_LOG_MIN_DURATION:-0}
PGDATA: /var/lib/postgresql/data/pgdata PGDATA: /var/lib/postgresql/data/pgdata
volumes: volumes:
- mvp_postgres_data:/var/lib/postgresql/data/pgdata - mvp_postgres_data:/var/lib/postgresql/data/pgdata
@@ -276,7 +272,7 @@ services:
container_name: mvp-redis container_name: mvp-redis
restart: unless-stopped restart: unless-stopped
# Redis log levels: debug | verbose | notice | warning # Redis log levels: debug | verbose | notice | warning
command: redis-server --appendonly yes --loglevel debug command: redis-server --appendonly yes --loglevel ${REDIS_LOGLEVEL:-debug}
volumes: volumes:
- mvp_redis_data:/data - mvp_redis_data:/data
networks: networks:

View File

@@ -1,11 +1,12 @@
#!/bin/bash #!/bin/bash
# generate-log-config.sh - Generate .env.logging from LOG_LEVEL # generate-log-config.sh - Generate log-level environment variables
# Maps a single LOG_LEVEL environment variable to per-container settings # Maps a single LOG_LEVEL to per-container settings and writes to stdout
# #
# Usage: ./generate-log-config.sh [LOG_LEVEL] # Usage: ./generate-log-config.sh [LOG_LEVEL]
# LOG_LEVEL: DEBUG, INFO, WARN, or ERROR (default: INFO) # LOG_LEVEL: DEBUG, INFO, WARN, or ERROR (default: INFO)
# #
# Output: Creates .env.logging file with container-specific log settings # Output: Log configuration variables on stdout (append to .env)
# Example: ./generate-log-config.sh INFO >> .env
# #
# Exit codes: # Exit codes:
# 0 - Configuration generated successfully # 0 - Configuration generated successfully
@@ -43,27 +44,13 @@ case "$LOG_LEVEL" in
ERROR) REDIS_LOGLEVEL="warning" ;; ERROR) REDIS_LOGLEVEL="warning" ;;
esac esac
# Generate .env.logging file # Output log configuration to stdout
cat > .env.logging << EOF cat << EOF
# Generated by generate-log-config.sh - DO NOT EDIT MANUALLY
# Regenerate with: ./scripts/ci/generate-log-config.sh $LOG_LEVEL
LOG_LEVEL=$LOG_LEVEL
# Backend/OCR (Pino) # Log levels (generated by generate-log-config.sh $LOG_LEVEL)
BACKEND_LOG_LEVEL=$LOG_LEVEL_LOWER BACKEND_LOG_LEVEL=$LOG_LEVEL_LOWER
TRAEFIK_LOG_LEVEL=$LOG_LEVEL
# Frontend (Vite)
VITE_LOG_LEVEL=$LOG_LEVEL_LOWER
# PostgreSQL
POSTGRES_LOG_STATEMENT=$POSTGRES_LOG_STATEMENT POSTGRES_LOG_STATEMENT=$POSTGRES_LOG_STATEMENT
POSTGRES_LOG_MIN_DURATION=$POSTGRES_LOG_MIN_DURATION POSTGRES_LOG_MIN_DURATION=$POSTGRES_LOG_MIN_DURATION
# Redis
REDIS_LOGLEVEL=$REDIS_LOGLEVEL REDIS_LOGLEVEL=$REDIS_LOGLEVEL
# Traefik
TRAEFIK_LOG_LEVEL=$LOG_LEVEL
EOF EOF
echo "Generated .env.logging with LOG_LEVEL=$LOG_LEVEL"