Vendure is a headless commerce framework built on Node.js, TypeScript, and NestJS, with a GraphQL API. It sits in a sweet spot: more code-first and lightweight than Saleor, more structured than rolling your own. Like most serious commerce platforms, a production Vendure deployment is two processes — a server and a worker — backed by a database. Here's how to ship it.
Vendure's process model
Vendure splits responsibilities cleanly:
- Server process — handles the Shop and Admin GraphQL APIs and HTTP traffic.
- Worker process — runs the job queue (order processing, email, search indexing, etc.).
Both run from the same codebase. By default Vendure can run the worker in the same process, but for production you should run them separately so you can scale and restart them independently.
Step 1: Configure the database
Vendure uses TypeORM and supports PostgreSQL, MySQL/MariaDB, and SQLite. Use PostgreSQL in production. In vendure-config.ts:
import { VendureConfig } from '@vendure/core';
export const config: VendureConfig = {
apiOptions: {
port: +(process.env.PORT || 3000),
adminApiPath: 'admin-api',
shopApiPath: 'shop-api',
},
dbConnectionOptions: {
type: 'postgres',
url: process.env.DATABASE_URL,
synchronize: false, // never true in production
migrations: ['./migrations/*.+(js|ts)'],
},
// ...
};Set synchronize: false and rely on migrations. Auto-sync in production will eventually corrupt your schema.
Step 2: Generate and run migrations
Vendure has a built-in migration generator. During development:
npx vendure migrateThis creates timestamped migration files. Commit them. On deploy, run migrations before the server starts. A clean pattern is a package.json script:
{
"scripts": {
"start:server": "node ./dist/index.js",
"start:worker": "node ./dist/index-worker.js",
"migrate": "node ./dist/migrate.js"
}
}Vendure's project scaffold generates index.ts, index-worker.ts, and a migration entrypoint for exactly this purpose.
Step 3: Deploy the server
On PandaStack, Vendure deploys cleanly as a Node container app — no Dockerfile required, since buildpacks auto-detect Node.
- 1Provision a managed PostgreSQL database (PostgreSQL 16.x works well).
- 2Create a container app linked to your repo. Link the database;
DATABASE_URLis injected. - 3Set the build command to
npm run buildand the start command tonpm run start:server. - 4Add a release/pre-deploy step that runs
npm run migrateso schema changes apply once per deploy.
Live build logs let you watch the TypeScript compile and the server boot.
Step 4: Deploy the worker
Create a second container app from the same repo with start command npm run start:worker. It needs the same DATABASE_URL and any shared secrets, but no public port — it's a background process pulling from the job queue.
| Process | Start command | Public? | Scales on |
|---|---|---|---|
| Server | start:server | Yes | HTTP/GraphQL load |
| Worker | start:worker | No | Job queue depth |
Step 5: Persistent asset storage
Vendure's AssetServerPlugin stores product images. The default local storage strategy writes to disk — fine for dev, lost on container restart. For production, use an S3-compatible backend:
import { AssetServerPlugin, configureS3AssetStorage } from '@vendure/asset-server-plugin';
AssetServerPlugin.init({
route: 'assets',
assetUploadDir: '/tmp/assets',
storageStrategyFactory: configureS3AssetStorage({
bucket: process.env.S3_BUCKET!,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
},
nativeS3Configuration: {
endpoint: process.env.S3_ENDPOINT,
forcePathStyle: true,
region: process.env.S3_REGION,
},
}),
});A self-hosted MinIO instance works as the S3 backend if you want everything in one place.
Step 6: Search
Vendure's DefaultSearchPlugin uses the database and is fine for modest catalogs. For larger stores, swap in the Elasticsearch plugin. If you go that route, point it at a dedicated search cluster rather than overloading your primary database.
Production checklist
synchronize: falseand committed migrations.- Server and worker as separate apps sharing one database.
- S3-compatible asset storage, not local disk.
- Strong
superadmincredentials and a stable cookie/session secret. - Database connection pooling if you run multiple server replicas.
Honest caveats
Vendure is younger than Magento or Shopify's ecosystem, so you'll find fewer off-the-shelf plugins. But the plugin API is excellent and the codebase is approachable TypeScript. For teams comfortable writing code, that trade is usually worth it.
Wrapping up
Vendure rewards a clean two-process deployment: a server for the API and a worker for jobs, both pointed at managed PostgreSQL with S3 asset storage. Get migrations and asset storage right and the rest is standard Node operations.
PandaStack auto-detects Node, injects DATABASE_URL from a linked database, and lets you run server and worker as independent apps — the free tier covers a dev store. Start at https://dashboard.pandastack.io.
References
- Vendure documentation: https://docs.vendure.io/
- Vendure deployment guide: https://docs.vendure.io/guides/deployment/deploying-to-production/
- Vendure migrations: https://docs.vendure.io/guides/developer-guide/migrations/
- AssetServerPlugin S3 storage: https://docs.vendure.io/reference/core-plugins/asset-server-plugin/s3asset-storage-strategy/
- TypeORM docs: https://typeorm.io/