Guide
Self-hosted backend
The API (apps/api) is a standard NestJS service backed by PostgreSQL. Deploying it yourself means signature records never leave your infrastructure.
Requirements
- Node.js 20+ and PostgreSQL 14+
- A GitHub and/or Google OAuth app registered under your own domain — you can't reuse SignFlow's hosted OAuth app, since callback URLs are tied to a specific domain
- S3-compatible object storage — only if you store signatures as rasterized images. The default SVG-path capture mode stores everything directly in Postgres and needs no object storage at all.
Quick start (Docker Compose)
A minimal starting point — adjust image tags and secrets before running this anywhere but locally.
services:
api:
build: ./apps/api
ports:
- "3001:3001"
environment:
DATABASE_URL: postgres://signflow:signflow@postgres:5432/signflow
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
SESSION_SECRET: ${SESSION_SECRET}
PUBLIC_APP_URL: https://your-domain.example.com
depends_on:
- postgres
postgres:
image: postgres:16
environment:
POSTGRES_USER: signflow
POSTGRES_PASSWORD: signflow
POSTGRES_DB: signflow
volumes:
- signflow_pg:/var/lib/postgresql/data
volumes:
signflow_pg:Run migrations before first use — npm run db:migrate (adjust to whatever migration tooling apps/api actually ships with).
Add a MinIO (or other S3-compatible) service to this file only if you're using rasterized signature storage — it's not required for the default SVG-path mode.
Environment variables
| Variable | Required | Description |
|---|---|---|
| DATABASE_URL | Yes | PostgreSQL connection string |
| GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | At least one provider | Your own GitHub OAuth App, scoped to your domain |
| GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | At least one provider | Same, for Google OAuth |
| SESSION_SECRET | Yes | Random string used to sign session cookies |
| PUBLIC_APP_URL | Yes | The public URL your instance is reachable at — used to build OAuth callback URLs |
| S3_ENDPOINT / S3_BUCKET / S3_ACCESS_KEY / S3_SECRET_KEY | Only for raster mode | Not needed when signatures are stored as SVG path data (the default) |
Pointing the SDK at your instance
The widget and API client need to talk to your deployment instead of the hosted default:
const client = new SignClient({
apiKey: 'pk_live_...',
baseUrl: 'https://api.your-domain.example.com',
});baseUrl is optional in SignClient's constructor and defaults to the hosted instance — a self-hosted deployment must always pass it explicitly, or submissions will silently go to the default backend instead of yours.Reverse proxy & TLS
The API doesn't terminate TLS itself — put it behind a reverse proxy (Caddy, Nginx, or Traefik all work fine) that handles certificates and forwards to the container's port. Make sure PUBLIC_APP_URL matches the externally-reachable HTTPS URL, since OAuth callback verification depends on it matching exactly.
Backups & data ownership
Signature records live in Postgres — back it up the way you would any production database (scheduled pg_dump, or your provider's point-in-time recovery). If you're using object storage for raster assets, back that up independently — it isn't covered by a Postgres backup.
SignFlow doesn't enforce a retention policy — how long you keep records, and your deletion process for right-to-erasure requests, is entirely under your control once self-hosted (see the Security & Compliance guide).
Self-hosting vs. the hosted dashboard
Self-host if data residency, retention control, or infrastructure ownership matters for your use case — you take on running Postgres and the API yourself in exchange for full control.
Use the hosted dashboard flow instead if you'd rather not run infrastructure at all — same API surface, same key model, just managed for you. Both point at the same client SDK; switching later is a matter of changing baseUrl, not rewriting integration code.