Files
beenvoice-web/docker-compose.yml
T
soconnorandClaude Sonnet 5 d589b94580 Make CRON_SECRET optional; fix docker env validation failure
CRON_SECRET was required (min 32 chars) in production by env.js, but
neither docker-compose.yml nor docker-compose.coolify.yml passed it
through. Since the release container isn't covered by
SKIP_ENV_VALIDATION (build-stage only), bun run start failed env
validation on every docker deploy unless CRON_SECRET was manually
configured — even though it only gates the optional recurring-invoice
cron endpoint, which already handles being unset gracefully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 14:43:20 -04:00

124 lines
4.5 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Production stack (app + Postgres + Garage). Local dev Postgres/Garage: docker-compose.dev.yml
#
# Coolify: deploy docker-compose.coolify.yml as ONE Docker Compose resource (preferred),
# or this file. S3_ENDPOINT=http://garage:3900 works only inside a single stack.
# Application + separate Garage → docs/COOLIFY.md.
#
# After git pull, rebuild before starting — a plain `docker compose up -d` reuses
# the existing local image and will NOT include new code. Use:
# ./scripts/docker-deploy.sh
# docker compose up -d --build
services:
app:
build:
context: .
args:
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
# Fixed default tag (beenvoice:local) is reused until you --build. docker-deploy.sh
# sets BEENVOICE_IMAGE=beenvoice:<git-sha> so each deploy gets a fresh tag.
image: ${BEENVOICE_IMAGE:-beenvoice:local}
environment:
NODE_ENV: production
AUTH_SECRET: ${AUTH_SECRET:?Set AUTH_SECRET in .env}
DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-postgres}
DB_DISABLE_SSL: "true"
BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
RESEND_API_KEY: ${RESEND_API_KEY:-}
RESEND_DOMAIN: ${RESEND_DOMAIN:-}
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${NEXT_PUBLIC_UMAMI_WEBSITE_ID:-}
NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${NEXT_PUBLIC_UMAMI_SCRIPT_URL:-https://analytics.umami.is/script.js}
NEXT_PUBLIC_AUTHENTIK_ENABLED: ${NEXT_PUBLIC_AUTHENTIK_ENABLED:-false}
DISABLE_SIGNUPS: ${DISABLE_SIGNUPS:-true}
CRON_SECRET: ${CRON_SECRET:-}
AUTHENTIK_ISSUER: ${AUTHENTIK_ISSUER:-}
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID:-}
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET:-}
AUTHENTIK_ORIGIN: ${AUTHENTIK_ORIGIN:-}
S3_ENDPOINT: http://garage:3900
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
S3_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
S3_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
S3_REGION: ${S3_REGION:-garage}
ports:
- "${WEB_PORT:-${PORT:-3000}}:3000"
depends_on:
db:
condition: service_healthy
garage:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:17-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-postgres}
volumes:
- beenvoice_pg_data:/var/lib/postgresql/data
healthcheck:
test:
["CMD-SHELL", 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"']
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
# S3-compatible receipt storage (~50100 MB RAM vs MinIO). API :3900.
garage:
image: dxflrs/garage:v2.3.0
environment:
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-GK3515373e4c851ebaad366558}
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34}
GARAGE_DEFAULT_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
configs:
- source: garage_config
target: /etc/garage.toml
volumes:
- beenvoice_garage_meta:/var/lib/garage/meta
- beenvoice_garage_data:/var/lib/garage/data
command: ["/garage", "server", "--single-node", "--default-bucket"]
ports:
- "${GARAGE_API_PORT:-3900}:3900"
healthcheck:
test: ["CMD", "/garage", "status"]
interval: 5s
timeout: 5s
retries: 15
start_period: 20s
restart: unless-stopped
volumes:
beenvoice_pg_data:
beenvoice_garage_meta:
beenvoice_garage_data:
configs:
garage_config:
content: |
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "garage:3901"
rpc_secret = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
root_domain = ".s3.garage"
[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.garage"
index = "index.html"
[admin]
api_bind_addr = "[::]:3903"
admin_token = "beenvoice_garage_admin_token_change_me_in_production"
metrics_token = "beenvoice_garage_metrics_token_change_me_in_production"