Self-Hosting
Deploy Dubbl with Docker Compose for production use.
Docker Compose
The easiest way to self-host Dubbl is with Docker Compose.
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 -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 |
CRON_SECRET | No | Secret for protecting cron endpoints |
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
Dubbl 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"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.
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:pushStorage
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.sqlSchedule automated backups. Losing financial data can be catastrophic.