fixbooks

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.

docker-compose.yml
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 -d

Push database schema

docker compose exec app pnpm db:push

Seed demo data (optional)

docker compose exec app pnpm db:seed

Environment Variables

VariableRequiredDescription
DATABASE_URLYesPostgreSQL connection string
AUTH_SECRETYesRandom secret for JWT signing
NEXT_PUBLIC_APP_URLYesPublic URL of your instance
AUTH_GOOGLE_IDNoGoogle OAuth client ID
AUTH_GOOGLE_SECRETNoGoogle OAuth client secret
AUTH_APPLE_IDNoApple Sign In service ID
AUTH_APPLE_TEAM_IDNoApple Developer team ID
AUTH_APPLE_KEY_IDNoApple Sign In key ID
AUTH_APPLE_KEY_BASE64NoBase64-encoded .p8 private key
STRIPE_SECRET_KEYNoStripe API key for billing
STRIPE_WEBHOOK_SECRETNoStripe webhook signing secret
RESEND_API_KEYNoResend API key for platform emails
TRIGGER_PROJECT_REFYesTrigger.dev project reference for production background jobs
TRIGGER_SECRET_KEYYesTrigger.dev secret key for scheduled tasks and delayed retries
S3_BUCKETNoS3 bucket for file uploads
S3_REGIONNoS3 region
S3_ACCESS_KEY_IDNoS3 access key
S3_SECRET_ACCESS_KEYNoS3 secret key
S3_ENDPOINTNoCustom 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_ID and AUTH_GOOGLE_SECRET from Google Cloud Console
  • Apple Sign In - Requires AUTH_APPLE_ID, AUTH_APPLE_TEAM_ID, AUTH_APPLE_KEY_ID, and AUTH_APPLE_KEY_BASE64 from Apple Developer. Encode your .p8 key file with base64 -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:

ModeDescription
openAnyone can register (default)
invite_onlyOnly users with an invitation can register
disabledNo 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.

Email

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:dev

For production, deploy the Trigger.dev tasks:

pnpm trigger:deploy

Database

Fixbooks uses PostgreSQL. Minimum version: 15.

The schema is managed by Drizzle ORM. To apply schema changes:

pnpm db:push

Storage

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.sql

Schedule automated backups. Losing financial data can be catastrophic.

On this page