docker compose logs app (or the Logs tab in Dokploy).
Database setup
This database already has tables (…). db:setup only initialises an empty database
This database already has tables (…). db:setup only initialises an empty database
bun run db:setup fails with This database already has tables (…). db:setup only initialises an empty database; an existing install is upgraded with the migrations in scripts/.Cause: db:setup refuses any database that already contains tables in the public schema, so it can never overwrite an install.Fix: For a first install, point DATABASE_URL at an empty database (create a new one). To upgrade an existing install, run the migrations instead: Upgrading. In Docker Compose the setup service passes --if-empty, which prints Database already initialised — nothing to do. and succeeds.PostgreSQL 16 or newer is required (this server is N)
PostgreSQL 16 or newer is required (this server is N)
db:setup checks the server version and refuses anything below 16.Fix: Use PostgreSQL 16, 17 or 18. With Compose the bundled database is PostgreSQL 18; this error appears only with your own server.DATABASE_URL is not set — copy .env.example to .env.local and fill it in
DATABASE_URL is not set — copy .env.example to .env.local and fill it in
DATABASE_URL. From source the file is .env.local in the repository root, and you must run the command from there. On Dokploy the variable is missing from the Environment tab or the app was not redeployed.Fix: Set it and restart. With Compose .env is used and the URL is built for you from POSTGRES_PASSWORD.Set POSTGRES_PASSWORD / CRON_SECRET / OUTREACH_TICK_SECRET in .env
Set POSTGRES_PASSWORD / CRON_SECRET / OUTREACH_TICK_SECRET in .env
Set POSTGRES_PASSWORD in .env, Set CRON_SECRET in .env or Set OUTREACH_TICK_SECRET in .env.Fix: Put them in .env next to docker-compose.yml (generate with openssl rand -hex 32). With your own database you also need Set DATABASE_URL in .env to your PostgreSQL.The app cannot connect: SSL / TLS errors
The app cannot connect: SSL / TLS errors
DATABASE_SSL=disable. A database without TLS (a local one, Dokploy’s Postgres) fails the handshake.Fix: Set DATABASE_SSL=disable for a database without TLS. For a hosted database leave it unset or empty. Compose sets it for the bundled db.Refusing to seed demo data
Refusing to seed demo data
bun run db:seed:demo prints Refusing to seed demo data: NODE_ENV=production. or Refusing to seed demo data into "host": it is not localhost/127.0.0.1. …Cause: The demo data is fictional and must not land in a real database.Fix: Run it only against a local database, without NODE_ENV=production. Pass --allow-remote only if you are sure.Database connections
remaining connection slots are reserved for roles with the SUPERUSER attribute
remaining connection slots are reserved for roles with the SUPERUSER attribute
max_connections is used up. It is shared by every deployment pointing at the server, and idle connections from other deployments count. A rolling deploy briefly runs two containers, each with its own pool of DB_POOL_MAX (default 8). This is capacity, not a code bug.Fix: Check select count(*) from pg_stat_activity on the server, stop what is holding connections, lower DB_POOL_MAX, or raise the server’s max_connections. Do not raise DB_POOL_MAX before checking.Sign-in and sign-up email
The verification or invitation email never arrives
The verification or invitation email never arrives
RESEND_API_KEY nothing is sent. The log shows [auth/email] RESEND_API_KEY is not set: verification, password-reset and invitation emails are written to this log instead of being sent. Set RESEND_API_KEY and AUTH_EMAIL_FROM before inviting anyone. and the message itself as [auth/email] (not sent — no RESEND_API_KEY) to=… subject="…".Fix: For the first account, copy the link from docker compose logs app. For everyone else set RESEND_API_KEY and AUTH_EMAIL_FROM (an address on a domain verified in Resend) and restart. See Resend. If both are set and mail still does not arrive, check the Resend dashboard for rejected sends and the domain’s verification status.This instance is invite-only: if … hasn't been invited, no account was created and no email will arrive
This instance is invite-only: if … hasn't been invited, no account was created and no email will arrive
AUTH_SIGNUP defaults to invite-only. After the first account, only an email address with a pending, unexpired invitation (they expire after 7 days) gets an account; anyone else gets the normal “check your email” response and nothing is created.Fix: Invite the person from Settings, Members, and have them sign up with that exact address. For a public deployment set AUTH_SIGNUP=open. See Who may sign up.Everyone was signed out after a restart
Everyone was signed out after a restart
BETTER_AUTH_SECRET changed or is not set to the same value as before.Fix: Restore the previous value. Keep it stable.Secrets
INTEGRATION_CREDENTIALS_KEY must be configured before saving or reading integration credentials
INTEGRATION_CREDENTIALS_KEY must be configured before saving or reading integration credentials
openssl rand -hex 32) and restart.Stored integration credential is invalid
Stored integration credential is invalid
INTEGRATION_CREDENTIALS_KEY differs from the key used when it was saved (changed, lost or different between instances).Fix: Restore the original key. If it is lost, reconnect each integration. To change the key on purpose, re-encrypt with bun run rotate:integration-credentials (new key in INTEGRATION_CREDENTIALS_KEY, old in LEGACY_INTEGRATION_CREDENTIALS_KEYS).UNSUBSCRIBE_SECRET is not configured
UNSUBSCRIBE_SECRET is not configured
UNSUBSCRIBE_SECRET and restart. Changing it later breaks links in email already sent.Sending and scheduled jobs
Emails are queued but nothing is sent
Emails are queued but nothing is sent
/api/outreach/build-queue job never ran. It fills each mailbox’s queue and resets daily counters; the in-process sender has nothing to send without it. Other causes: the app is not running with NODE_ENV=production (the sender runs only in production, so not under bun run dev), or PAUSE_EMAIL_OUTBOUND / PEOPLE_MIGRATION_MODE is on.Fix: Check that the cron service is running (docker compose logs cron should show agentsdr cron: schedules loaded) or that your external cron calls it. Run it once by hand: curl -fsS -X POST -H "x-tick-secret: $OUTREACH_TICK_SECRET" "$APP/api/outreach/build-queue". See Scheduled jobs.Unified People migration is in progress; campaign and outbound changes are temporarily paused.
Unified People migration is in progress; campaign and outbound changes are temporarily paused.
PEOPLE_MIGRATION_MODE or PAUSE_CAMPAIGN_MUTATIONS is on, so POST, PUT, PATCH and DELETE calls to campaign, LinkedIn job and mailbox routes answer 503, including your scheduled jobs.Fix: Set them to false (or remove them) and restart. See PAUSE switches.Gmail replies stop arriving after about a week
Gmail replies stop arriving after about a week
/api/outreach/mailboxes/watch is not scheduled.Fix: Schedule it once a day. See Gmail reply sync.Two copies of each email were sent
Two copies of each email were sent
/api/outreach/tick is also scheduled. The outreach sender loop has no distributed lock.Fix: Run exactly one app replica and do not schedule /api/outreach/tick.Integrations and webhooks
… is not connected — connect it in Settings → …
… is not connected — connect it in Settings → …
Unipile is not connected — connect it in Settings → LinkedIn → Connection, Google Workspace is not connected — connect it in Settings → Email → Connection or the Cloudflare R2 equivalent (Settings → WhatsApp → Integrations). API calls answer 409.Cause: Integrations are per organization and stored in the database. There is no environment fallback, so putting keys in .env does nothing. The organization you are signed in to has not connected the service, or its stored credentials could not be read (see “Stored integration credential is invalid” above). Background jobs for that feature return early.Fix: Open the named Settings page and connect the service. See Integrations.Unipile webhooks are not registered on localhost
Unipile webhooks are not registered on localhost
… is a local address that Unipile's servers cannot reach. Webhooks are registered automatically once the app runs at its public address. or BETTER_AUTH_URL is not set, so there is no public address for Unipile to call.Cause: Unipile’s servers cannot reach localhost, so registration is skipped.Fix: Run the app at a public HTTPS address, set BETTER_AUTH_URL to it, and save the Unipile integration again. For local development use a tunnel and set BETTER_AUTH_URL to the tunnel address. See Unipile.Links in email or Unipile redirects point to localhost
Links in email or Unipile redirects point to localhost
BETTER_AUTH_URL is still http://localhost:3000, or unset and NEXT_PUBLIC_APP_URL (fixed at build time) is used.Fix: Set BETTER_AUTH_URL to the public origin without a trailing slash and restart.The WhatsApp recorder extension does not connect to my domain
The WhatsApp recorder extension does not connect to my domain