Self-host Quickstart

Get Notipo running with Docker Compose, Traefik, and Let's Encrypt TLS in about five minutes.

Prerequisites

Before you start, make sure you have:

  • A Linux VPS with Docker Engine 24+ and the Compose plugin installed (1 vCPU / 1 GB RAM is enough for personal use)
  • A domain name with an A record pointing at the server's public IP
  • Ports 80 and 443 open to the internet (Let's Encrypt verifies via HTTP-01 on port 80, then traffic moves to 443)
  • A WordPress site with Application Passwords enabled (WP 5.6+)
  • Optional: a Resend account if you want email verification, password-reset emails, or admin signup notifications. Without it, signups are auto-verified on creation.
1

Clone the repo

The compose stack lives in the app repo. Clone it onto your server:

git clone https://github.com/kfuras/notipo-app.git
cd notipo-app

You're only going to use docker-compose.yml and the apps/api/.env.example file from this repo. The Docker images themselves are pulled from GHCR — no local build needed.

2

Create the .env file

Copy the example file to the repo root and fill in the values:

cp apps/api/.env.example .env

Required variables:

# Domain that points at this server's public IP (no protocol).
DOMAIN=notipo.example.com

# Email used by Let's Encrypt for cert expiry notifications.
ACME_EMAIL=admin@example.com

# Postgres password for the bundled database container.
DB_PASSWORD=$(openssl rand -hex 24)

# Required by Notipo to wire DATABASE_URL inside containers.
DATABASE_URL=postgresql://notipo:${DB_PASSWORD}@postgres:5432/notipo

# 64-char hex key — encrypts WordPress and Notion credentials at rest.
# Generate ONCE and back up safely. Rotating this loses access to all
# stored credentials.
ENCRYPTION_KEY=$(openssl rand -hex 32)

# Admin API key for the /api/admin/* routes. Long random string.
API_KEY=$(openssl rand -hex 32)

# Allow new users to sign up. Set to false to lock down to existing users only.
ALLOW_SIGNUP=true

# Optional: Resend account for transactional email. If unset, signups
# auto-verify on creation and password-reset emails are unavailable.
# RESEND_API_KEY=re_...
# RESEND_FROM_EMAIL=noreply@example.com
Back up ENCRYPTION_KEY
Notipo encrypts every WordPress application password and Notion token with this key. If you lose it, every connected integration becomes unrecoverable and users have to reconnect from scratch. Treat it like a database backup — store a copy somewhere safe before you start the stack.

See Configuration for the full env reference, including optional features (Notion OAuth, Gemini AI images, Stripe billing, custom branding for the email templates).

3

Start the stack

One command pulls the images, runs Postgres, applies database migrations on first boot, and starts the API and admin UI behind Traefik with a Let's Encrypt certificate:

docker compose up -d

Tail the logs to watch the cert request and the first migration run:

docker compose logs -f

Once you see Server listening on 0.0.0.0:3000 from the API container and Traefik reports the cert is issued, the stack is ready.

4

Create the first account

Open https://your-domainin a browser. You'll be redirected to the signup page. Register with your email and password — without Resend configured, the account is auto-verified on creation and you're logged in immediately.

Self-host mode (no STRIPE_SECRET_KEY set) skips the trial and lands new accounts on the Pro plan automatically. All features unlocked, no usage limits.

To lock the instance to existing users only after you've created your account, set ALLOW_SIGNUP=false in .env and run docker compose up -d again.

5

Connect WordPress

From the dashboard, enter your WordPress site URL and click Connect WordPress. WordPress redirects you to its application-password approval page; one click and you're back in Notipo with the credentials saved (encrypted with your ENCRYPTION_KEY).

Notipo must be on HTTPS
The one-click flow only works when Notipo is served over HTTPS. WordPress core rejects non-HTTPS values for the success_url parameter (with a localhost / 127.0.0.1 exception for dev). The compose stack handles this automatically via Traefik + Let's Encrypt — if you put Notipo behind a TLS-terminating proxy you control yourself, make sure the public URL ends up as https://. If you can't (yet), use the Enter credentials manually link to paste your username + application password directly.

Open the Write page and publish your first post end-to-end as a quick smoke test.

Common issues

Let's Encrypt rate-limit during repeated startup tests

If you restart the stack many times during setup, Let's Encrypt will rate-limit cert issuance for ~1 hour. Use the staging environment while testing by editing the Traefik command in docker-compose.yml to add --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory, then remove it once you're ready for the real cert.

Resend emails not arriving (only if Resend is configured)

Verify your RESEND_FROM_EMAIL domain inside the Resend dashboard. Until DNS records (SPF, DKIM, DMARC) are verified, Resend silently drops messages. Check the Resend logs first — if you don't see the message, the API request never landed; check docker compose logs appfor an error. (If you don't need transactional email, leave Resend unset — signups auto-verify in self-host mode.)

One-click WordPress flow fails or doesn't redirect back

Most likely one of two things:

  • Notipo is not on HTTPS. WordPress refuses non-HTTPS success_url values (localhost is the only exception). Put TLS in front of Notipo before retrying.
  • WordPress can't reach Notipo from its loopback IP. Some hosts block outbound from the WP server — the inline credentials test from the Notipo container can't complete the round-trip.

In both cases, click Enter credentials manually and paste your WordPress username + application password directly. The manual fallback works without HTTPS or loopback connectivity.

ARM hosts (Apple Silicon, Raspberry Pi)

The GHCR images are multi-arch (linux/amd64 + linux/arm64), so ARM works out of the box — Docker pulls the right variant automatically.