dubbl

Self-Hosting

Deploy Dubbl with Docker Compose for production use.

Docker Compose

The easiest way to self-host Dubbl is with Docker Compose.

docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://dubbl:dubbl@db:5432/dubbl
      AUTH_SECRET: change-me-in-production
      AUTH_URL: https://your-domain.com
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: dubbl
      POSTGRES_PASSWORD: dubbl
      POSTGRES_DB: dubbl
    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
CRON_SECRETNoSecret for protecting cron endpoints
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

Dubbl 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"

Self-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 self-hosting settings from /admin/settings:

  • Registration mode and allowed email domains
  • Organization creation permissions
  • Self-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.

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

Database

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

Self-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 Dubbl 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 dubbl dubbl > backup.sql

# Restore
docker compose exec -T db psql -U dubbl dubbl < backup.sql

Schedule automated backups. Losing financial data can be catastrophic.

On this page