The pipeline
The tracker shows the same steps live (starting build, building image, running release command, deploying runtime, verifying health), so a stuck deploy is stuck at a named step rather than “in progress”.
When a deployment builds nothing, the tracker shows Building as skipped and says why. Reused build means an existing image was deployed: a redeploy of a commit Meridian already built (its image still exists, so it goes straight to deploying) or a promote from another environment. No build: rollback is shown for a rollback.
Release command
A release command is a shell command that runs once per deploy, inside the image that was just built, before the new revision exists. It runs with the same environment variables, secrets, database access and network as the app itself, sonpx prisma migrate deploy works there exactly as it would at startup.
Set it in the environment’s Runtime settings. The usual value is your migration command.
Why move migrations there: the Shopify app template runs prisma generate and prisma migrate deploy in its docker-start script, which means every new instance pays for both before it can answer a request. That is most of a scaled-to-zero cold start. With migrations in the release command and Prisma generated at build time, the start command is just the server, and a cold start drops from over ten seconds to a few.
Rules:
- The command runs through
sh -c, so&&chains work. - It has 15 minutes. A command that has not finished by then fails the deploy.
- A non-zero exit fails the deploy at the “running release command” step. No revision is created and the live revision keeps serving.
- The command’s output appears in the deploy log, in place, between “Running release command” and the line that closes the step, whether it succeeded or failed. A failure also links to the full Cloud Run job logs.
- It runs once per deployment. If Meridian retries a later step, a release command that already completed is not run again.
- Promotions run it too, since the promoted image is deployed into an environment that has its own database.
What starts one
Automatic deployments are a branch plus a target: every push to that branch builds to Development or Production (Staging too on Enterprise). If the target is Development and the app has no development environment, pushes build to Production. Set the target to none and nothing deploys without you asking.
A manual deploy picks the environment (production, development, or staging on Enterprise), then the branch and commit. The latest is marked, or you can name any sha. A sha that isn’t pushed to the connected repository fails during the build rather than silently doing nothing.
Promote takes a succeeded deployment’s image digest and deploys that same digest into the next environment, with the Bring to staging and Bring to production buttons. There is no build step. Secrets, variables and add-ons stay per-environment. Promoting into production requires the hosting.promote permission. See Bringing a build forward.
Pull requests get their own preview deployments, torn down when the PR closes. An app keeps at most two live previews at a time. See Github.
React Router apps: form actions through the edge router
Meridian serves your app through an edge router. The router talks to your Cloud Run service by its internal hostname and passes your public one inX-Forwarded-Host. From React Router 7.18 the framework compares the origin of every form or fetcher POST with the host it believes it serves, and @react-router/serve does not read that header. Left alone, that check would answer 400 to every form your own pages submit.
The router closes that gap for you. When a request’s Origin is the public hostname it arrived on, the router forwards it as the origin your app expects, so the framework’s check passes exactly as it would without a proxy. A request from any other origin keeps its Origin and your app rejects it as before. This applies to every hostname your app answers on, custom domains included, with no configuration, no rebuild and no redeploy.
You do not need allowedActionOrigins in react-router.config.ts for Meridian hostnames. If you added one, it can stay. Keep it to exact hostnames: a wildcard, or turning the check off, hands any origin the right to post to your actions, which is the attack the check exists to stop.
If your app reads the request host itself, read X-Forwarded-Host rather than Host. A framework whose check also compares the scheme, such as SvelteKit on adapter-node, additionally needs to trust X-Forwarded-Proto (for SvelteKit, set PROTOCOL_HEADER=x-forwarded-proto).