Cloud Hosted
Deploy Fixbooks with Docker Compose for production use.
Docker Compose
The easiest way to run Fixbooks as Cloud Hosted is with Docker Compose.
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://fixbooks:fixbooks@db:5432/fixbooks
AUTH_SECRET: change-me-in-production
AUTH_URL: https://your-domain.com
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: fixbooks
POSTGRES_PASSWORD: fixbooks
POSTGRES_DB: fixbooks
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:Production Deployment
Build and start
docker compose up -dPush database schema
docker compose exec app pnpm db:pushSeed demo data (optional)
docker compose exec app pnpm db:seedEnvironment Variables
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | PostgreSQL connection string |
AUTH_SECRET | Yes | Random secret for JWT signing |
NEXT_PUBLIC_APP_URL | Yes | Public URL of your instance |
AUTH_GOOGLE_ID | No | Google OAuth client ID |
AUTH_GOOGLE_SECRET | No | Google OAuth client secret |
AUTH_APPLE_ID | No | Apple Sign In service ID |
AUTH_APPLE_TEAM_ID | No | Apple Developer team ID |
AUTH_APPLE_KEY_ID | No | Apple Sign In key ID |
AUTH_APPLE_KEY_BASE64 | No | Base64-encoded .p8 private key |
STRIPE_SECRET_KEY | No | Stripe API key for billing |
STRIPE_WEBHOOK_SECRET | No | Stripe webhook signing secret |
RESEND_API_KEY | No | Resend API key for platform emails |
TRIGGER_PROJECT_REF | Yes | Trigger.dev project reference for production background jobs |
TRIGGER_SECRET_KEY | Yes | Trigger.dev secret key for scheduled tasks and delayed retries |
S3_BUCKET | No | S3 bucket for file uploads |
S3_REGION | No | S3 region |
S3_ACCESS_KEY_ID | No | S3 access key |
S3_SECRET_ACCESS_KEY | No | S3 secret key |
S3_ENDPOINT | No | Custom S3 endpoint (for MinIO, etc.) |
Always set a strong, unique AUTH_SECRET in production. Generate one with openssl rand -base64 32.
Authentication
Fixbooks supports three authentication methods:
- Email & Password - Works out of the box, no additional setup needed
- Google OAuth - Requires
AUTH_GOOGLE_IDandAUTH_GOOGLE_SECRETfrom Google Cloud Console - Apple Sign In - Requires
AUTH_APPLE_ID,AUTH_APPLE_TEAM_ID,AUTH_APPLE_KEY_ID, andAUTH_APPLE_KEY_BASE64from Apple Developer. Encode your.p8key file withbase64 -w0 AuthKey_XXXXXXXXXX.p8
For OAuth providers, set the callback URL to https://your-domain.com/api/auth/callback/google (or /apple).
First User Setup
The first user to register on a new instance automatically becomes a site admin. This applies to both email/password and OAuth registration. Site admins can access the admin panel at /admin and configure site-wide settings at /admin/settings.
Registration Controls
Control who can create accounts on your instance:
| Mode | Description |
|---|---|
open | Anyone can register (default) |
invite_only | Only users with an invitation can register |
disabled | No new registrations (except the first user) |
Set via environment variable (REGISTRATION_MODE) or the admin settings page at /admin/settings.
Domain Restrictions
Restrict registration to specific email domains by setting ALLOWED_EMAIL_DOMAINS (comma-separated) or configuring it in admin settings. When set, only users with matching email domains can register or sign in via OAuth.
ALLOWED_EMAIL_DOMAINS="acme.com,example.com"Cloud Hosted Unlimited Mode
Without Stripe configured, all organizations automatically receive unlimited Pro features. No billing, subscriptions, or plan limits apply. This is detected automatically based on the absence of STRIPE_SECRET_KEY.
You can also control this behavior from the admin settings page:
- Auto (default) - Unlimited when Stripe is not configured
- Always on - Force unlimited regardless of Stripe config
- Always off - Enforce plan limits even without Stripe
Admin Settings
Site admins can configure all Cloud Hosted settings from /admin/settings:
- Registration mode and allowed email domains
- Organization creation permissions
- Cloud Hosted unlimited mode toggle
Billing (Stripe)
Billing is fully optional. Without Stripe configured, all users get unlimited Pro features automatically. Site admins can also override plan limits per organization from /admin/organizations.
To enable paid plans, see the Billing & Stripe Setup guide for full instructions on creating products, prices, and configuring webhooks.
Cloud Hosted instances don't need Stripe. All features are automatically unlocked.
Platform emails (welcome, invitations, login alerts, notification digests) use Resend. Set RESEND_API_KEY to enable these. Without it, platform emails are silently skipped.
Organization-level emails (invoices, reminders, statements) use per-org SMTP configuration set up in Settings > Email.
Background Jobs
Fixbooks uses Trigger.dev for scheduled maintenance and delayed retries. Set TRIGGER_PROJECT_REF and TRIGGER_SECRET_KEY in production before accepting traffic.
Scheduled tasks are defined in trigger/scheduled.ts:
- Notification digests every 15 minutes
- Report schedules hourly
- Stripe retry hourly
- Invoicing, bookkeeping, trash purge, and backups daily
Webhook delivery failures schedule exact delayed retry runs, so retries happen at their intended backoff time without polling. If TRIGGER_SECRET_KEY is missing when a webhook delivery fails, that retry run cannot be scheduled.
For local development:
pnpm trigger:devFor production, deploy the Trigger.dev tasks:
pnpm trigger:deployDatabase
Fixbooks uses PostgreSQL. Minimum version: 15.
The schema is managed by Drizzle ORM. To apply schema changes:
pnpm db:pushStorage
File uploads (receipts, attachments) require S3-compatible storage:
Standard cloud storage. Set S3_ENDPOINT to https://s3.amazonaws.com or omit it.
Cloud Hosted S3-compatible storage. Set S3_ENDPOINT to your MinIO server URL.
Cost-effective alternative. Set S3_ENDPOINT to your R2 endpoint URL.
Without S3 configured, file upload features are disabled but the rest of the app works normally.
Reverse Proxy
For production, place Fixbooks behind a reverse proxy:
server {
server_name accounting.example.com;
location / {
proxy_pass http://localhost:3000;
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;
}
}accounting.example.com {
reverse_proxy localhost:3000
}Backups
Back up your PostgreSQL database regularly:
# Dump database
docker compose exec db pg_dump -U fixbooks fixbooks > backup.sql
# Restore
docker compose exec -T db psql -U fixbooks fixbooks < backup.sqlSchedule automated backups. Losing financial data can be catastrophic.