Skip to content

Deploying Dazzle to Heroku

Prerequisites

  • Heroku CLI installed
  • Git repository with your Dazzle project
  • dazzle.toml at the repository root

Quick Start

# Create Heroku app
heroku create my-dazzle-app

# Add PostgreSQL and Redis
heroku addons:create heroku-postgresql:essential-0
heroku addons:create heroku-redis:mini

# Set required env vars
heroku config:set DAZZLE_ENV=production
heroku config:set DAZZLE_SECRET_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(32))")

# Deploy
git push heroku main

DATABASE_URL and REDIS_URL are set automatically by the add-ons.

Procfile

Your repository should contain a Procfile at the root:

web: uvicorn dazzle_back.runtime.app_factory:create_app_factory --factory --host 0.0.0.0 --port $PORT --workers ${WEB_CONCURRENCY:-4}

This uses uvicorn's multi-worker mode. Each worker gets its own process and connection pool.

Environment Variables

Variable Required Default Description
DATABASE_URL Yes (auto) PostgreSQL URL (set by Heroku Postgres add-on)
REDIS_URL Yes (auto) Redis URL (set by Heroku Redis add-on)
DAZZLE_ENV Yes development Set to production
DAZZLE_SECRET_KEY Yes - Secret key for sessions and tokens
DAZZLE_DB_POOL_MIN No 2 Minimum connection pool size
DAZZLE_DB_POOL_MAX No 10 Maximum connection pool size
WEB_CONCURRENCY No 4 Number of uvicorn worker processes

Connection Pool Tuning

Match DAZZLE_DB_POOL_MAX to your Heroku Postgres plan's connection limit. The full per-process budget is ~14 connections (pool ceiling + ~3-4 event-framework + ~1-2 startup-migration transients), so the formula has a hidden multiplier:

Plan Max Connections Recommended DAZZLE_DB_POOL_MAX (with 4 workers) Total footprint
Essential-0 20 2 (2+4)*4 = 24 — over; use WEB_CONCURRENCY=1
Essential-1 40 5 (5+4)*4 = 36
Standard-0 120 15 (15+4)*4 = 76
Standard-2+ 400+ 25 (25+4)*4 = 116

Formula: (DAZZLE_DB_POOL_MAX + 4) * WEB_CONCURRENCY < plan_max_connections - 5

The +4 accounts for non-pool framework connections; the -5 leaves headroom for heroku pg:psql debug sessions.

# Conservative defaults for Essential-1 (single dyno, 4 workers):
heroku config:set DAZZLE_DB_POOL_MAX=5 WEB_CONCURRENCY=4

# Or simplify the footprint with a single worker per dyno + horizontal scaling:
heroku config:set DAZZLE_DB_POOL_MAX=10 WEB_CONCURRENCY=1
heroku ps:scale web=4

See docs/reference/databases.mdConnection Pool for diagnostic commands and full symptom → fix table.

Scaling

Start with a single standard dyno:

heroku ps:scale web=1:standard-1x

Before scaling horizontally, try vertical scaling:

heroku ps:scale web=1:standard-2x

Horizontal scaling works out of the box — each dyno runs independent workers with their own connection pools. The advisory lock on migrations ensures only one worker runs schema changes.

Asset Bundling

DAZZLE_ENV=production flips the framework's CSS/JS loading mode from individual files (live-reload friendly, dev-only) to a single bundled file (/static/dist/dazzle.min.{js,css}). The bundle is precomputed at framework release time and ships in the dazzle-dsl wheel — no Node toolchain required at slug build time.

Configure via [ui] assets in dazzle.toml:

[ui]
# "auto"   = bundle when DAZZLE_ENV=production, individual in dev (default)
# "always" = bundle in every environment (perf testing / staging)
# "never"  = individual scripts always (live-reload during prod debugging)
assets = "auto"

CLI overrides for one-off testing:

dazzle serve --bundle      # force bundled regardless of manifest
dazzle serve --no-bundle   # force individual regardless of manifest

Smoke-test bundled mode locally before deploying so you catch any project-side custom JS/CSS that doesn't survive bundling:

DAZZLE_ENV=production dazzle serve --local
# Then load the app in a browser, open DevTools → Network, verify
# /static/dist/dazzle.min.js and /static/dist/dazzle.min.css both
# return 200 with the framework banner at the top.

On Heroku the bundle ships in the wheel; no slug-build step is required. Once you've set DAZZLE_ENV=production and deployed v0.61.141 or later, every page automatically loads the bundle. If you ever need to revert to individual scripts (e.g. to reproduce a dev-mode bug), set assets = "never" in dazzle.toml or pass --no-bundle to dazzle serve.

Trade-offs

Mode Pros Cons
Bundled One CSS + one JS request; smaller wire transfer; faster first paint on cold connections Source maps less granular; live-reload no longer per-file
Individual Per-file source maps; live-reload reloads only what changed ~24 separate requests; slower cold first paint on real RTT

The default auto mode picks bundled in production and individual in dev — appropriate for most projects. Override only if you've measured a specific reason.

File Storage

Heroku's filesystem is ephemeral. For file uploads, configure S3:

heroku config:set DAZZLE_FILE_STORAGE=s3
heroku config:set AWS_ACCESS_KEY_ID=...
heroku config:set AWS_SECRET_ACCESS_KEY=...
heroku config:set AWS_S3_BUCKET=my-dazzle-uploads

Framework Version Pinning

Pin the framework version in dazzle.toml to prevent unexpected changes:

[project]
name = "my-app"
version = "1.0.0"
framework_version = "~0.38"

The server refuses to start if the installed version doesn't match.

Backups

Heroku Postgres provides automatic daily backups. For manual backups:

# Heroku's built-in backup
heroku pg:backups:capture

# Dazzle's backup (includes uploads and metadata)
heroku run dazzle backup create
heroku run dazzle backup create --output /tmp/backup.tar.gz

Custom Domains and SSL

heroku domains:add app.example.com
# SSL is automatic with ACM (Automated Certificate Management)
heroku certs:auto:enable

Monitoring

# Live logs
heroku logs --tail

# Metrics dashboard
heroku open --app my-dazzle-app
# Navigate to "Metrics" tab in Heroku Dashboard

Troubleshooting

Port already in use: Heroku assigns $PORT dynamically. Never hardcode the port.

Connection pool exhaustion: Check heroku pg:info for connection count. Reduce DAZZLE_DB_POOL_MAX or scale down workers.

Migration conflicts: The advisory lock prevents concurrent migrations across workers. If a migration hangs, check heroku pg:locks.

Memory issues: Monitor with heroku logs --tail | grep "Memory quota". Reduce WEB_CONCURRENCY if needed.