Snowflake DCM Projects: Infrastructure as Code, Native

Written by

in

Until August 2026, managing Snowflake infrastructure as code meant one of two paths. You either adopted Terraform with the Snowflake provider — an external tool with its own state file, version lag behind new Snowflake features, and a separate CI/CD pipeline to learn and maintain. Or you ran Schemachange or a migration-script pattern — imperative SQL files executed in order, no dry-run, no idempotency, no rollback if something failed halfway through.

Both paths work. Both also mean that your Snowflake infrastructure is being managed by something that lives outside Snowflake and has an imperfect model of what’s actually in your account.

Snowflake DCM Projects — Database Change Management, GA on August 7 2026 — is the native alternative. You write DEFINE statements in SQL files describing your desired state. You run PLAN to see exactly what will change. You run DEPLOY and Snowflake reconciles the diff. No external tool. No state file to lose. No provider lag. And from July 2026, you can have Cortex Code author and debug your DEFINE files with a natural-language prompt.

This is the complete practitioner guide: how DCM Projects works, when to reach for it over Terraform, the full CI/CD pipeline with GitHub Actions, and every production gotcha documented in the official docs.

TL;DR

  • DCM Projects is a native Snowflake object (schema-level) that manages other Snowflake objects declaratively. You write DEFINE statements in SQL files, run PLAN to preview changes, and DEPLOY to apply them. All state is tracked inside Snowflake — no external state file.
  • The workflow is Terraform-like but Snowflake-native: one DCM project per target environment (DEV, STAGING, PROD), all pointing at the same parameterized definition files, deployed independently via Jinja template variables.
  • Jinja2 templating is first-class: dictionaries, loops, conditionals, and macros work in definition files. Use loops to provision the same database + role + warehouse stack for multiple teams in one DEFINE file.
  • PLAN DELTA (preview, July 2026) evaluates only changed definitions and their dependents — dramatically faster feedback during active development on large projects.
  • Inherited grants (preview, July 2026) let you write one GRANT ... INHERITED statement that automatically applies to every current and future object of a specified type within a container. No more grant drift on new tables.
  • Snowflake provides reusable GitHub Actions (dcm/plan, dcm/deploy) and sample workflows for full PR-based CI/CD. OIDC authentication is recommended — no stored secrets.
  • DCM Projects supports up to 10,000 entities per project and a maximum of 10 MB total definition file size. Projects exceeding these limits may timeout during PLAN or DEPLOY.
  • DCM Projects are available on all Snowflake editions at no additional cost beyond the cloud services compute consumed by PLAN and DEPLOY operations.

The Plan-Then-Deploy Lifecycle

The mental model maps directly to Terraform if you’ve used it. You write desired state. You preview the diff. You apply. The difference is that the state Snowflake compares against is the live account — not a separate state file that can diverge from reality.

📷 three zones, one workflow — source files parameterised with jinja, commands run against the live account, environments deployed independently

The key difference from Terraform’s state model: when you run PLAN, Snowflake queries your live account to compare current state against your definitions. If someone manually altered a table outside of DCM, PLAN will show that as a change. The source of truth is always the account, not a file on disk. This eliminates the class of bugs that come from state file drift — but it also means that manual changes outside DCM are always detected and potentially overwritten on the next DEPLOY.

Writing Your First DEFINE File

DEFINE statements are the core primitive. They look like SQL DDL but describe desired state rather than imperative commands. Snowflake figures out whether to CREATE, ALTER, or do nothing based on the diff between your DEFINE and the live object.

-- definitions.sql
-- Describes the desired state of a complete analytics stack for one team

create DATABASE {{ team_name }}_DB
  COMMENT = 'Analytics database for {{ team_name }} team';

create SCHEMA {{ team_name }}_DB.RAW
  COMMENT = 'Raw ingestion layer';

create SCHEMA {{ team_name }}_DB.ANALYTICS
  COMMENT = 'Curated analytics layer — analyst-facing';

create WAREHOUSE {{ team_name }}_WH
  WITH
    WAREHOUSE_SIZE = '{{ wh_size | default("SMALL") }}'
    AUTO_SUSPEND   = 300
    AUTO_RESUME    = TRUE
  COMMENT = 'Compute for {{ team_name }} team';

