A self-hosted Pulumi backend for tested core CLI workflows, including login, stack management, updates, and state import/export — on your own infrastructure and without a Pulumi Cloud account.
- Tested Pulumi CLI workflows — login, stack management, updates, previews, refreshes, destroys, and state import/export across documented support tiers
- Web dashboard — React SPA with stack/update/event views, API token management, and admin settings
- Admin settings panel — invite users, manage roles, view audit log, edit tenant profile (Descope mode)
- API token management — create and revoke Descope access keys from the browser dashboard
- Browser CLI login —
pulumi loginopens a browser flow; token is stored automatically - Multi-tenant authentication — dev mode with static tokens or Descope with tenant RBAC
- Role-based access control — viewer / member / admin roles enforced per-organization
- AES-256-GCM encryption — per-stack key derivation via HKDF for secrets at rest
- Horizontal scaling — serverless functions on Vercel with Neon database, stateless and zero-ops
- S3-compatible blob storage — local filesystem or any S3-compatible backend (AWS S3, MinIO, R2)
- Single process — CLI API + tRPC dashboard share one Hono server
- Production deployment — Deploy to Vercel with a single git push
- Stack search — full-text search with tag filtering and cursor-based pagination
- Webhooks — outbound HTTP event delivery with HMAC-SHA256 signing and retries
- Audit logs — automatic audit trail via Descope management API
- GitHub Actions — Procella-defaulted Pulumi and ESC actions, plus tenant-bound GitHub App setup and repository-scoped OIDC authentication
Benchmarked from Iowa (USA) against Procella deployed in us-east-1 (N. Virginia), N=10 resources, journal mode:
| Backend | up |
preview |
destroy |
|---|---|---|---|
| Procella (AWS) | 1,412ms | 677ms | 1,087ms |
| Pulumi Cloud | 1,647ms | 749ms | 1,158ms |
Procella is faster than Pulumi Cloud. Server-side processing is ~55ms p50 per request on both — the difference comes from infrastructure choices (CloudFront edge termination, direct Lambda invocation). See the benchmarking docs for methodology and how to run your own comparison.
| Component | Technology |
|---|---|
| Runtime | Bun 1.2 |
| HTTP Router | Hono v4 |
| Dashboard API | tRPC v11 + Drizzle ORM |
| Dashboard UI | React 19, Vite 7, Tailwind CSS v4 |
| Database | Neon Serverless PostgreSQL |
| Auth | Descope / static tokens |
| Encryption | AES-256-GCM + HKDF |
| Blob Storage | Local filesystem / S3 |
| Hosting | Vercel (serverless functions + static sites) |
| Quality | Biome + TypeScript strict |
# Clone and start the dev environment
git clone https://github.com/tektum/procella.git
cd procella
bun run devThis starts PostgreSQL, MinIO, the Bun server (with hot-reload), and the Vite UI dev server locally.
# Dev mode — set token directly
export PULUMI_ACCESS_TOKEN=devtoken123
pulumi login http://localhost:9090
# Descope mode — browser login flow (PULUMI_CONSOLE_DOMAIN is set in mise.toml)
pulumi login http://localhost:9090
# The CLI opens your browser to /cli-login, you sign in via Descope,
# and the token is stored automatically in ~/.pulumi/credentials.json
# Create and deploy a stack
mkdir my-project && cd my-project
pulumi new typescript
pulumi up| Platform | Method | Config file |
|---|---|---|
| Render | One-click Blueprint | render.yaml |
| Railway | One-click from repo | railway.toml |
| Vercel | One-click clone | vercel.json |
| Fly.io | fly launch && fly deploy |
fly.toml |
| Coolify | Docker Compose in Coolify UI | docker-compose.coolify.yml |
| Docker Compose | docker compose --profile dev up |
docker-compose.yml |
All platforms need PostgreSQL and the PROCELLA_* environment variables. See the full configuration reference.
All configuration is via PROCELLA_* environment variables. Set these as Vercel environment variables for production deployment. See .env.example for a complete reference.
| Variable | Default | Description |
|---|---|---|
PROCELLA_LISTEN_ADDR |
:9090 |
Server listen address |
PROCELLA_DATABASE_URL |
(required) | PostgreSQL connection string (Neon on Vercel, any PostgreSQL locally) |
PROCELLA_SUBSCRIPTION_MAX_CONCURRENT |
500 |
Per-replica ceiling on concurrent dashboard subscriptions |
PROCELLA_AUTH_MODE |
(required) | dev (static tokens) or descope (Descope access keys) |
PROCELLA_DEV_AUTH_TOKEN |
(required if dev) | Static auth token for dev mode |
PROCELLA_DEV_USER_LOGIN |
dev-user |
Dev user login name |
PROCELLA_DEV_ORG_LOGIN |
dev-org |
Dev org login name |
PROCELLA_DEV_USERS |
JSON array of additional dev users | |
PROCELLA_DESCOPE_PROJECT_ID |
(required if descope) | Descope project ID |
PROCELLA_DESCOPE_MANAGEMENT_KEY |
Descope management key — enables pulumi login browser flow and API token creation |
|
PROCELLA_BLOB_BACKEND |
local |
local (filesystem) or s3 (S3-compatible) |
PROCELLA_BLOB_LOCAL_PATH |
./data/blobs |
Local blob storage path |
PROCELLA_BLOB_S3_BUCKET |
(required if s3) | S3 bucket name |
PROCELLA_BLOB_S3_ENDPOINT |
Custom S3 endpoint (MinIO, R2, etc.) | |
PROCELLA_BLOB_S3_REGION |
us-east-1 |
S3 region |
PROCELLA_ENCRYPTION_KEY |
(required) | 64 hex chars (32 bytes) for AES-256-GCM |
PROCELLA_LEGACY_DECRYPTION_ENABLED |
true |
Keep v1 reads enabled until the documented per-stack v2 rewrite is complete |
PROCELLA_LEGACY_ORG_MAPPINGS |
{} |
Unique JSON tenant-ID → original org-slug mappings required for Descope v1 reads |
PROCELLA_DELTA_CHECKPOINTS_ENABLED |
false |
Advertise delta-checkpoint-uploads-v2; disable and restart to return clients to full checkpoints |
PROCELLA_CRON_SECRET |
(required when /cron/gc is enabled) |
Bearer token used to authorize the GC cron endpoint |
PROCELLA_CORS_ORIGINS |
(optional) | Comma-separated allowed CORS origins; omit for strict same-origin |
PROCELLA_GITHUB_APP_ID |
(optional as a group) | Positive canonical-decimal GitHub App ID within the safe integer range |
PROCELLA_GITHUB_APP_PRIVATE_KEY |
(optional as a group) | Valid RSA GitHub App private key (PEM format) |
PROCELLA_GITHUB_APP_WEBHOOK_SECRET |
(optional as a group) | Non-whitespace GitHub App webhook secret; bytes are preserved |
PROCELLA_GITHUB_OUTBOUND_APP_ID |
procella-github |
Descope Outbound Application that vaults each admin's GitHub user token |
PROCELLA_APP_ORIGIN |
(required for GitHub setup) | Absolute https dashboard origin the Descope outbound callback returns to |
| PROCELLA_TRUST_PROXY | (optional) | Set to true only behind a trusted reverse proxy so Procella honors X-Forwarded-For / X-Real-IP |
For the optional GitHub App integration, use a dedicated App and configure all three PROCELLA_GITHUB_APP_* credential variables; omit all three to disable it. GitHub user authorization runs through a Descope Outbound Application (procella-github), so the App's OAuth callback is https://api.descope.com/v1/outbound/oauth/callback and the GitHub OAuth client ID and secret are deploy-time inputs to scripts/provision-descope-outbound-app.ts only, never runtime configuration. Grant read-only organization Members permission. During setup a Procella administrator enters the exact GitHub account login; Procella mints a one-time connect transaction bound to the tenant, that administrator, the requested account, and a __Host- browser nonce, carries it inside the Descope redirect URL, and consumes it in the initiating browser before issuing browser-bound installation state. Vaulted tokens are tenant scoped, and account administration is proven server-side against GitHub before the tenant binding is saved. Tenant setup also needs PROCELLA_DESCOPE_PROJECT_ID, PROCELLA_DESCOPE_MANAGEMENT_KEY, and PROCELLA_APP_ORIGIN; without them webhooks and PR publication still work while setup fails closed. SST deployments additionally require PROCELLA_GITHUB_APP_ENABLED=true; unset or false keeps the integration disabled and removes retained SST secrets.
Encryption keys must be set explicitly in every environment. Generate one with openssl rand -hex 32.
By default Procella does not mount CORS middleware. Set PROCELLA_CORS_ORIGINS explicitly when you need browser cross-origin access.
Using PROCELLA_CORS_ORIGINS=* is allowed for local experiments, but it enables any origin and should never be used in production.
- Set
PROCELLA_TRUST_PROXY=trueonly when Procella is behind a trusted reverse proxy (for example Caddy or CloudFront) that sanitizes and re-emits client IP headers. - When Procella is reached directly, leave
PROCELLA_TRUST_PROXYunset so audit logging and rate limiting use the socket peer IP instead of user-supplied forwarded headers. apps/ui/Caddyfilekeepsauto_https offbecause it assumes TLS is terminated upstream. For self-hosted deployments without an upstream TLS terminator, removeauto_https off(or setauto_https on) and configure Caddy for Let's Encrypt.
bun run check # biome lint + typecheck + 320 unit tests
bun run e2e # E2E acceptance tests (89 tests)
bun run check:all # check + e2eFull documentation is available in the apps/docs/ directory, built with Starlight:
bun run docs:dev # Start docs dev server
bun run docs:build # Build static docs sitepackages/
types/ Pulumi protocol types + domain types + errors
config/ Zod-validated env config (PROCELLA_*)
db/ Drizzle schema + dual-driver connection factory (Neon / node-postgres)
crypto/ AES-256-GCM with HKDF per-stack key derivation
storage/ Blob storage (local filesystem + S3)
auth/ Dev mode (static token) + Descope (JWT)
stacks/ Stack CRUD, rename, tags (PostgreSQL)
updates/ Update lifecycle, checkpoints, events, GC worker
api/ @procella/api — tRPC router (stacks, updates, events)
apps/
server/ @procella/server — Hono HTTP server (CLI + tRPC + middleware)
ui/ @procella/ui — React SPA (Vite + Tailwind + tRPC client)
Pages: StackList, StackDetail, UpdateDetail, Tokens, Settings, CliLogin
examples/ Pulumi YAML example programs (7 examples)
e2e/ E2E acceptance tests (89 tests, 9 files)
docs/ @procella/docs — Starlight documentation site
See LICENSE for details.