Everything so far assumed a console and a running engine. This lesson is about how a schema change gets from your laptop to production without anybody clicking anything — and about the awkward question of who owns the schema when your application already runs its own migrations.
Three directories
The Hasura CLI keeps a project as files:
hasura init stayhub-hasura --endpoint http://localhost:8081
migrations/ # SQL: the database schema itself
metadata/ # YAML: tracked tables, relationships, permissions, actions
seeds/ # SQL: reference dataThe distinction matters. Migrations change the database. Metadata changes the API over it. They are separate directories because they are separate concerns — and they are deployed together because a permission referring to a column that does not exist yet is a broken deploy.
hasura migrate create add_cancellation_reason --from-server
hasura metadata export
hasura migrate apply --database-name default
hasura metadata apply
hasura metadata reloadOrder is not negotiable
Migrations first, then metadata. Metadata that references a new column fails to apply if the column is not there yet, and the failure leaves you with a half-configured API.
Going the other way — dropping a column — the order reverses: remove it from metadata first, then drop it. Otherwise the engine boots with inconsistent metadata and quietly drops the broken object, which is lesson 3’s failure mode arriving in production.
Who owns the schema?
Here is the real question, and most tutorials skip it.
The demo app runs Alembic. Its models are SQLAlchemy, its migrations are Python, and its schema is owned by the backend. Hasura reads that database but did not create it.
Running hasura migrate as well would mean two tools writing DDL to the same database
with two separate version tables, and no coordination between them. That is a genuinely bad time.
So pick one owner:
If Hasura is the only writer of DDL — a Hasura-first project with no
significant backend — use hasura migrate for everything. It is coherent and the
tooling is designed for it.
If an application owns the schema — which is the common case, and the demo app’s case — let it keep ownership. Use Alembic, Flyway or Prisma for DDL, and use Hasura only for metadata:
alembic upgrade head # the schema
hasura metadata apply # the API over it
hasura metadata reload # re-read the new columnsDo not half-do this. A migrations/ directory that is sometimes authoritative is worse
than not having one.
A pipeline
name: deploy-hasura
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install the Hasura CLI
run: curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
- name: Apply the schema
run: alembic upgrade head
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Apply metadata
run: |
hasura metadata apply \
--endpoint "$HASURA_ENDPOINT" --admin-secret "$HASURA_ADMIN_SECRET"
hasura metadata reload \
--endpoint "$HASURA_ENDPOINT" --admin-secret "$HASURA_ADMIN_SECRET"
env:
HASURA_ENDPOINT: ${{ secrets.HASURA_ENDPOINT }}
HASURA_ADMIN_SECRET: ${{ secrets.HASURA_ADMIN_SECRET }}Two things to add before you trust it. Run hasura metadata diff on pull requests so a
reviewer sees the API change, not just the YAML. And check
get_inconsistent_metadata after applying — the apply can succeed while leaving
objects inconsistent, and you want the pipeline to fail rather than the API to quietly shrink.
CI/CD in v3 (DDN)
On the v3 sections. Everything marked v3 (DDN) is taken from the official Hasura DDN documentation as read on 2026-08-21 and was not run locally — it shows configuration, never claimed output. The v2 material was executed against a running engine.
v3 replaces apply-in-place with build-and-promote:
ddn supergraph build createEach build is immutable and gets its own unique GraphQL endpoint. You can point a test suite at a build before anything else sees it, then promote it. The entire category of “somebody applied metadata to production” disappears, because applying is no longer the operation.
And migrations are no longer Hasura’s job
hasura migrate has no v3 successor. Schema migration belongs to your
own tool, and you tell Hasura about the result by re-introspecting:
alembic upgrade head
ddn connector introspect my_connector
ddn model add my_connector new_table
ddn supergraph build createFor teams who use the v2 CLI to version schema and metadata together, this is a real workflow change. For teams already in the second camp above — application owns DDL, Hasura owns metadata — v3 simply makes official the split you already had.
Next
Lesson 14 covers making it fast.