use ROLE {{ team_name }}_ADMIN;
use ROLE {{ team_name }}_ANALYST;

-- Grants declared alongside objects — reconciled on every DEPLOY
GRANT OWNERSHIP ON DATABASE {{ team_name }}_DB    TO ROLE {{ team_name }}_ADMIN;
GRANT OWNERSHIP ON WAREHOUSE {{ team_name }}_WH   TO ROLE {{ team_name }}_ADMIN;
GRANT USAGE     ON WAREHOUSE {{ team_name }}_WH   TO ROLE {{ team_name }}_ANALYST;
GRANT USAGE     ON DATABASE  {{ team_name }}_DB    TO ROLE {{ team_name }}_ANALYST;
GRANT USAGE     ON SCHEMA    {{ team_name }}_DB.ANALYTICS TO ROLE {{ team_name }}_ANALYST;
GRANT SELECT    ON ALL TABLES IN SCHEMA {{ team_name }}_DB.ANALYTICS
                                          TO ROLE {{ team_name }}_ANALYST;
GRANT ROLE {{ team_name }}_ADMIN    TO ROLE SYSADMIN;

This file is parameterised with {{ team_name }} and {{ wh_size }}. To provision the Finance team with a LARGE warehouse, you pass those variables at plan or deploy time. To provision five teams in one shot, wrap the whole file in a Jinja {% for team_name in teams %} loop and pass a list.

The manifest.yml — environment targets and templating config

# manifest.yml
# Declares target environments and their templating configurations

version: 1

targets:
  DEV:
    account_identifier: myorg-myaccount-dev
    project_name: ANALYTICS.PROJECTS.ANALYTICS_DCM_DEV
    project_owner: DCM_DEVELOPER_ROLE
    templating_config: dev_config

  STAGING:
    account_identifier: myorg-myaccount-staging
    project_name: ANALYTICS.PROJECTS.ANALYTICS_DCM_STAGING
    project_owner: DCM_STAGING_ROLE
    templating_config: staging_config

  PROD:
    account_identifier: myorg-myaccount-prod
    project_name: ANALYTICS.PROJECTS.ANALYTICS_DCM_PROD
    project_owner: DCM_PROD_ROLE   # service account only — not individual devs
    templating_config: prod_config

default_target: DEV

configurations:
  dev_config:
    variables:
      teams: ['Engineering']
      wh_size: 'XSMALL'
      suffix: 'DEV'

  staging_config:
    variables:
      teams: ['Engineering', 'Analytics']
      wh_size: 'SMALL'
      suffix: 'STAGING'

  prod_config:
    variables:
      teams: ['Engineering', 'Analytics', 'Finance', 'HR']
      wh_size: 'MEDIUM'
      suffix: ''

The manifest is the single source of truth for your environment topology. Each CI/CD workflow reads account_identifier, project_name, and project_owner directly from the manifest — environment-specific config lives here, not scattered across GitHub secrets.

Running PLAN and DEPLOY

The full command set is available via SQL, Snowflake CLI, Snowsight UI, or Cortex Code. The CLI is the best choice for local development and CI/CD pipelines:

# Navigate to your project directory
cd ./analytics-dcm-project/

# Run PLAN against the default target (DEV from manifest)
snow dcm plan

# Run PLAN against PROD to see what a production deployment would change
snow dcm plan --target PROD --save-output

# Run PLAN DELTA — only evaluate definitions changed since last deploy
# Use during active development for fast feedback; always run full PLAN before deploying
snow dcm plan --delta

# Deploy to DEV
snow dcm deploy

# Deploy to PROD with a deployment alias (like a commit message for the deployment)
snow dcm deploy --target PROD --alias "Add HR team infrastructure - ticket PLAT-421"

# Preview what PURGE would do (dry run)
snow dcm plan --target DEV  # should show all objects as DROPs if you purge after

# Purge a sandbox DCM project — drops all managed objects
EXECUTE DCM PROJECT MY_SCHEMA.PROJECTS.ANALYTICS_DCM_DEV PURGE AS "sandbox_cleanup";
DROP DCM PROJECT MY_SCHEMA.PROJECTS.ANALYTICS_DCM_DEV;

