Zero-to-App Local Quickstart
Go from nothing to an app running against Productify locally — the whole platform in one command, an example app wired up, and a trigger firing on your machine.
Requires the pfy dev stack
This guide uses the pfy dev commands (up, seed, proxy, status, down). They ship in the CLI; the platform images they start are pulled from ghcr.io/productifyfw/*. You need Docker + Docker Compose and the pfy CLI on your PATH.
The two dev loops
Productify supports two local development loops. Know which you are in:
| Loop | What runs | Identity | Use it for |
|---|---|---|---|
| Bare | Just your app + the tiny pfy dev proxy (or the @productifyfw/dev Vite shim) | A fixed dev identity you choose, injected as the production header set | Pure domain/UI work — fast inner loop, no containers, no OIDC |
| Full-stack | The whole platform (Manager, database, Pocket ID, real proxy) | Real login through Pocket ID | Auth-adjacent work, tenant switching, end-to-end behavior |
The bare loop gives you the exact production header contract without the four containers or the OIDC dance — that middle ground is where most integration bugs hide. The full-stack loop is the real thing. This quickstart walks both, in order.
1. Bring up the platform and seed it
pfy dev up && pfy dev seedpfy dev upwrites (if absent) and starts the pinned-version compose stack — Manager + PostgreSQL + Pocket ID + proxy — under the Docker Compose projectpfy-dev. Add--profile minimalto start only Manager + database for the bare loop.pfy dev seedidempotently bootstraps a project, adevtenant, an application, and a machine user, then writes two generated files (see Generated files). Re-running it is safe — it creates nothing that already exists.
Check health any time:
pfy dev statusPointing the CLI at the local Manager
pfy dev seed talks to the Manager the stack just started. If the CLI can't find it, set PFY_MANAGER_URL=http://localhost:8080 (or pass --manager).
Generated files
pfy dev seed writes two files into your app repo and gitignores both — they hold dev-only, visibly-fake (dev--prefixed) values, never production credentials:
| File | Purpose | Consumed by |
|---|---|---|
.env.productify.dev | MANAGER_URL, APPLICATION_ID, MACHINE_USER_TOKEN, and PROXY_SHARED_SECRET (when set) | Your backend, pfy dev proxy |
dev.productify.json | A fake window.__PRODUCTIFY__ payload with a dev users list | The @productifyfw/dev Vite shim, pfy dev proxy |
One dev.productify.json feeds the shim, the dev proxy, and your tests — one source of truth for local identity.
2. Get the example app
Scaffold a new project, or clone an example to start from something that runs:
# scaffold a fresh repo
pfy init
# — or — clone an example from the monorepo
git clone https://github.com/ProductifyFW/thesis
cd thesis/be-integrations/packages/node-express-example # backend example
# (a Vue 3 frontend example lives under fe-integrations/)Run pfy dev seed from your app's repo root so the generated files land beside its code.
3. Bare loop — the Vite shim
For pure frontend work, the @productifyfw/dev Vite plugin injects the same window.__PRODUCTIFY__ object the real proxy would, reading the dev.productify.json from step 1 — no containers required.
// vite.config.ts
import { defineConfig } from "vite";
import { productifyDevShim } from "@productifyfw/dev";
export default defineConfig({
plugins: [
productifyDevShim({ configFile: "dev.productify.json" }),
],
});npm run devYour SPA now sees a fully-populated window.__PRODUCTIFY__ (user, tenant, application, logout_url) exactly as in production. The shim hot-reloads when dev.productify.json changes and no-ops in production builds.
4. Bare loop — the header contract via pfy dev proxy
To exercise your backend against the real header contract without Pocket ID, put the header-injecting dev proxy in front of it:
pfy dev proxy --upstream localhost:8080 --port 9000It forwards to your app injecting the standard identity headers (X-User-*, X-Tenant-*, X-Auth-Type: oauth, and X-Proxy-Auth when a secret is set) from dev.productify.json. It binds loopback only and performs no authentication — the startup banner says so.
Switch identity or tenant instantly, no cookies, no OIDC:
curl http://127.0.0.1:9000/_dev/users
curl -X POST http://127.0.0.1:9000/_dev/switch -d '{"user":"dev@local.test"}'This is the closest bare-loop fidelity to production: your backend sees the exact headers the real proxy sends.
5. Full-stack loop — real login through the proxy
Now the real thing. The full stack from step 1 already runs the production proxy and Pocket ID. Reach your app through the proxy's hostname (see the Caddyfile for the vhosts the dev stack serves, e.g. http://vue3-example.localhost) and you get a real Pocket ID login, real tenant selection, and the proxy-injected identity headers.
Create the Pocket ID dev user
pfy dev seed prints a reminder to create the Pocket ID dev user (dev@local.test) for the full-stack login. Creating a Pocket ID account is not something the CLI does — the Manager's Pocket ID integration is read-only (see Session policy). Create it once in the Pocket ID admin UI (http://pocketid.localhost), using the same email you passed to pfy dev seed (--dev-user-email, default dev@local.test). Pocket ID owns passkey/email-code onboarding.
Then sign in through the app's hostname in the browser and complete the OIDC flow. Signing out uses the standardized logout contract — window.__PRODUCTIFY__.logout_url — which clears both the portal session and the tenant cookie.
Driving the Manager from the CLI
To operate the Manager from the CLI in the full-stack loop, log in and confirm:
pfy login --web # browser SSO (device flow); or --token / --token-stdin
pfy whoami # confirms the manager URL and token work6. Fire your first trigger
Triggers are evaluated by the Manager's executor (running in the stack, on PFY_CRON_TRIGGER_CHECK_INTERVAL, default every 30s). Create a trigger for your seeded application in the Manager UI (or via GraphQL — see the API Reference), point it at an endpoint on your app, and watch the executor invoke it locally within the check interval. See Triggers & Automation for authoring triggers.
That is the full path: platform up, app wired, both loops exercised, a trigger firing on your machine.
Tear down
pfy dev down # stop the stack (keep data)
pfy dev down --volumes # stop and destroy the local databaseWhere to go next
- Configuration Reference — every environment variable, and the minimum secure production config.
- Session Policy & Logout — the logout contract and session lifetime.
- Generated Deployment Artifacts — how real deployments render.