7f86db26db
Implement bilingual text for push notifications, allowing users to receive them in their preferred language. Automatically configure HTTPS with Let's Encrypt for custom domains to ensure persistent subscriptions.
156 lines
5.7 KiB
YAML
156 lines
5.7 KiB
YAML
name: tx-os
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:-tx}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-tx_dev_password}
|
|
POSTGRES_DB: ${POSTGRES_DB:-tx}
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-tx} -d ${POSTGRES_DB:-tx}"]
|
|
interval: 5s
|
|
timeout: 5s
|
|
retries: 20
|
|
|
|
migrate:
|
|
build:
|
|
context: .
|
|
dockerfile: docker/api-server.Dockerfile
|
|
image: tx-os/api-server:latest
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
environment:
|
|
NODE_ENV: production
|
|
DATABASE_URL: postgres://${POSTGRES_USER:-tx}:${POSTGRES_PASSWORD:-tx_dev_password}@postgres:5432/${POSTGRES_DB:-tx}
|
|
# Seed passwords are now optional. When unset, the seed script
|
|
# creates roles/permissions only and leaves admin creation to
|
|
# the first-run Setup Wizard (/api/setup/complete).
|
|
SEED_ADMIN_PASSWORD: ${SEED_ADMIN_PASSWORD:-}
|
|
SEED_USER_PASSWORD: ${SEED_USER_PASSWORD:-}
|
|
SEED_DEMO_MEETINGS: ${SEED_DEMO_MEETINGS:-false}
|
|
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://localhost:${APP_PORT:-3000}}
|
|
user: root
|
|
entrypoint: ["/usr/bin/tini", "--"]
|
|
command: ["/bin/bash", "/usr/local/bin/migrate.sh"]
|
|
restart: "no"
|
|
|
|
api:
|
|
build:
|
|
context: .
|
|
dockerfile: docker/api-server.Dockerfile
|
|
image: tx-os/api-server:latest
|
|
restart: unless-stopped
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
migrate:
|
|
condition: service_completed_successfully
|
|
environment:
|
|
NODE_ENV: production
|
|
PORT: 8080
|
|
DATABASE_URL: postgres://${POSTGRES_USER:-tx}:${POSTGRES_PASSWORD:-tx_dev_password}@postgres:5432/${POSTGRES_DB:-tx}
|
|
SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET is required — copy .env.docker.example to .env and edit it}
|
|
LOCAL_STORAGE_SIGNING_SECRET: ${LOCAL_STORAGE_SIGNING_SECRET:-${SESSION_SECRET}}
|
|
LOCAL_STORAGE_ROOT: /app/storage
|
|
STORAGE_DRIVER: ${STORAGE_DRIVER:-local}
|
|
PRIVATE_OBJECT_DIR: ${PRIVATE_OBJECT_DIR:-/app/storage/private}
|
|
PUBLIC_OBJECT_SEARCH_PATHS: ${PUBLIC_OBJECT_SEARCH_PATHS:-/app/storage/public}
|
|
DEFAULT_OBJECT_STORAGE_BUCKET_ID: ${DEFAULT_OBJECT_STORAGE_BUCKET_ID:-tx-local}
|
|
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:${APP_PORT:-3000}}
|
|
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-http://localhost:${APP_PORT:-3000}}
|
|
TRUST_PROXY_HTTPS: ${TRUST_PROXY_HTTPS:-false}
|
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
|
HTTPS_MODE: ${HTTPS_MODE:-local}
|
|
LOCAL_DOMAIN: ${LOCAL_DOMAIN:-}
|
|
LOCAL_IP: ${LOCAL_IP:-}
|
|
SMTP_HOST: ${SMTP_HOST:-}
|
|
SMTP_PORT: ${SMTP_PORT:-}
|
|
SMTP_USER: ${SMTP_USER:-}
|
|
SMTP_PASS: ${SMTP_PASS:-}
|
|
SMTP_FROM: ${SMTP_FROM:-}
|
|
SMTP_SECURE: ${SMTP_SECURE:-}
|
|
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
|
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
|
VAPID_SUBJECT: ${VAPID_SUBJECT:-}
|
|
volumes:
|
|
- app_storage:/app/storage
|
|
|
|
web:
|
|
build:
|
|
context: .
|
|
dockerfile: docker/tx-os.Dockerfile
|
|
image: tx-os/web:latest
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- api
|
|
# No host port binding — Caddy is the single public edge.
|
|
expose:
|
|
- "80"
|
|
|
|
caddy:
|
|
image: caddy:2.8-alpine
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- api
|
|
- web
|
|
environment:
|
|
LOCAL_DOMAIN: ${LOCAL_DOMAIN:-tx.local}
|
|
LOCAL_IP: ${LOCAL_IP:-127.0.0.1}
|
|
HTTPS_MODE: ${HTTPS_MODE:-local}
|
|
PUBLIC_DOMAIN: ${PUBLIC_DOMAIN:-}
|
|
ACME_EMAIL: ${ACME_EMAIL:-}
|
|
# Pick the Caddyfile at boot:
|
|
# * HTTPS_MODE=auto → Caddyfile.auto (Let's Encrypt for PUBLIC_DOMAIN)
|
|
# * HTTPS_MODE=skip → Caddyfile.skip (plain HTTP, dev only)
|
|
# * /certs missing → Caddyfile.skip (graceful fall-back)
|
|
# * otherwise → Caddyfile (BYO cert under /certs)
|
|
entrypoint:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
if [ "$${HTTPS_MODE}" = "auto" ]; then
|
|
if [ -z "$${PUBLIC_DOMAIN}" ]; then
|
|
echo "HTTPS_MODE=auto requires PUBLIC_DOMAIN to be set (e.g. tx.example.com)" >&2
|
|
exit 1
|
|
fi
|
|
if [ -n "$${ACME_EMAIL}" ]; then
|
|
export ACME_EMAIL_DIRECTIVE="email $${ACME_EMAIL}"
|
|
else
|
|
export ACME_EMAIL_DIRECTIVE=""
|
|
fi
|
|
exec caddy run --config /etc/caddy/Caddyfile.auto --adapter caddyfile
|
|
elif [ "$${HTTPS_MODE}" = "skip" ] || [ ! -f /certs/local-cert.pem ] || [ ! -f /certs/local-key.pem ]; then
|
|
exec caddy run --config /etc/caddy/Caddyfile.skip --adapter caddyfile
|
|
else
|
|
exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
|
|
fi
|
|
ports:
|
|
# HTTP edge: defaults to APP_PORT so the legacy http://localhost:${APP_PORT}
|
|
# URL keeps working without modifying start.sh.
|
|
- "${HTTP_PORT:-${APP_PORT:-3000}}:80"
|
|
# HTTPS edge: HTTPS_PORT is the operator-facing variable used by
|
|
# both .env.example (default 443, for local-setup.sh users) and
|
|
# .env.docker.example (default 8443, for start.sh users on hosts
|
|
# without privileged-port access). When HTTPS_MODE=skip the
|
|
# entrypoint loads Caddyfile.skip which doesn't listen on :443,
|
|
# so a high default keeps the bind harmless either way.
|
|
- "${HTTPS_PORT:-443}:443"
|
|
volumes:
|
|
- ./docker/Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- ./docker/Caddyfile.skip:/etc/caddy/Caddyfile.skip:ro
|
|
- ./docker/Caddyfile.auto:/etc/caddy/Caddyfile.auto:ro
|
|
- ./certs:/certs:ro
|
|
- caddy_data:/data
|
|
- caddy_config:/config
|
|
|
|
volumes:
|
|
postgres_data:
|
|
app_storage:
|
|
caddy_data:
|
|
caddy_config:
|