This directory contains the production-grade Docker Compose bundle:
- PostgreSQL 16
- Redis 7
- FastAPI backend + Celery worker/beat
- Next.js app
- Caddy reverse proxy (TLS by default)
- One-shot Drizzle migration job (runs on deploy/boot)
- Copy
deploy/compose/.env.exampletodeploy/compose/.env. - Edit
.envvalues (at minimum:POSTGRES_PASSWORD,BETTER_AUTH_SECRET,INTERNAL_AUTH_SECRET).- CRITICAL: Set a strong
POSTGRES_PASSWORD(e.g.,openssl rand -hex 32). - IMPORTANT: The
DATABASE_URLvalue must use the same password you set inPOSTGRES_PASSWORD. The format ispostgresql://financeuser:YOUR_PASSWORD@postgres:5432/finance_dbwhereYOUR_PASSWORDmatchesPOSTGRES_PASSWORD. - Generate secrets:
BETTER_AUTH_SECRET:openssl rand -hex 32INTERNAL_AUTH_SECRET:openssl rand -hex 32DATA_ENCRYPTION_KEY_CURRENT(optional, recommended):openssl rand -base64 32
APP_URLdefaults tohttp://localhost:8080for LAN/dev mode.HTTP_PORTdefaults to8080in the example env for a conflict-free local default.- For public internet exposure, set
APP_URL,CADDY_ADDRESS, andACME_EMAILso TLS is enabled.
- CRITICAL: Set a strong
- Start:
docker compose --env-file deploy/compose/.env -f deploy/compose/docker-compose.yml up -d- Verify all services are running:
docker compose --env-file deploy/compose/.env -f deploy/compose/docker-compose.yml psAll containers should show Up status. The migrate container will exit after completing database migrations (this is expected).
Once all containers are running:
- Web UI: Open your browser and navigate to
http://localhost:8080(or whateverHTTP_PORTyou configured in.env) - Backend API: Internal only at
http://backend:8000within the Docker network; proxied through Caddy for external access - MCP Server: Available at
http://localhost:8001(if enabled)
First Time Setup:
- The app will prompt you to create an account or log in.
- Follow the authentication flow to set up your profile.
- Start importing transactions or connecting your accounts.
Use this when you want containers to run your current local code (instead of GHCR prebuilt images):
docker compose \
--env-file deploy/compose/.env \
-f deploy/compose/docker-compose.yml \
-f deploy/compose/docker-compose.local.yml \
up -d --buildThis is the recommended flow when validating recent code changes.
If you're running this stack from the repo and you already have local dev env files like:
backend/.envfrontend/.env.local
…you can layer them into Compose using multiple --env-file flags.
Tip: put deploy/compose/.env last so the Docker-friendly values (like DATABASE_URL=...@postgres:5432/...) win over any localhost URLs.
Example (local build):
docker compose \
--env-file backend/.env \
--env-file frontend/.env.local \
--env-file deploy/compose/.env \
-f deploy/compose/docker-compose.yml \
-f deploy/compose/docker-compose.local.yml \
up -d --build- Only web ports are exposed by default:
HTTP_PORT(80) andHTTPS_PORT(443). - DB migrations run automatically via the
migrateservice. They are idempotent. - In production-like external DB setups, use
DATABASE_URLwith?sslmode=require. - File uploads and CSV imports are stored in
public/uploadsand persisted via theuploads_dataDocker volume. - This bundle defaults to Postgres 16. If you have an existing local Docker volume created by Postgres 15, you must dump/restore to upgrade (or temporarily set
POSTGRES_IMAGE=postgres:15-alpineto keep running on 15). - We set explicit
container_namevalues to avoid the*-1suffix. This makes container names stable, but it also means you cannot scale services with--scale, and you shouldn't run multiple Syllogic stacks on the same Docker host without changing names. - See
docs/deployment-matrix.mdfor the cross-environment contract (local/self-host/Railway v1+v2).
This bundle includes an MCP HTTP server (FastMCP) and starts it by default.
- Generate an API key in the app UI (Settings -> API Keys).
- Configure your MCP client to send
Authorization: Bearer pf_.... - Start (or restart) normally:
docker compose --env-file deploy/compose/.env -f deploy/compose/docker-compose.yml up -dMCP port contract:
- Internal container port is fixed at
8001. - External host port defaults to
8001. - Override external port with
MCP_PORT(example:MCP_PORT=9001maps9001 -> 8001). - Health endpoint is exposed at
http://localhost:${MCP_PORT:-8001}/health.
Security note: the MCP service is currently best treated as single-user and should only be exposed to trusted networks (LAN/VPN), or protected by an auth layer.
For truly one-click installs, the GHCR packages must be public:
- GitHub → org → Packages → select the image → Package settings → Change visibility → Public
- Set
APP_VERSIONin.envto the new release tag (e.g.v1.2.3). - Pull + restart:
docker compose --env-file deploy/compose/.env -f deploy/compose/docker-compose.yml pull
docker compose --env-file deploy/compose/.env -f deploy/compose/docker-compose.yml up -dDo not use edge for internet-facing production.
If you're upgrading an existing install to the encrypted-field rollout, run from the backend container or backend working directory:
python postgres_migration/run_encryption_upgrade.py --batch-size 500This command validates encryption keys, runs the backfill, prints coverage counters, and exits non-zero if coverage is incomplete.
Optional:
# Check coverage without writing changes
python postgres_migration/run_encryption_upgrade.py --batch-size 500 --dry-run
# Clear plaintext columns after your validation window
python postgres_migration/run_encryption_upgrade.py --batch-size 500 --clear-plaintextFrom repository root:
- Local infra + migrations for source development:
./scripts/dev-up.sh --local - Full prebuilt self-host stack:
./scripts/prod-up.sh - Local source-compose smoke validation:
./scripts/local-smoke.sh - VPS post-install verification:
deploy/install/post-install-check.sh /opt/syllogic
See deploy/railway/ for a Railway-specific compose file and instructions.
Example manual backup:
docker compose --env-file deploy/compose/.env -f deploy/compose/docker-compose.yml exec -T postgres \
sh -lc 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup.sql