Deployment¶
Development (Docker Compose)¶
The docker-compose.yml in the project root starts the full stack in development mode:
All services use development configurations: hot-reload, debug logging, and development credentials.
Production Builds¶
Production images live alongside their dev counterparts in each app directory. Use docker-compose.prod.yml to build and run the full stack with production settings.
Backend¶
backend/Dockerfile.prod is a multi-stage Python 3.12-slim build. Highlights:
- Stage 1 (builder) — installs build tooling, resolves
pyproject.tomldeps, and optionally downloads the MaxMind GeoLite2 database at build time (viaMAXMIND_ACCOUNT_ID/MAXMIND_LICENSE_KEYbuild args; GeoLite2 can also be fetched at runtime from admin → Settings → Geocoding). - Stage 2 (runtime) — slim image that copies in only
site-packages, app code, and the GeoLite2 DB. - Entrypoint — Uvicorn with
--workerssized for the host.
Warning
Do not use --reload in production. Use --workers to match your CPU count.
Storefront / Admin¶
Both SvelteKit apps ship their own Dockerfile and build with @sveltejs/adapter-node into a build/ directory served by node build. Dev images mount source for hot reload; production builds bake the compiled output in.
Run npx houdini generate && npx svelte-check as part of the build (already wired into npm run check) so type-checking failures block deploys.
Environment Variables¶
Required variables for production:
| Variable | Description |
|---|---|
DATABASE_URL | PostgreSQL connection string (use asyncpg driver) |
REDIS_URL | Redis connection string |
SECRET_KEY | JWT signing secret (generate with python -c "import secrets; print(secrets.token_urlsafe(64))") |
REDPANDA_BROKER | Redpanda/Kafka broker address |
TEMPORAL_HOST | Temporal server address |
MEILISEARCH_URL | Meilisearch endpoint (admin search) |
MEILISEARCH_API_KEY | Meilisearch admin API key |
TYPESENSE_URL | Typesense endpoint (storefront search) |
TYPESENSE_API_KEY | Typesense admin API key |
TYPESENSE_SEARCH_ONLY_KEY | Typesense search-only key (for scoped key generation) |
FILE_STORAGE_DIR | Local filesystem directory for uploads (default: uploads). Used by the built-in local storage strategy. |
PUBLIC_API_BROWSER_ORIGIN | Optional on API: browser-reachable origin (e.g. https://api.example.com). When set, the local file strategy returns absolute /uploads URLs. Set the same value on the admin service so the upload BFF can rewrite relative URLs if the API omits this setting. |
S3_ENDPOINT | S3-compatible storage endpoint (used by ext_minio, ext_s3, ext_digitalocean_spaces) |
S3_PUBLIC_URL | Public URL for S3 objects (browser-accessible). Required when S3_ENDPOINT uses a Docker-internal hostname. Falls back to S3_ENDPOINT if unset. |
S3_ACCESS_KEY | S3 access key (required when using a storage extension) |
S3_SECRET_KEY | S3 secret key (required when using a storage extension) |
S3_BUCKET | S3 bucket name |
Tip
Use a secrets manager (AWS Secrets Manager, Vault, etc.) for SECRET_KEY, database credentials, and API keys. Never commit secrets to the repository.
Database Migrations¶
Run migrations before starting the application:
For first-time setup, also run the seed script:
Health Check¶
The API exposes a { health } GraphQL query that returns "Vectis Commerce API is healthy". Use this for load balancer and container health checks:
curl -X POST http://localhost:8000/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ health }"}'
Scaling¶
| Service | Scaling Strategy |
|---|---|
| API | Horizontal — add more Uvicorn worker processes or container replicas |
| Storefront / Admin | Horizontal — stateless Node.js processes behind a load balancer |
| Temporal Worker | Horizontal — add worker replicas; Temporal distributes work automatically |
| Event Consumer | Single instance per consumer group (Redpanda handles partitioning) |
| PostgreSQL | Vertical or read replicas |
| Redis | Single instance or Redis Cluster for high-traffic sessions |
TLS / HTTPS¶
Place a reverse proxy (Nginx, Caddy, or cloud load balancer) in front of all services. Terminate TLS at the proxy. Internal service-to-service communication can use HTTP within a private network.
The API runs Uvicorn with --proxy-headers --forwarded-allow-ips "*" so it honours X-Forwarded-For from the BFF and the reverse proxy (Decided #165). Make sure the reverse proxy sets X-Forwarded-For correctly and nothing accepts the header from the public internet directly — only your trusted hop should be allowed to set it.
DigitalOcean Deployment Guide¶
A full DigitalOcean walkthrough lives at vectis/docs/DEPLOYMENT_DIGITALOCEAN.md in the application repo. It covers four customer-choice paths:
- Single droplet — single VM running the full Docker Compose stack. Good for pilots and demos.
- App Platform + Managed Databases — DO App Platform for stateless services, DO Managed Postgres + Managed Redis. Less ops overhead, slightly higher cost.
- Kubernetes (DOKS) — for customers already running on K8s. Helm charts not yet provided; the runbook covers manifest generation.
- Self-hosted on a VPS provider — generic Compose deployment with a Caddy reverse proxy and Let's Encrypt.
Each path covers networking, secrets, backups, observability, and DNS. The runbook is the canonical onboarding doc — keep it in sync with this page when paths or env vars change.
Crypto Secrets (Decided #155)¶
Two environment variables control payload encryption (webhook secrets, gateway credentials, anything stored Fernet-encrypted in settings):
| Variable | Purpose |
|---|---|
SECRETS_MASTER_KEY | Current master key — used for new writes and decryption reads |
SECRETS_MASTER_KEY_ROTATING_FROM | Optional, comma-separated list of older keys tried during decryption — lets you rotate without downtime |
To rotate: generate a new key with python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())", move the current key to SECRETS_MASTER_KEY_ROTATING_FROM, then set the new key as SECRETS_MASTER_KEY and restart. Any encrypted row written with the previous key continues to read; new writes use the new key. After 14 days (or your chosen retention) remove the old key from the rotating list.
Customer Pilot Playbook¶
For white-glove customer onboarding, see vectis/docs/CUSTOMER_ONBOARDING_PILOT.md — store-credit seeding, demo accounts, OAuth providers, Houdini schema regeneration. The admin route /customers/pilot-playbook renders the same runbook for in-app reference.