Always run full PLAN before DEPLOY. PLAN DELTA is fast but only evaluates changed definitions — it won’t catch external changes (a table manually dropped or altered outside DCM since the last deployment). PLAN DELTA is for development feedback; full PLAN is the pre-deploy gate.

DCM Projects vs Terraform vs Schemachange

📷 dcm projects is not a replacement for every terraform use case — but for snowflake-only infrastructure it removes an entire layer of external tooling

DimensionTerraform + Snowflake ProviderSchemachangeSnowflake DCM Projects
State managementExternal state file (S3/GCS/local)Migration history tableInside Snowflake — always live
Dry-run / previewterraform planNone — scripts run directlyPLAN / PLAN DELTA
IdempotencyYesDepends on migration designYes — DEPLOY skips matching objects
Multi-env supportWorkspaces / varsManual per-env scriptsmanifest.yml targets + Jinja vars
New Snowflake feature supportProvider update lag (weeks)Immediate (plain SQL)Day-0 (DEFINE added same GA)
Non-Snowflake infraYes — multi-cloud providersNoSnowflake objects only
AI-assisted authoringNone nativeNoneCortex Code DCM skill
CostTool + provider maintenanceMinimalCloud Services compute only

The honest comparison: if your team already runs Terraform for multi-cloud infrastructure — S3 buckets, IAM roles, Kafka clusters, and Snowflake objects in one plan — stick with Terraform. DCM Projects doesn’t replace it for cross-cloud IaC. It replaces it for Snowflake-only infrastructure, where the external tool overhead was never really worth it.

CI/CD with GitHub Actions

Snowflake publishes reusable GitHub Actions for DCM Projects in the snowflakedb/snowflake-actions repository. Four actions cover the full lifecycle: dcm/parse-manifest, dcm/connection-test, dcm/plan, and dcm/deploy.

📷 every pr triggers a full production plan — reviewers see create/alter/drop before approving, not after

# .github/workflows/dcm_pr_to_main.yml
# Runs PLAN against PROD on every PR — gives reviewers a full diff before merge

name: DCM Plan on PR

on:
  pull_request:
    branches: [main]
    paths:
      - 'dcm/**'

jobs:
  plan:
    runs-on: ubuntu-latest
    environment: PROD
    permissions:
      contents: read
      id-token: write       # Required for OIDC — no stored secrets
      pull-requests: write  # Post plan summary as PR comment
    steps:
      - uses: actions/checkout@v4

      - uses: snowflakedb/snowflake-actions/dcm/plan@v3
        id: plan
        with:
          target: PROD
          project-path: dcm/analytics/
          snowflake-user: ${{ env.SNOWFLAKE_USER }}
          # OIDC: no SNOWFLAKE_PASSWORD secret needed

      - name: Post plan summary to PR
        uses: actions/github-script@v7
        with:
          script: |
            const summary = `${{ steps.plan.outputs.summary }}`;
            github.rest.issues.createComment({
              ...context.repo,
              issue_number: context.payload.pull_request.number,
              body: summary
            });
# .github/workflows/dcm_deploy_prod.yml
# Deploys to STAGING then PROD on merge to main

name: DCM Deploy

on:
  push:
    branches: [main]
    paths: ['dcm/**']

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: STAGING
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: snowflakedb/snowflake-actions/dcm/deploy@v3
        with:
          target: STAGING
          project-path: dcm/analytics/
          snowflake-user: ${{ env.SNOWFLAKE_USER }}
          alias: "Deploy from ${{ github.sha }}"

  deploy-prod:
    needs: deploy-staging     # Production blocked until staging succeeds
    runs-on: ubuntu-latest
    environment: PROD
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: snowflakedb/snowflake-actions/dcm/deploy@v3
        with:
          target: PROD
          project-path: dcm/analytics/
          snowflake-user: ${{ env.SNOWFLAKE_SERVICE_USER }}
          alias: "Promote from staging - ${{ github.sha }}"

The sample workflows in the Snowflake Labs DCM repository include a DROP detection step that parses plan_result.json and blocks deployment if the changeset contains top-level DROPs on databases, schemas, tables, or stages. This is a safety guardrail, not a guarantee — nested destructive changes or ALTER operations that effectively drop data won’t be caught by it.

