Deploy to production
Use this guide for an app that already works on a development store. You need a production PostgreSQL database, an HTTPS app origin, a process supervisor or hosting platform, and the credentials for the Shopify app being deployed.
A deployment has three stages: build, apply migrations once, then start the web and worker services. Run app commands from the project root.
1. Prepare the release
Section titled “1. Prepare the release”With development dependencies installed in development or CI:
pnpm install --frozen-lockfilepnpm run lintpnpm exec openshop migrate generatepnpm exec openshop migrate checkRun your app’s tests if you have set up a test suite.
Review and commit any generated files in drizzle/. Test the migrations against
a separate database before applying them to production; see
Manage migrations.
2. Configure the environment
Section titled “2. Configure the environment”Inject these into the production services using your platform’s secret settings:
NODE_ENV=productionDATABASE_URL=postgresql://user:password@database:5432/openshopSHOPIFY_API_KEY=your-client-idSHOPIFY_API_SECRET=your-client-secretHOST=https://your-app.example.comENCRYPTION_KEY=replace-with-64-hex-charactersGenerate ENCRYPTION_KEY once with openssl rand -hex 32, store it securely, and
reuse it across deployments. Both services need the same key, database, and
Shopify configuration. Production commands do not load .env automatically.
For multiple apps, use Configure Shopify apps.
3. Build the artifact
Section titled “3. Build the artifact”Provide the app’s build environment, including SHOPIFY_API_KEY for single-app
App Bridge configuration, then run:
pnpm run buildThe output contains dist/ui/ and dist/openshop/server/. Deploy them together
with the production dependencies, drizzle/, package metadata, and any referenced
Shopify TOML files. The generated Dockerfile and ecosystem.config.cjs provide
a starting point for a container running both services.
4. Apply migrations
Section titled “4. Apply migrations”Run a single release job with the target production DATABASE_URL:
pnpm exec openshop migratepnpm exec openshop migrate statusConfirm pending: 0 before starting the new processes. This command applies
committed SQL only. Neither the web process nor the worker migrates on startup.
5. Start both services
Section titled “5. Start both services”Configure two independently supervised services from the same build:
| Service | Command | Initial process count |
|---|---|---|
| Web | pnpm exec openshop start |
1 |
| Worker | pnpm exec openshop worker --concurrency=5 |
1 |
Both commands keep running. Put them in separate service definitions, containers, or process-manager entries; two sequential lines in a shell script would never start the worker. The web process serves HTTP and dispatches scheduled runs. The worker executes the queued work.
Keep one web replica when using crons: every web replica starts a scheduler and there is no leader election. Scale workers after measuring database and provider capacity. See Operate an app for that procedure.
For PM2, the template uses node_modules/openshop/bin/cli.js as the script.
Keep that path: node_modules/.bin/openshop can be a shell shim, which PM2 may
try to execute as JavaScript.
6. Release the Shopify configuration
Section titled “6. Release the Shopify configuration”Set the production application and callback URLs in the linked Shopify app TOML, then release its configuration:
shopify app deploy --config shopify.app.tomlRepeat for every configured app. This updates Shopify-side configuration; it does not deploy the OpenShop server. Complete the app’s installation or renewed scope authorization as needed.
7. Verify the whole path
Section titled “7. Verify the whole path”- Request
https://your-app.example.com/healthand expect HTTP 200. - Open the installed app inside Shopify admin.
- Check a configured provider using its production credentials.
- Run a harmless flow and verify it reaches completed with the expected steps.
- Confirm both service logs show no database, encryption, or configuration errors.
/health only proves the HTTP process responds. A completed smoke flow also
exercises the queue and worker. If runs stay pending, use
Troubleshooting.
Roll back a failed release
Section titled “Roll back a failed release”Restore the previous app build only if it remains compatible with the current database schema. Applied SQL is not undone by rolling back a process. For an incompatible schema change, use a reviewed forward fix or your database restore procedure. Keep the original encryption key available with database backups.