Vendune
docs/deployment.mdView on GitHub ↗

Vercel frontend + self-hosted Rust / PostgreSQL

Current status: the operator console and deployment package are implemented and tested locally. The managed Rust Studio and /health are reachable at https://app.vendune.ai/ (checked 2026-10-06). The root vendune.ai currently fails TLS hostname negotiation. Public Vercel commerce deployment and production checkout remain unverified; see managed hosting for the active topology. GitHub Pages hosts documentation, not the Rust commerce service.

Prepare the private host configuration

Use a Linux host with Docker Compose, a public DNS name pointing to it and reachable ports 80/443. PostgreSQL/Qdrant stay on the private Compose network. The repository does not purchase a server or reuse unrelated services.

From the repository root:

python3 scripts/prepare_host.py --domain commerce-api.YOUR-DOMAIN --admin-email YOUR-OPERATOR-EMAIL

This creates ignored deploy/.env with mode 0600, independent generated database, instance and operator credentials. It refuses to overwrite an existing file and never prints secret values. Configure the private inference endpoint and optional provider/app endpoints there. Transfer this private file securely to the selected host; do not commit it or paste resolved Compose configuration into logs.

Public Compose enforces SEED_DEMO=false, ALLOW_PUBLIC_SIGNUP=false and ALLOW_BOOTSTRAP_AUTH=false. It creates no known demo customer accounts. Existing demo data is not deleted during an upgrade: never reuse an old publicly seeded demo database for a new public deployment without reviewing its accounts.

Start the backend and your operator account

docker compose --env-file deploy/.env -f deploy/compose.yaml up -d --build postgres qdrant commerce gateway
docker compose --env-file deploy/.env -f deploy/compose.yaml --profile operator-setup run --rm operator

The image builds the real Rust binary and frontend, including the shared analytics SDK. Rust runs as UID 10001. The one-shot operator service receives the initial operator password; the long-running commerce service does not. After successful setup, remove PLATFORM_ADMIN_PASSWORD from the private file (retain your password in your own password manager). Sign in at https://YOUR-BACKEND-DOMAIN/#platform. No instance token should be entered in the browser. Use the console to create empty or example-catalogue shops, then invite individual merchant team members.

Caddy terminates HTTPS and proxies to the internal Rust service. Caddy automatic HTTPS requires a reachable configured domain. Persistent volumes retain PostgreSQL, uploaded binary product assets (stored in PostgreSQL), connector state and certificates. Back up PostgreSQL before upgrades; automatic backup/restore/failover is not yet implemented.

Setup records immutable checksummed migrations. BOOTSTRAP_MODE=migrate performs setup without workers; serve requires all migrations to be ready. On upgrades, run the controlled migration-only job and then the post-migration runtime-role provisioning job before restarting serve replicas; do not roll mixed schema versions blindly. The initial auto mode supports one admitted experimental deployment, not rolling-upgrade orchestration.

Ollama is an operator-controlled private service. Compose does not download a model or reserve a GPU. Optional OpenAI/Anthropic credentials stay in Rust. The connected-app service is optional; configure private Google/Slack clients, encryption/gateway keys and the actual callback URL before starting the connected-apps profile. Storyfront remains independently deployed; configure its admitted tenant mappings and APP_SERVICES as in Storyfront setup. Creating a commerce shop does not automatically provision those external services or enable real payment credentials.

Deploy the frontend on Vercel

Only after the backend is reachable over valid HTTPS:

python3 scripts/hosting_check.py --origin https://YOUR-BACKEND-DOMAIN
python3 scripts/prepare_vercel.py --backend https://YOUR-BACKEND-DOMAIN

Create the dedicated vendune Vercel project from this GitHub repository. Select frontend as root, Vite, npm run build, and dist. Enable including source files outside the root directory: the frontend imports the shared extensions/sdk/analytics module. This is also covered by the corrected Docker build. Keep the generated frontend/vercel.json in the deployment checkout or configure the same rewrites in the deployment's committed environment-specific configuration; it is ignored by this generic source repository and must be supplied before a Git-based build. Do not import a project with placeholder rewrites and call it a working deployment.

Set COMMERCE_PUBLIC_ORIGIN on Rust to the HTTPS origin of the Studio browser. MCP validates this configured origin; a proxy Host header cannot grant browser access. For a separate Vercel Studio, use its real public origin rather than the internal Rust host.

Generated configuration proxies /api, /store-api, /mcp, /ucp, /.well-known, /media and /health to the backend and serves the SPA. It contains no credentials. Vercel supports external rewrites and Vite deployments. Private API responses are marked non-cacheable. Authenticated/personalized results must never be shared across tenants by CDN rules. Run the HTTPS check against the Vercel preview too, then verify operator sign-in, creation of an isolated shop, customer registration/address checkout and own order access before promoting it.

