Deployments

View as Markdown

mz-deploy gives you a complete lifecycle from local development through production deployment:

compile ──▶ test ──▶ dev ──▶ stage ──▶ wait ──▶ promote
  local      local   real env  real env

compile and test run locally to catch errors fast. dev builds a per-developer overlay against real production data so you can validate behavior before staging. When ready, a deployer runs stage, wait, and promote to ship to production with zero downtime.

Set up deployment tracking

mz-deploy setup

This creates the _mz_deploy database, its tracking tables, three roles for access control, and the deployment server cluster that every mz-deploy connection runs against. The command is idempotent — you can safely run it again without side effects.

When RBAC is enabled, setup must be run by a superuser. It grants CREATEDB and CREATECLUSTER on the system to the deploy roles, and only a superuser can grant system privileges while RBAC is enforced. On clusters with RBAC disabled, the check is skipped. Once setup completes, the deployer, developer, and monitor roles use mz-deploy without any further superuser involvement.

Roles

setup creates three roles that control who can do what:

Role Commands
materialize_deployer delete, stage, promote, abort — full write access
materialize_developer dev, list, describe, log
materialize_monitor list, describe, log — read-only deployment state

Membership is enforced when the command connects: stage, promote, abort, and delete require materialize_deployer; dev requires materialize_developer; and list, describe, and log accept any of the three roles. apply provisions infrastructure (clusters, secrets, connections, sources, tables) and is not currently restricted by role.

Your database user must be a member of one of these roles to run commands that connect to the database. Grant the appropriate role to each user:

GRANT materialize_deployer TO deploy_bot;
GRANT materialize_developer TO dev_user;

compile and test do not require an mz-deploy role because they run locally.

Deploy to staging

mz-deploy stage

stage compiles the project, diffs against the last promoted snapshot, and deploys only changed objects to staging schemas with suffixed names (for example, public_a1b2c3d).

The deploy ID defaults to the current git SHA prefix. To override it:

mz-deploy stage --deploy-id my-feature

Preview what would be staged without making changes:

mz-deploy stage --dry-run

Allow staging with uncommitted changes:

mz-deploy stage --allow-dirty
NOTE:

Common errors during staging:

  • Deploy ID already exists — abort the existing deployment with mz-deploy abort <id> or choose a different --deploy-id.
  • Uncommitted changes — commit your changes or pass --allow-dirty.

Iterate against production data

dev deploys a personal, throwaway copy of your changes to your remote Materialize so you can validate them against real production data. It creates a per-developer overlay database (<db>__<profile>) containing only the views and materialized views you’ve changed, with references rewritten so unchanged dependencies resolve to production. External dependencies pass through unchanged.

You pass the cluster every overlay materialized view and index runs on; the IN CLUSTER clause in your source is rewritten to it. dev refuses to target a cluster that hosts a promoted deployment, so provision a dedicated dev cluster and reuse it:

mz-deploy dev <cluster>

Every run drops the overlay and rebuilds it from scratch, so there’s no state to manage.

Show the plan without executing any DDL:

mz-deploy dev <cluster> --dry-run

Tear down the overlay when you’re done:

mz-deploy dev --down

dev requires the materialize_developer role. setup grants the CREATEDB system privilege to that role, so members inherit it automatically. Tables, sources, sinks, connections, and secrets are silently skipped — dev only overlays views and materialized views.

stage dev
Required role materialize_deployer materialize_developer
Target Staging schemas alongside production Per-developer overlay database
Git dirty check Yes No
Object types All project objects Views and materialized views only
Can be promoted Yes No

Wait for hydration

mz-deploy wait <deploy-id>

This monitors cluster hydration and displays a live dashboard. The possible statuses are:

Status Meaning
ready Fully hydrated and caught up
hydrating Objects still being materialized
lagging Hydrated but lag exceeds the --allowed-lag threshold
failing No healthy replicas (possible OOM)

Flags:

  • --timeout <seconds> — maximum time to wait before exiting with an error.
  • --allowed-lag <seconds> — lag threshold for the lagging status (default: 300).
NOTE:

Common errors during hydration:

  • Timeout — increase --timeout or check cluster health.
  • “failing” status — check cluster sizing; replicas may need more resources.

Promote to production

mz-deploy promote <deploy-id>

This atomically swaps staging schemas into production. promote automatically runs a readiness check before proceeding.

Preview what would change:

mz-deploy promote <deploy-id> --dry-run

Flags:

  • --force — skip conflict detection.
  • --no-ready-check — skip the automatic readiness check.
  • --allowed-lag <SECONDS> — maximum wallclock lag a cluster may have and still pass the readiness check. Clusters lagging further block promotion. Defaults to 300 (5 minutes).
  • --dry-run — preview the promotion without applying changes.
NOTE: If production changed since you staged, promote detects the conflict and aborts. Re-run mz-deploy stage to pick up the latest production state before promoting.

Manage deployments

List active staging deployments (similar to git branch):

mz-deploy list

View promotion history (similar to git log):

mz-deploy log

Clean up a staging deployment:

mz-deploy abort <deploy-id>

View deployment details:

mz-deploy describe <deploy-id>

Day-two operations

Making changes

mz-deploy uses a diff-based model. When you change a SQL file and re-stage, only the modified objects and their dependents are redeployed.

For example, to change stalled_orders from a 30-minute threshold to 1 hour, update the SQL file:

-- models/materialize/public/stalled_orders.sql
CREATE MATERIALIZED VIEW stalled_orders
IN CLUSTER orders AS
SELECT
    id,
    customer,
    amount,
    created_at,
    updated_at,
    mz_now() - updated_at AS stalled_for
FROM orders
WHERE status = 'pending'
  AND updated_at < mz_now() - INTERVAL '1 hour';

When you re-stage, only stalled_orders and its dependents are redeployed.

Deleting objects

Use mz-deploy delete to drop objects. The command drops without CASCADE and requires confirmation:

mz-deploy delete cluster orders

Pass --yes to skip the confirmation prompt.

Supported types: cluster, connection, network-policy, role, secret, source, table.

Stable API schemas

If other teams depend on your materialized views, you can mark schemas as stable API boundaries so that deployments never break downstream consumers. See Stable APIs for details.

Back to top ↑