OpenWeb is a monorepo CMS platform with:
apps/web: React + Vite admin/public frontendapps/api: Fastify + Drizzle API- Postgres for persistence
- NGINX reverse proxy in Docker deployment
apps/web/frontend appapps/api/backend APIapps/api/drizzle/SQL migrationsapps/api/uploads/uploaded media in local/dev runtimedeploy/Docker Compose/Swarm deployment assetssetup-deploy.shinteractive production setup script (root)
Use the interactive root script:
./setup-deploy.shThis script handles environment setup, Docker deployment, migrations, and optional restore from backup ZIP.
Install and verify:
docker --version
docker compose version
unzip -vRecommended host requirements:
- Linux server or VM
- Docker Engine 24+
- Port
80open - Sufficient disk space for Postgres, uploads, and backups
When you run the script, it prompts for:
- Postgres database name
- Postgres username
- Postgres password
- Docker type (deployment mode):
1= Docker Compose2= Docker Swarm (autoscaler enabled)
- If Docker Swarm chosen: stack name
- Whether you have a backup ZIP to restore now
- Backup ZIP path (if you answered yes)
JWT_SECRET is auto-generated by the script.
Use this for single-server deployments.
Script behavior:
- Writes
deploy/.env - Runs:
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
- Applies all SQL files in
apps/api/drizzle/*.sqlin order - If backup ZIP provided:
- Restores
database.sql - Restores
uploads/contents
- Restores
Use this when you want autoscaling of API/Web.
Script behavior:
- Builds required images:
openweb-api:latestopenweb-web:latestopenweb-autoscaler:latest
- Initializes swarm if needed (
docker swarm init) - Deploys stack from
deploy/docker-stack.yml - Applies all SQL migrations
- If backup ZIP provided:
- Restores
database.sql - Restores
uploads/
- Restores
Autoscaling runs only in Swarm mode.
The script prints URLs:
- App entry:
http://localhost - First-time setup:
http://localhost/setup - Admin:
http://localhost/admin
If this is a fresh install:
- Open
/setup - Create initial admin user
- Login and configure site
If you choose to restore a backup ZIP during setup, the ZIP must contain at least:
database.sql- optional
uploads/directory
Restore behavior:
- Drops and recreates public schema
- Imports
database.sql - Replaces uploads content if provided
# status
docker compose -f deploy/docker-compose.yml --env-file deploy/.env ps
# logs
docker compose -f deploy/docker-compose.yml --env-file deploy/.env logs -f
# stop
docker compose -f deploy/docker-compose.yml --env-file deploy/.env down
# scale manually
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --scale api=3 --scale web=3# list services
docker stack services openweb
# service logs
docker service logs -f openweb_api
docker service logs -f openweb_web
docker service logs -f openweb_autoscaler
# manual scale override
docker service scale openweb_api=4
docker service scale openweb_web=4
# remove stack
docker stack rm openwebIf you selected a different stack name in setup, replace openweb accordingly.
In admin: System -> Backups
- Create ZIP backup
- Download backup ZIP
- Restore by uploading ZIP
OpenWeb supports:
- local email/password login
- Google OAuth login
- Microsoft Entra ID (Azure AD) login
- custom OIDC IdP login (Okta/Auth0/Keycloak/etc.)
The login page automatically shows only SSO providers that are configured.
First time? Run setup is only shown when /api/setup/needed returns needed: true.
- User opens login page.
- Web app calls
GET /api/auth/sso/providers. - User clicks
Sign in with <provider>. - Browser is redirected to
/api/auth/sso/:provider/start. - API sends user to provider authorization URL.
- Provider redirects back to
/api/auth/sso/:provider/callback. - API exchanges code for tokens, fetches profile, upserts the user, issues OpenWeb JWT.
- Callback writes
openweb_tokenandopenweb_userto localStorage and redirects to/admin(or requested redirect path).
Notes on account mapping:
- Existing email: account is linked to that SSO provider.
- New email: a new user is created.
- First user ever created via SSO becomes
admin; next users default tosubscriber.
Set these in your API env:
- Production Docker:
deploy/.env(used bysetup-deploy.sh) - Dev run:
apps/api/.env
Common:
JWT_SECRET(already auto-generated insetup-deploy.shfor production)
Google:
SSO_GOOGLE_CLIENT_IDSSO_GOOGLE_CLIENT_SECRET
Microsoft:
SSO_MICROSOFT_CLIENT_IDSSO_MICROSOFT_CLIENT_SECRETSSO_MICROSOFT_TENANT_ID(optional, defaults tocommon)
Custom OIDC:
SSO_OIDC_LABEL(button label, defaultSSO)SSO_OIDC_CLIENT_IDSSO_OIDC_CLIENT_SECRETSSO_OIDC_AUTH_URLSSO_OIDC_TOKEN_URLSSO_OIDC_USERINFO_URLSSO_OIDC_SCOPES(defaultopenid profile email)
You must register these callback URLs in each provider app:
- Google callback:
https://YOUR_DOMAIN/api/auth/sso/google/callback
- Microsoft callback:
https://YOUR_DOMAIN/api/auth/sso/microsoft/callback
- OIDC callback:
https://YOUR_DOMAIN/api/auth/sso/oidc/callback
For local development (Vite + API):
http://localhost:3000/api/auth/sso/google/callbackhttp://localhost:3000/api/auth/sso/microsoft/callbackhttp://localhost:3000/api/auth/sso/oidc/callback
Important:
- Callback host/protocol must match the real public URL seen by users/proxy.
- If using reverse proxy/TLS termination, ensure forwarded host/proto headers are correct.
- Create OAuth credentials in Google Cloud Console.
- Add authorized redirect URI:
https://YOUR_DOMAIN/api/auth/sso/google/callback
- Add credentials to env:
SSO_GOOGLE_CLIENT_IDSSO_GOOGLE_CLIENT_SECRET
- Restart API service.
- Verify login page shows
Sign in with Google.
- Register app in Microsoft Entra admin center.
- Add redirect URI:
https://YOUR_DOMAIN/api/auth/sso/microsoft/callback
- Create client secret.
- Set env:
SSO_MICROSOFT_CLIENT_IDSSO_MICROSOFT_CLIENT_SECRETSSO_MICROSOFT_TENANT_ID(optional; keepcommonfor multi-tenant)
- Restart API service.
- Verify login page shows
Sign in with Microsoft.
If your organization uses LDAP, expose it via an IdP that supports OIDC (for example Keycloak, Authentik, Okta, Auth0, etc.). OpenWeb integrates via OIDC endpoints.
- In your IdP, create an OIDC client.
- Configure callback:
https://YOUR_DOMAIN/api/auth/sso/oidc/callback
- Ensure
emailand subject (sub) claims are returned by userinfo. - Set env:
SSO_OIDC_LABEL=Your Company SSOSSO_OIDC_CLIENT_IDSSO_OIDC_CLIENT_SECRETSSO_OIDC_AUTH_URLSSO_OIDC_TOKEN_URLSSO_OIDC_USERINFO_URLSSO_OIDC_SCOPES=openid profile email
- Restart API service.
- Verify login page shows your custom button label.
- Always use HTTPS in production.
- Use strong, rotated client secrets.
- Keep
JWT_SECRETlong and random. - Restrict who can create users in external IdP if needed (group policy/tenant policy).
- Back up database regularly; SSO user links are stored in users table.
- SSO button not visible:
- missing or empty SSO env vars
- API not restarted after env update
- Provider says redirect URI mismatch:
- callback URL in provider does not exactly match OpenWeb callback
- Login returns 400
Provider did not return required account data:- provider userinfo is missing
emailorsub
- provider userinfo is missing
- Login returns 502 token/userinfo errors:
- invalid client secret/ID, wrong token URL/userinfo URL, or provider-side issue
- User can’t access admin:
- role is
subscriber; promote user role in admin/users data
- role is
Use this mode when coding locally with hot reload.
- Node.js 22+
- npm 10+
- Postgres running locally (or remote instance)
From repository root:
npm installCreate API env file:
cp apps/api/.env.example apps/api/.envEdit apps/api/.env and set:
PORT(default3000)DATABASE_URLJWT_SECRET- Optional SSO vars (
SSO_GOOGLE_*,SSO_MICROSOFT_*,SSO_OIDC_*)
Example:
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/openweb
JWT_SECRET=dev-secret-change-meFrom repo root:
npm run db:migrateWhen schema changes are introduced:
npm run db:generate
npm run db:migrateRun API and web in separate terminals:
npm run dev:api
npm run dev:webDefaults:
- API:
http://localhost:3000 - Web:
http://localhost:5173
Vite proxies /api, /uploads, /health, /robots.txt, and /sitemap.xml to API.
- Public:
http://localhost:5173 - Admin:
http://localhost:5173/admin - Setup:
http://localhost:5173/setup
Before committing/deploying:
npm run build:api
npm run build:webDetailed plugin documentation is available in PLUGINS.md.
- Template ZIP:
hello-world-plugin-template.zip - Template source:
templates/plugins/hello-world/ - Recommended: test plugins locally from your GitHub clone before production install.
From root:
# dev
npm run dev:api
npm run dev:web
# build
npm run build:api
npm run build:web
# db
npm run db:generate
npm run db:migrate- Persistent Docker volumes include database, uploads, and backups.
- Never commit secrets (
deploy/.env,apps/api/.env).
For low-level deployment internals, see deploy/README.md.