Skip to content

Deployment

This guide matches the docker-compose.prod.yml and .env.example files in the repository. For day-to-day local development, see Installation.

DependencyMinimum
Docker24+
Docker Compose v22.20+
RAM (app + Postgres + Redis)2 GB recommended (1 GB minimum tight)

TLS termination is usually handled by a reverse proxy (Nginx, Traefik, Caddy) or your platform (Coolify, Kubernetes ingress).

Terminal window
git clone https://github.com/defcon1702/orimora.git orimora
cd orimora
cp .env.example .env

Fill all variables marked as required in .env. The Compose file expects at least:

  • APP_URL — public HTTPS URL, e.g. https://wiki.example.com
  • POSTGRES_PASSWORD — a strong random value (interpolated into DATABASE_URL)
  • SESSION_SECRET, MAGIC_LINK_SECRET, LLM_ENCRYPTION_KEY — 64-character hex strings (32 bytes each)

Use the same pattern as in the root DEPLOYMENT.md:

Terminal window
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # repeat for each *64 hex* secret

Keep generated values in a password manager; never commit .env.

Terminal window
docker compose -f docker-compose.prod.yml up -d --build

Required on first deploy and after upgrades that ship new migration files:

Terminal window
docker compose -f docker-compose.prod.yml exec app yarn db:migrate
Terminal window
curl -s http://localhost:3000/api/ready

Then open APP_URL in the browser and complete onboarding if this is a fresh database.

The table below aligns with .env.example. Variable names differ from older drafts that used SECRET_KEY or PUBLIC_BASE_URL.

VariablePurpose
APP_URLCanonical public URL — used in links, OAuth redirects, magic-link URLs
DATABASE_URLSet automatically in Compose from POSTGRES_*; custom installs must point to PostgreSQL 16+
REDIS_URLPoints at the bundled redis service in Compose (redis://redis:6379, no password)
SESSION_SECRETSigns session cookies
MAGIC_LINK_SECRETSigns magic-link tokens
LLM_ENCRYPTION_KEYAES-256-GCM for stored LLM API keys (required even if AI is off)
POSTGRES_PASSWORDDatabase user password (Compose)
VariablePurpose
CRON_SECRETBearer token for POST /api/admin/cron.cleanup (scheduled trash purge, reminders)
COLLAB_SECRETShared secret for Yjs / Hocuspocus collaboration
COLLAB_MAX_CONNECTIONSCap simultaneous collab sockets (default 50)
SMTP_*Outbound email for magic links (SMTP_PASSWORD in .env.example)
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle OAuth
OIDC_*Generic OIDC provider
STORAGE_DRIVER / S3_*Upload storage backend. Default local (disk). Set STORAGE_DRIVER=s3 + S3_BUCKET/S3_ACCESS_KEY/S3_SECRET_KEY (+ optional S3_ENDPOINT/S3_FORCE_PATH_STYLE/S3_PRESIGN_TTL_SECONDS) to store attachments/avatars/logos in an S3-compatible bucket, served via presigned URLs — see Configuration. (Separate from off-site backups, which use BACKUP_RCLONE_REMOTE; BACKUP_S3_BUCKET is parsed but not wired up.)

Full column-style reference: DEPLOYMENT.md in the repo.

Terminate TLS at Nginx, Caddy, or Traefik and proxy to the app container (port 3000 inside the network). You must preserve:

  • Host
  • X-Forwarded-For
  • X-Forwarded-Proto (HTTPS detection)
  • WebSocket upgrade headers for /collab

Example Nginx location (HTTP → app):

location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Terminal window
git pull
docker compose -f docker-compose.prod.yml build app
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml exec app yarn db:migrate

Watch application logs during the first request after a schema change.

Do not run production without backups. BACKUP_ENABLED defaults to true in the production compose file (daily encrypted DB dumps + weekly Markdown exports), but you still need to:

  1. Set CRON_SECRET (drives the scheduled backup + cleanup jobs).
  2. Configure an off-site target (BACKUP_RCLONE_REMOTE) so a host loss doesn’t lose everything.
  3. Store the recovery code (shown once under Settings → Admin → Backups) — off-site copies are encrypted and cannot be decrypted without it.

Then rehearse a restore. See Backups and the Disaster Recovery runbook.

IssueChecks
OAuth redirect mismatchAPP_URL must exactly match the URL in Google / OIDC console
Redis connection errorsREDIS_URL reachable; redis container healthy
502 from proxyApp not listening on expected port; WebSocket path blocked
Migrations failBackup DB first; ensure only one migration runner at a time