Configuration
Reference for every environment variable, optional integrations, custom branding, and how to upgrade between releases.
Required variables
These must be set for the stack to start. The bundled docker-compose.yml reads them from the .env file at the repo root.
DOMAINrequired | The public hostname this instance serves on. Used by Traefik for routing and Let's Encrypt for cert issuance. No protocol — e.g. notipo.example.com. |
ACME_EMAILrequired | Email address Let's Encrypt sends cert-expiry notifications to. |
DB_PASSWORDrequired | Postgres password for the bundled notipo user. Generated once with openssl rand -hex 24 is plenty. |
DATABASE_URLrequired | Postgres connection string. With the bundled compose stack: postgresql://notipo:${DB_PASSWORD}@postgres:5432/notipo. |
ENCRYPTION_KEYrequired | 64-character hex string used to encrypt every WordPress and Notion credential at rest. Generate once with openssl rand -hex 32 and back it up. Rotating it loses access to all stored credentials. |
API_KEYrequired | Admin API key for the /api/admin/* routes. Long random string. |
ALLOW_SIGNUP | Whether new accounts can be created via the public signup page. Set to false after creating your account if you want a single-tenant instance. Default: true |
Email (optional)
Notipo only sends transactional email if Resend is configured. Without it, the API skips email entirely and adapts:
- Signup: new accounts are auto-verified on creation and the user is logged in immediately. No verification link sent.
- Password reset: the "forgot password" flow returns success but no email is sent — there's no way to reset a password without email. If you lose your password and Resend isn't configured, you have to reset the
passwordHashdirectly in the database. - Admin signup notifications, onboarding reminders, trial-expiry warnings: all silently no-op.
If you want any of those features, configure Resend:
RESEND_API_KEY | Resend API key. Sign up at resend.com; free tier covers most personal use. |
RESEND_FROM_EMAIL | Address transactional emails are sent from. Domain must be verified in Resend (SPF + DKIM). |
Custom branding
Two variables let you rebrand transactional emails without forking the codebase — set them and the welcome / verification / password-reset emails switch from saying "Notipo" to whatever you choose:
BRAND_NAME | Display name used in email subject lines and bodies. E.g. "Acme Publish". Default: Notipo |
SUPPORT_EMAIL | Reply-to address shown in transactional emails. Default: support@notipo.com |
Optional integrations
These unlock additional features. The stack works without any of them.
GEMINI_API_KEY | Enables AI-generated featured images via Google Gemini's free 2.5 Flash Image model. Without it, featured images use Unsplash photos or category-based fallbacks. |
UNSPLASH_ACCESS_KEY | Unlocks the Unsplash search backend for featured images. Without it, the featured-image generator falls back to a gradient. |
STRIPE_SECRET_KEY | Enables paid plans + Stripe Checkout. Leave unset for self-host mode (all features unlocked, no plan limits, new signups land on Pro automatically). |
STRIPE_WEBHOOK_SECRET | Required if STRIPE_SECRET_KEY is set. Verifies inbound Stripe webhook signatures. |
STRIPE_PRO_PRICE_ID | Required if STRIPE_SECRET_KEY is set. Stripe price ID for the Pro plan. |
NOTION_OAUTH_CLIENT_ID | Enables Notion OAuth in addition to manual integration tokens. Create a public Notion integration to get the ID and secret. |
NOTION_OAUTH_CLIENT_SECRET | Required if NOTION_OAUTH_CLIENT_ID is set. |
NOTION_WEBHOOK_SECRET | Enables instant Notion webhook delivery (instead of the 5-minute fallback poll). Set to whatever Notion provides during webhook subscription. |
GCS_BUCKET | Google Cloud Storage bucket name for category-image uploads. If unset, uploads fall back to local disk under the uploads/ volume. |
ADMIN_NOTIFY_EMAIL | If set, Notipo sends you an email whenever a new user signs up. Useful for monitoring a private instance. |
POLL_INTERVAL_SECONDS | Notion safety-net poll interval. The Notion webhook is the primary trigger; this catches missed events. Default: 300 |
LOG_LEVEL | pino log level. One of trace, debug, info, warn, error.Default: info |
Upgrading
New tagged releases publish multi-arch images to GHCR. To upgrade:
# Pull the latest images (or a specific tag)
docker compose pull
# Recreate containers with new images. Migrations run automatically
# from the API container's entrypoint on startup.
docker compose up -dFor predictable upgrades, pin to a specific minor in docker-compose.yml instead of :latest:
services:
app:
image: ghcr.io/kfuras/notipo-api:1.2
web:
image: ghcr.io/kfuras/notipo-web:1.2That gets you patches automatically (1.2.0 → 1.2.1 → 1.2.2) without surprise majors. Bump the minor manually after reading the release notes.
docker compose exec postgres pg_dump -U notipo notipo > backup.sqlis the simplest one-liner. Migrations are forward-only — there's no built-in down path.Backing up your data
Two pieces of state matter:
- The Postgres database — users, posts, jobs, encrypted credentials. Back it up regularly.
- Your
ENCRYPTION_KEY— without it, the encrypted credentials in the DB are useless. Store it separately from the DB backup so a single-source compromise can't exfiltrate both.
# Database backup
docker compose exec -T postgres pg_dump -U notipo notipo > notipo-backup.sql
# Restore
cat notipo-backup.sql | docker compose exec -T postgres psql -U notipo -d notipoRelated
Continue reading