> ## Documentation Index
> Fetch the complete documentation index at: https://help.the-meridian.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

A deployment builds your app from one commit and rolls it out to an [environment](/environments) on Cloud Run. Meridian orchestrates the whole pipeline; you choose what to deploy and watch it progress.

## The pipeline

| Status | Step |
| - | - |
| **Queued** | Accepted, waiting for a build slot |
| **Building** | Cloud Build turns the commit into a container image |
| **Deploying** | The environment's [release command](#release-command) runs once in the new image, then the image is released to the environment as a new revision |
| **Verifying** | Meridian calls the environment's health check path before sending traffic |
| **Succeeded** · **Failed** · **Cancelled** | Terminal |

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](#what-starts-one) from another environment. **No build: rollback** is shown for a [rollback](#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, so `npx 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.

Once it is set, remove the migration from your container start command. Keeping it in both places is harmless but wastes the cold start you just saved.

Meridian deploys **by image digest**, never by tag, so a release is exactly the artifact that was built and verified. **One deployment can be active per environment**, which is what stops two rollouts fighting over the same service.

## What starts one

| Trigger | When |
| - | - |
| **GitHub** | A push to the auto-deploy branch, to the environment you chose |
| **Manual** | You pick a branch and a commit and deploy it to the selected environment |
| **Promote** | You ship a verified image digest from one environment to another, with no rebuild |
| **Rollback** | You send traffic back to an earlier release |

**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](/environments#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](/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 in `X-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](/custom-domain) 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`).

## Health checks and traffic

A new revision receives traffic **only after its health check passes**. If it fails, the previous healthy revision keeps serving. There's no automatic rollback, so a bad release doesn't take the site down, it just doesn't take over.

## Rollback

A rollback shifts all live traffic back to a previously **succeeded** deployment, reusing its existing revision, with no rebuild, so it's as fast as a traffic switch. It's refused if the target never succeeded or the environment has another deployment in flight.

Rolling back development or staging needs **Hosting: Edit**. Deploying to production and rolling production back both need the **hosting.promote** [permission](/customer-permissions), the same one that gates **Bring to production**.

Since there's no restart button, redeploy and rollback are also the two ways to replace running instances. See [Restart](/restart).

## Afterwards

Every deployment keeps its git ref and sha, its image tag and digest, its Cloud Run revision, its event timeline and links to its logs. See [Build history](/artifacts) for the record and [Logs](/logs) for build and runtime output.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.