Infrastructure

View as Markdown

mz-deploy apply converges your infrastructure objects declaratively — it creates what’s missing and alters what has drifted. This page covers all object types managed by apply.

Overview

mz-deploy apply applies all types in dependency order: clusters → roles → network policies → secrets → connections → sources → tables. Each step is idempotent — running apply multiple times converges to the same state.

Preview changes before applying:

mz-deploy apply --dry-run

Skip secrets (useful in CI where secret values aren’t available):

mz-deploy apply --skip-secrets

You can also target individual object types with subcommands for granular control:

mz-deploy apply clusters
mz-deploy apply secrets

Clusters

-- clusters/orders.sql
CREATE CLUSTER orders (SIZE = '25cc');

apply creates missing clusters and alters drifted configuration. Grants and comments are applied idempotently.

Roles

-- roles/order_reader.sql
CREATE ROLE order_reader;

Secrets

Secret values use client-side provider functions that are resolved at apply time. This means compile works without access to actual secret values.

Use env_var() to read values from environment variables:

-- models/materialize/public/pg_user.sql
CREATE SECRET pg_user AS env_var('PG_USER');
-- models/materialize/public/pg_password.sql
CREATE SECRET pg_password AS env_var('PG_PASSWORD');

Alternatively, use aws_secret() to pull values from AWS Secrets Manager:

-- models/materialize/public/pg_password.sql
CREATE SECRET pg_password AS aws_secret('prod/pg-password');

aws_secret() requires an aws_profile in your project.toml:

[profiles.default.security]
aws_profile = "my-aws-profile"

apply secrets is idempotent — it runs CREATE SECRET IF NOT EXISTS then ALTER SECRET to update the value.

Connections

Postgres connection using secrets:

-- models/materialize/public/pg_conn.sql
CREATE CONNECTION pg_conn TO POSTGRES (
    HOST 'my-postgres.example.com',
    DATABASE 'app',
    USER SECRET pg_user,
    PASSWORD SECRET pg_password,
    SSL MODE 'require'
);

Sources

Postgres source:

-- models/materialize/public/pg_source.sql
CREATE SOURCE pg_source
IN CLUSTER orders
FROM POSTGRES CONNECTION pg_conn
(PUBLICATION 'mz_source');

Tables

-- models/materialize/public/orders.sql
CREATE TABLE orders FROM SOURCE pg_source
(REFERENCE public.orders);

Before creating anything, apply tables checks each REFERENCE against what its source can currently read from the upstream system. A reference the source cannot read fails the command, and the error suggests any reference the source does expose that is spelled closely enough to be the one you meant. Tables reading from a source the same command is creating are not checked ahead of time, because the source does not exist yet; those references are resolved when the table is created.

The check runs in a dry run as well as a real apply, and it is not read-only. It refreshes each source’s references first, which contacts the upstream system and updates what the source records. Refreshing requires owning the source, and one you do not own is checked against the references already recorded for it.

After apply tables, the table’s column schema is automatically captured in types.lock. This is how compile knows what columns orders has when type-checking views that reference it. See Local development — Lock types for details.

Back to top ↑