The Three New Capabilities to Know (July 2026 Preview)

PLAN DELTA — fast feedback during development

On a project with 500+ entities, a full PLAN can take several minutes. PLAN DELTA evaluates only the definition files you changed since the last deployment, plus any downstream definitions that depend on them. For tightening a masking policy or tweaking a dynamic table schedule, PLAN DELTA gives you sub-30-second feedback. Always switch to a full PLAN before deploying.

Inherited grants — close the grant drift problem permanently

The most painful recurring problem in Snowflake governance is grant drift: a new table is created, nobody remembers to grant SELECT to the analyst role, and the dashboard breaks. Inherited grants solve this at the DCM level:

-- grants.sql
-- One INHERITED grant covers all current AND future tables in this schema

GRANT SELECT
  ON ALL TABLES IN SCHEMA ANALYTICS_DB.ANALYTICS
  TO ROLE ANALYTICS_ANALYST
  INHERITED;   -- applies automatically to tables created after this DEPLOY

GRANT SELECT
  ON ALL VIEWS IN SCHEMA ANALYTICS_DB.ANALYTICS
  TO ROLE ANALYTICS_ANALYST
  INHERITED;

Inherited grants require a behavior-change parameter enabled at account level, independent of DCM Projects. This parameter affects all future object grants in your account — not just DCM-managed ones. Read the Managing access with inherited grants section before enabling it in production.

ATTACH TAG — declarative tagging reconciled on every deploy

Tag assignment used to be a post-deployment manual step or a separate Terraform resource. With ATTACH TAG in DCM Projects, tag assignments are part of your definition files and reconciled on every DEPLOY — including column-level tags for PII classification that your masking policies depend on:

-- tags.sql
-- Declaratively assigns PII tags — reconciled on every DEPLOY

ATTACH TAG GOVERNANCE_DB.TAGS.PII_EMAIL
  TO COLUMN ANALYTICS_DB.ANALYTICS.CUSTOMERS.EMAIL;

ATTACH TAG GOVERNANCE_DB.TAGS.PII_PHONE
  TO COLUMN ANALYTICS_DB.ANALYTICS.CUSTOMERS.PHONE_NUMBER;

ATTACH TAG GOVERNANCE_DB.TAGS.GDPR_SUBJECT
  TO TABLE ANALYTICS_DB.ANALYTICS.CUSTOMERS;

The Gotchas

Removing a DEFINE statement expresses intent to DROP — not intent to unmanage.

If you delete a DEFINE TABLE ... line from your definitions file and run DEPLOY, DCM drops the table. This is the correct declarative behaviour — the desired state no longer includes that table. If you want to stop managing an object without dropping it, use ALTER TABLE ... UNSET DCM PROJECT to detach it first, then remove the DEFINE statement. Missing this step is how teams accidentally drop production tables during a refactor.

DEPLOY failure mid-execution can leave objects in a partially-applied state.

Unlike Terraform which can plan a rollback, a DCM DEPLOY that fails partway through has applied some DDL statements and not others. The fix in most cases is to fix the root cause and run DEPLOY again — DCM reconciles from wherever it left off. For a critical failure, the Recover an earlier defined state workflow lets you replay an earlier deployment artifact. This is why PLAN + manual review before DEPLOY is non-negotiable in production.

DCM project owner must hold every role that receives GRANT OWNERSHIP — or PLAN fails.

If your definitions include GRANT OWNERSHIP ON TABLE ... TO ROLE TEAM_ADMIN, the DCM project owner role must hold TEAM_ADMIN (directly or through role hierarchy). DCM checks for potential owner lockout at PLAN time and fails before making any changes if the hierarchy isn’t right. This is a safety feature — but it means your DCM project owner role needs a carefully designed privilege stack before you can express ownership transfers in definitions.

Projects over 1,000 entities can timeout on PLAN or DEPLOY.

The official docs state that PLAN or DEPLOY for large projects can take 10 minutes or more, and exceeding the 10,000-entity or 10 MB limits can cause timeouts. Mitigation: split large projects along natural boundaries (separate business units, separate data domains) rather than running one mega-project. Consolidating definitions into fewer files also speeds up PLAN and DEPLOY. And use PLAN DELTA during active development to avoid full-project evaluations on every edit.