Use ?shop=SHOP_ID and optionally &channel=CHANNEL_ID for storefronts. /#platform opens the operator console; ?shop=SHOP_ID#merchant opens a shop's merchant studio. Staging previews use the existing private merchant session. SHOP_DOMAIN_SUFFIX supports operator-configured shop subdomains with trusted host-to-tenant binding; see managed hosting. Automatic DNS/certificate provisioning for arbitrary merchant domains remains unimplemented.

Local release checks

docker build -f deploy/Dockerfile -t vendune:platform .
# With the existing local Compose PostgreSQL container and private DATABASE_URL:
python3 scripts/hosting_container.py

This creates and removes only a uniquely named synthetic test database/container, checks the built frontend, non-root Rust process, personal operator login, actual shop creation and closed signup/bootstrap gates. It is distinct from a public HTTPS/Vercel deployment. platform.py and platform_setup.py are mandatory HTTP/PostgreSQL CI tests.

Additional public deployments need their own host/domain and operator identity. Broader production SaaS requires recovery/email verification, full provider/token/spend quotas, signed app trust, billing, large-catalog staged branches, backups/restores and measured failover. The operator guide describes exactly what the dashboard measures.

The Northflank topology, cost model and migration boundaries are in managed-hosting.md.

Central provider secrets and shop domains

With wildcard DNS/TLS and SHOP_DOMAIN_SUFFIX=vendune.ai, new shops use SHOP.vendune.ai; admin.vendune.ai is the public service directory. Set COMMERCE_PUBLIC_ORIGIN=https://app.vendune.ai for the shared merchant Studio. Provision PLATFORM_SECRET_KEY as a persistent runtime-only 64-character random hex key before saving provider API keys in the operator console. All replicas and AI workers need the same key; back it up separately. Do not rotate it without re-encrypting existing credentials. INFERENCE_ALLOW_LOOPBACK stays false in public deployments. Control-plane behavior and boundaries.

Optional private experience service

The generic trusted broker, one-use Studio handoff and hosted frontend mount are opt-in. Configure the exact private service issuer/origin and matching runtime-only shared keys; see experience integration. The private service and Storyfront never belong in this public build or repository.

Storefront product addresses

Public storefronts use https://SHOP.vendune.ai/. The shared Studio deliberately remains at https://app.vendune.ai/?shop=SHOP#merchant so merchant login stays on its original domain. Legacy product bookmarks discard stale studio parameters and move to the shop domain. Product links use /products/SKU/LOCALIZED-SLUG (or /products/SKU without a slug), with the selected content language and sales channel retained. SKU identity prevents two same-name products from colliding; translated slugs inherit through the existing language chain. The server admits direct HTML requests through the same tenant, active-product and sales-channel checks as the Store API. Browser navigation and Back/Forward retain cart state, and reloads serve the application at that URL. Older slug addresses still find the SKU and canonicalize to the currently saved slug. Metadata and canonical tags are rendered by the client; server-side product HTML, sitemap generation and slug-only redirect history are not implemented by this change.

Strict runtime and real transaction pooling

The production Compose template separates migration owner, commerce runtime and connector credentials. Migrations finish before role provisioning; commerce starts in serve with strict RLS, no bootstrap auth and no demo fallback. Run provisioning after additive upgrades. Existing hosts must migrate and change their own runtime credentials explicitly; changing this template alone does not reconfigure them.

DB_CLUSTER_CONNECTION_BUDGET is shared by all HTTP/worker processes. Each reserves DB_POOL_MAX + 2; set DB_MAX_PROCESSES and the same budget on every replica. Transaction pooling requires PgBouncer 1.21+, protocol prepared-statement support and a direct/session DATABASE_LISTENER_URL under the non-owner login. Set DB_POOLER_MODE=transaction; statement pooling is unsupported. Complete settings, upgrade sequence, worker retention and tested boundaries.

Production image compile-time dependencies

Run python3 scripts/testing/image_context.py before building deploy/Dockerfile. This CI gate reads literal Rust include_str!/include_bytes! dependencies and checks them against the Rust stage COPY set. Its negative control removes the historical app-manifest COPY and must find the missing inputs. These eight files are used by app approval/upgrade compatibility; tests against a full checkout alone cannot detect their absence from a container build. The final image still contains no Python interpreter. A green dependency check does not replace an actual image build or the managed backup/migration/runtime-role rollout.