OfferKitDocs

Deploy OfferKit

Deploy OfferKit with its web app, background worker, Postgres, and Redis. Choose local Docker setup or a production platform and verify each service.

An OfferKit deployment has four components:

  • web — dashboard and API on port 3000;
  • worker — background jobs and webhook delivery, with health on port 9091;
  • Postgres — authoritative application and ledger data;
  • Redis — BullMQ background jobs, with a Postgres fallback when Redis is absent.

The web and worker use the same published image, ghcr.io/offerkit/offerkit, with different start commands.

Evaluate locally

git clone https://github.com/offerkit/offerkit.git
cd offerkit
cp .env.example .env
docker compose up -d

Set at least:

DATABASE_URL=postgres://offerkit:dev@postgres:5432/offerkit
REDIS_URL=redis://redis:6379
BETTER_AUTH_SECRET=replace-me
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=replace-me

If you use webhooks, set WEBHOOK_SECRET_ENCRYPTION_KEY to the same stable value on the web and worker services. It must be at least 32 characters; generate one with openssl rand -base64 32. Deployments without webhooks can omit it. Replace every example secret before exposing the deployment beyond local development.

The web service runs database migrations on startup. Sign in at http://localhost:3000 or the server’s LAN address and change the seeded administrator password when prompted. Set OFFERKIT_PUBLIC_URL when the deployment has a canonical external URL.

Production topology

Create managed Postgres and Redis services, one public web service, and one private worker service. The worker command is:

node apps/worker/dist/index.js

Expose the web service through TLS. Do not expose Postgres, Redis, or the worker directly to the public internet.

Set the web health check to /api/v1/ready and the worker health check to /health on its configured health port.

Pin a release

Use latest for evaluation and a version tag for production, for example:

ghcr.io/offerkit/offerkit:v0.2.0

Use the same image version for web and worker. edge follows the latest main-branch commit and is not a production stability channel.

Environments

Run staging and production as separate deployments with separate databases, Redis instances, secrets, API keys, customers, campaigns, and voucher codes. Promote configuration intentionally; do not treat one database as several logical environments.

Backups and upgrades

Postgres contains the authoritative configuration and ledgers, so include it in automated backups and test restoration. Before upgrading:

  1. read the release notes;
  2. back up Postgres;
  3. deploy and verify the version in staging;
  4. upgrade web and worker together;
  5. verify readiness, worker health, sign-in, a safe validation call, and webhook delivery.

When upgrading from v0.1.x, recreate existing webhook subscriptions after configuring WEBHOOK_SECRET_ENCRYPTION_KEY. Legacy subscriptions stored only a hash of the signing secret and cannot be delivered by v0.2.0. Deployments without webhooks are unaffected.

Observability

OfferKit exports OpenTelemetry data when an OTLP endpoint is configured:

OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <token>

Web and worker data share the export destination and can be separated by service.name. Set OTEL_SDK_DISABLED=true to disable telemetry.

Platform-specific walkthroughs

The detailed Docker Compose, Railway, and Diploi variable and service instructions are available in the self-hosting reference.

What runs where

The web service receives browser and API requests. The worker consumes queued work such as webhook deliveries, large code batches, and daily point-expiry sweeps. Stopping the worker does not stop the API; it delays that work.

Postgres stores customers, configuration, transactions, and ledgers. Redis holds the normal BullMQ job queue. Redis is optional in the job implementation: if REDIS_URL is unset on both web and worker, jobs use Postgres instead. This is a configuration choice, not automatic failover when a configured Redis server becomes unreachable. The supplied Compose file starts Redis and sets its URL explicitly.

Inside Compose, postgres and redis are service hostnames. A Node process running on your laptop instead uses localhost and the published ports. The .env.example URLs are for that host process; Compose supplies its own internal database and Redis URLs.

A liveness check asks whether a process is running. A readiness check asks whether it can serve traffic with its dependencies. Point web traffic health checks at /api/v1/ready, and check the worker separately at :9091/health.

Keep the secrets separate

SecretPurposeWhere it belongs
BETTER_AUTH_SECRETSigns staff authentication sessionsWeb service
OfferKit API keyAuthorizes your application or a local agentCalling server or local MCP environment
Webhook signing secret (whsec_…)Lets your receiver verify one subscription’s deliveriesYour webhook receiver
WEBHOOK_SECRET_ENCRYPTION_KEYEncrypts those signing secrets in the databaseSame stable value on web and worker
ADMIN_PASSWORDSeeds the first staff account on an empty databaseInitial web setup; changing it later does not reset an existing account

Back up the webhook encryption key with the database. Changing it does not rotate subscriptions; it makes existing ciphertext unreadable. To change a subscription’s signing secret, create a replacement subscription and follow the receiver rotation procedure.