Files
beenvoice/docker-compose.coolify.yml
T

153 lines
5.8 KiB
YAML

# beenvoice on Coolify — single Docker Compose resource (recommended).
#
# Deploy: Coolify → New Resource → Docker Compose → compose file: docker-compose.coolify.yml
#
# 1. Assign a domain to the `app` service in Coolify (SERVICE_FQDN_APP wires Traefik).
# 2. Set AUTH_SECRET, POSTGRES_PASSWORD, S3_ACCESS_KEY, S3_SECRET_KEY in the resource env (see .env.example).
# 3. Ports are configurable with APP_PORT, POSTGRES_PORT, and GARAGE_*_PORT.
# Only APP_PORT is exposed to Coolify's proxy; db and Garage stay internal.
# 4. Do NOT override S3_ENDPOINT — it follows GARAGE_API_PORT on the shared network.
# 5. Rebuild after changing NEXT_PUBLIC_* (image build args use SERVICE_URL_APP).
#
# Migrating from Application + separate Postgres + Garage compose:
# - Export Postgres data, point DATABASE_URL at this stack's `db` service, redeploy once here.
# - Or keep external Postgres and remove the `db` service + volume from this file.
services:
app:
build:
context: .
args:
NEXT_PUBLIC_APP_URL: ${SERVICE_URL_APP:-${NEXT_PUBLIC_APP_URL:-http://localhost:${APP_PORT:-3000}}}
BETTER_AUTH_URL: ${SERVICE_URL_APP:-${BETTER_AUTH_URL:-http://localhost:${APP_PORT:-3000}}}
image: ${BEENVOICE_IMAGE:-beenvoice:coolify}
environment:
SERVICE_FQDN_APP:
NODE_ENV: production
PORT: ${APP_PORT:-3000}
AUTH_SECRET: ${AUTH_SECRET:?Set AUTH_SECRET in Coolify env}
DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
DB_DISABLE_SSL: "true"
BETTER_AUTH_URL: ${SERVICE_URL_APP:-${BETTER_AUTH_URL:-http://localhost:${APP_PORT:-3000}}}
NEXT_PUBLIC_APP_URL: ${SERVICE_URL_APP:-${NEXT_PUBLIC_APP_URL:-http://localhost:${APP_PORT:-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:${GARAGE_API_PORT:-3900}
S3_BUCKET: ${S3_BUCKET:-beenvoice-receipts}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_REGION: ${S3_REGION:-garage}
expose:
- "${APP_PORT:-3000}"
depends_on:
db:
condition: service_healthy
garage:
condition: service_healthy
restart: unless-stopped
worker:
build:
context: .
target: worker
image: ${BEENVOICE_WORKER_IMAGE:-beenvoice-worker:coolify}
environment:
NODE_ENV: production
DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
DB_DISABLE_SSL: "true"
AUTH_SECRET: ${AUTH_SECRET:?Set AUTH_SECRET in Coolify env}
WORKER_POLL_MS: ${WORKER_POLL_MS:-2000}
WORKER_SCHEDULE_MS: ${WORKER_SCHEDULE_MS:-60000}
RESEND_API_KEY: ${RESEND_API_KEY:-}
RESEND_DOMAIN: ${RESEND_DOMAIN:-}
CRON_SECRET: ${CRON_SECRET:-}
APP_INTERNAL_URL: http://app:${APP_PORT:-3000}
depends_on:
app:
condition: service_started
db:
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}
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
command: ["postgres", "-p", "${POSTGRES_PORT:-5432}"]
volumes:
- beenvoice_pg_data:/var/lib/postgresql/data
healthcheck:
test:
[
"CMD-SHELL",
'pg_isready -h 127.0.0.1 -p "$${POSTGRES_PORT}" -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"',
]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
garage:
image: dxflrs/garage:v2.3.0
environment:
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY}
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY}
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"]
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 = "[::]:${GARAGE_RPC_PORT:-3901}"
rpc_public_addr = "garage:${GARAGE_RPC_PORT:-3901}"
rpc_secret = "${GARAGE_RPC_SECRET:-000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f}"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:${GARAGE_API_PORT:-3900}"
root_domain = ".s3.garage"
[s3_web]
bind_addr = "[::]:${GARAGE_WEB_PORT:-3902}"
root_domain = ".web.garage"
index = "index.html"
[admin]
api_bind_addr = "[::]:${GARAGE_ADMIN_PORT:-3903}"
admin_token = "${GARAGE_ADMIN_TOKEN:-beenvoice_garage_admin_token_change_me_in_production}"
metrics_token = "${GARAGE_METRICS_TOKEN:-beenvoice_garage_metrics_token_change_me_in_production}"