Jinja template variables render in cleartext — never put credentials in them.

The official docs include this as an explicit warning: rendered SQL definitions don’t redact any values inserted by Jinja template variables, and those rendered files are stored in the DCM project’s deployment artifacts. Use opaque identifiers, environment names, and configuration values in Jinja — never API keys, passwords, or connection strings. Use Snowflake Secrets for credentials, referenced by name, not by value.

PLAN DELTA won’t catch external changes to your account since the last deployment.

PLAN DELTA only evaluates changed definition files — it doesn’t query the live account for the unchanged portions. If someone manually altered a view outside of DCM since your last deployment, PLAN DELTA won’t detect that drift. Full PLAN always compares against the live account. The rule: PLAN DELTA for development speed, full PLAN as the pre-deploy gate in every CI/CD pipeline.

The One Principle

“Write what you want. Let Snowflake figure out how to get there. The PLAN is the diff — read it before every DEPLOY, especially the DROP lines.”

FAQ

What is Snowflake DCM Projects and when did it go GA?

DCM Projects (Database Change Management) is Snowflake’s native infrastructure-as-code system. You write DEFINE statements in SQL files describing your desired Snowflake object state, run PLAN to preview changes, and DEPLOY to apply them. Snowflake tracks state internally — no external state file. It went generally available on August 7 2026 and is available on all Snowflake editions at no additional cost beyond cloud services compute.

Should I replace Terraform with Snowflake DCM Projects?

It depends on your scope. If you manage only Snowflake objects and have been using Terraform purely for that, DCM Projects is a cleaner native alternative with day-zero feature support and no state file drift. If you manage multi-cloud infrastructure — S3 buckets, IAM, Kafka, and Snowflake together — keep Terraform for the cross-cloud layer. DCM Projects only manages Snowflake objects.

What is the difference between PLAN and PLAN DELTA in DCM Projects?

PLAN compares all your definition files against the current live account state and produces a full changeset. PLAN DELTA evaluates only the definition files that changed since the last deployment, plus any downstream definitions that depend on them. PLAN DELTA is dramatically faster on large projects — use it during active development for quick feedback. Always run a full PLAN before deploying to catch any external changes to your account that PLAN DELTA would miss.

How do I prevent DCM Projects from accidentally dropping objects?

Two safeguards: first, always review the PLAN output before DEPLOY — look specifically at the DROP entries. Second, if using GitHub Actions, add the DROP detection step from the Snowflake Labs sample workflows, which parses plan_result.json and blocks the deploy workflow if it finds top-level DROPs on databases, schemas, tables, or stages. If you want to stop managing an object without dropping it, use ALTER TABLE … UNSET DCM PROJECT to detach it before removing its DEFINE statement.

What are inherited grants in DCM Projects and why do they matter?

Inherited grants let you write a single GRANT statement with the INHERITED keyword that automatically applies to every current and future object of a specified type within a container — for example, all current and future tables in a schema. This permanently closes the grant drift problem: a new table is created, the grant applies automatically without any manual step or separate Terraform resource. Inherited grants require enabling a behavior-change parameter at the account level, independent of DCM Projects itself.

Can Cortex Code write my DCM Project definition files for me?

Yes. The Cortex Code DCM skill can scaffold a new project from scratch, author and edit DEFINE statements and Jinja templates, run PLAN and DEPLOY, interpret plan output, and diagnose failures. Available in Cortex Code CLI, Snowsight, the VS Code extension, and Cortex Code Desktop. Use natural-language prompts like “Add a new dynamic table for customer spending” or “Why did my last plan fail?” and Cortex Code handles the authoring and debugging loop.

Related reading: Snowflake Dynamic Data Masking & Row Access Policies · Using MCP Servers with Snowflake · Cortex AI token usage monitoring · Identifying hidden Cortex AI token costs · dbt State — stop rebuilding what hasn’t changed · DCM Projects overview (official) · Deploy and manage DCM Projects (official) · Snowflake Labs DCM repository (GitHub)