Start here

Self-hosting with Docker

Self-host OpenPromoter on your server with Docker Compose and PostgreSQL 16. It has every feature with no limits under AGPL-3.0. See the steps.

Self-hosted OpenPromoter has every feature of Cloud with no revenue or partner limits. One container runs the dashboard, partner portal, REST API, tracking endpoints and background worker. It needs PostgreSQL 16 and an SMTP server or a Resend key for email.

Quick start

git clone https://github.com/openpromoter/openpromoter
cd openpromoter
docker compose up

That starts three services:

Service URL What it is
app http://localhost:3000 OpenPromoter
postgres localhost:5432 PostgreSQL 16
mailpit http://localhost:8025 Catches every email the app sends

The compose file seeds a demo workspace. Sign in with:

  • Email: demo@openpromoter.test
  • Password: openpromoter-demo

Set SEED_DEMO=false before the first start to skip the demo data.

Before you go to production

The defaults in docker-compose.yml are for trying OpenPromoter on your laptop. For a real deployment:

  1. Set APP_URL to the public URL (for example https://partners.example.com). Tracking links, emails and webhook URLs are built from it.
  2. Generate two long random secrets for BETTER_AUTH_SECRET and ENCRYPTION_KEY (for example with openssl rand -base64 32). Keep ENCRYPTION_KEY safe: it encrypts Stripe, PayPal and Wise credentials and tax IDs at rest, and they cannot be decrypted without it.
  3. Point SMTP_URL at a real mail server or set RESEND_API_KEY. Then set EMAIL_FROM.
  4. Use a managed or backed-up Postgres and change the default database password.
  5. Put the app behind HTTPS.

Environment variables

Variable Required Description
DATABASE_URL Yes Postgres connection string such as postgres://user:pass@host:5432/db
APP_URL Yes Public URL of this instance. Default http://localhost:3000
BETTER_AUTH_SECRET Yes Secret for sessions and auth tokens. 32+ random characters
ENCRYPTION_KEY Yes Key for AES-256-GCM encryption of stored secrets. 32+ random characters
SMTP_URL One of these two SMTP server such as smtp://user:pass@host:587
RESEND_API_KEY One of these two Send email through Resend instead of SMTP
EMAIL_FROM Recommended Sender such as Acme Partners <partners@acme.com>
GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET No Adds Continue with Google. Callback URL {APP_URL}/api/auth/callback/google
GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET No Adds Continue with GitHub. Callback URL {APP_URL}/api/auth/callback/github
ANTHROPIC_API_KEY No Turns on AI features with Anthropic (default model claude-opus-5-5)
OPENAI_API_KEY No Use OpenAI instead
OPENAI_BASE_URL No Any OpenAI-compatible endpoint such as Ollama or LM Studio
AI_MODEL No Override the model name
AI_PROVIDER No anthropic, openai or none. Chosen automatically from the keys if unset
HOSTED_MODE No true turns on Cloud plans, limits and Stripe Connect OAuth. Leave false when self-hosting
STRIPE_CONNECT_CLIENT_ID No Cloud only: lets founders connect Stripe with OAuth
PLATFORM_STRIPE_SECRET_KEY No Cloud only: OpenPromoter's own Stripe account for plans and OAuth
PLATFORM_STRIPE_WEBHOOK_SECRET No Cloud only: webhook secret for that account
RUN_WORKER No true (default) runs the job worker inside the web process
SEED_DEMO No true seeds the demo workspace on start (compose file only)

Without any AI variable, every AI feature still works in a reduced form. See AI features.

Each sign-in button shows only when both of its values are set. A Google or GitHub login joins an existing account only when the provider confirms the email. Accounts with two-factor on must sign in with email and password.

Background jobs

The worker sends emails and webhooks, matures commissions after the hold period, ends campaigns, runs fraud scoring and Autopilot, and prepares scheduled payouts. It keeps jobs in a Postgres table and needs no Redis.

With RUN_WORKER=true it runs inside the app process. That suits a single container. If you run several web replicas, the queue is safe to share because jobs are claimed with FOR UPDATE SKIP LOCKED.

Railway

  1. Create a project from the GitHub repo. Railway builds the Dockerfile at the root.
  2. Add the PostgreSQL plugin and set DATABASE_URL to its connection string.
  3. Set APP_URL to the generated domain (or your custom domain). Also set BETTER_AUTH_SECRET, ENCRYPTION_KEY, EMAIL_FROM and either RESEND_API_KEY or SMTP_URL.
  4. Expose port 3000.

Render

  1. Create a Web Service from the repo with the Docker runtime.
  2. Create a PostgreSQL instance and copy its internal connection string into DATABASE_URL.
  3. Add the same variables as for Railway. Use a paid instance type: free instances sleep, and the in-process worker stops while they sleep.

Upgrading

Pull the new version and restart. Database migrations run on start.

git pull
docker compose up --build -d

Read the changelog before upgrading across minor versions.

Connecting Stripe on a local instance

Stripe will not send webhooks to localhost. Run the Stripe CLI and paste the signing secret it prints into Settings > Integrations:

stripe listen --forward-to localhost:3000/api/webhooks/stripe/<workspaceId>

See Stripe for the full setup.

Edit this page on GitHub