Every platform engineer knows the scenario. A deployment runs at 2 AM. The migration tool reports applying changeset 47 of 52. Then: connection reset. The external binary exits. The database is left in a state that neither the tool nor the operator fully understands — 47 applied changesets, a partially created index, and a tracking table that no longer agrees with reality. A senior DBA spends the next three hours querying pg_catalog by hand, reconciling what the tool believes happened with what the database actually contains.
This has been happening for over a decade. In 2026, it is still happening.
The broader infrastructure landscape has evolved dramatically. We deploy application containers with deterministic precision across global Kubernetes clusters. We run ephemeral database instances on Databricks Lakebase where compute and storage scale independently. We define entire cloud environments as reproducible code.
Yet operational database deployments — the single most unforgiving stateful layer in the entire stack — still depend on the same fundamental pattern we adopted in 2013: an external binary, written in a language the database does not speak, pushing SQL statements over a TCP connection and hoping nothing breaks mid-flight.
The Illusion of Stateless Control
The standard deployment pipeline today follows a predictable pattern. A stateless CI/CD runner (GitHub Actions, Azure DevOps, GitLab CI) spins up, downloads a migration CLI, and points it at a PostgreSQL instance. The binary reads .sql files or XML/YAML changelogs, applies its own parsing logic, infers transaction boundaries, and fires statements sequentially over the wire.
These tools have earned their adoption. Flyway provides disciplined version tracking with a clean mental model. Liquibase offers comprehensive changelog formats across database engines. Bytebase adds a collaborative GUI with role-based access control and built-in review workflows. For teams moving from manual deployments to structured migration pipelines, any of these tools represents a meaningful improvement. The Liquibase 2024 State of Database DevOps report found that a majority of organizations still deploy database changes manually or semi-manually — for them, the bar to clear is low, and these tools clear it.
But they all operate under the same fundamental constraint: the orchestration logic lives outside the database engine.
This works reliably for the common case — a linear sequence of CREATE TABLE and ALTER TABLE statements applied one after another. Enterprise database deployments, however, are rarely that simple. A typical production deployment might need to:
- Apply schema DDL within an all-or-nothing transaction.
- Run
CREATE INDEX CONCURRENTLY, which PostgreSQL explicitly forbids inside a transaction block. - Load environment-specific reference data conditionally, based on the current state of the database.
- Validate the resulting schema against business rules before committing.
- Handle partial failures in one phase without abandoning the entire deployment.
When the orchestrator is external, these requirements create contradictions that no amount of configuration can resolve. The tool must manage transaction boundaries without direct access to the database’s transactional state. It must sequence operations that have fundamentally different transaction requirements. It must handle failures in a system whose internal state it cannot query in real time.
The Anatomy of a Mid-Flight Failure
The failure mode is specific and structural.
Every external migration tool maintains its own tracking table — a record of which changesets have been applied, in what order, with what checksums. This tracking table represents the tool’s belief about the database’s state. The actual database schema, queryable through pg_catalog, is the ground truth.
Under normal operation, these two realities stay synchronized. When a deployment fails mid-execution, they diverge.
The tracking table says changeset 47 was applied. The database contains a partially created table from changeset 47, a lingering advisory lock from the migration framework, and a connection slot still held by a terminated backend. The next deployment attempt fails because the tool believes changeset 47 is complete, but the schema contradicts that belief. The database is flagged as “dirty” — a status that blocks all further automated deployments until a human intervenes.
The standard recovery procedure:
- A DBA connects directly and inspects
pg_stat_activityto identify orphaned connections. - Queries
pg_catalog.pg_locksto find lingering lock state. - Manually drops partial artifacts — half-created tables, failed indexes, orphaned sequences.
- Edits the migration tool’s tracking table to reconcile it with the actual schema.
- Re-runs the deployment and hopes the same failure does not recur.
This is not an edge case. Redgate’s State of Database DevOps surveys have consistently found that roughly half of respondents have experienced a failed deployment requiring manual database intervention. The number has not meaningfully decreased in three years of measurement.
The root cause is structural, not incidental: the entity managing the deployment has less information about the database’s state than the database itself.
What the Engine Already Provides
PostgreSQL is not a passive storage target that accepts SQL strings from the outside. It is a sophisticated execution runtime — and the capabilities it provides are precisely the capabilities that external migration tools attempt to reimplement, less reliably, from the wrong side of a TCP connection.
Transactional DDL. PostgreSQL can roll back CREATE TABLE, ALTER TABLE, CREATE FUNCTION, and most schema changes within a transaction. This is not a minor feature. MySQL does not support it. Oracle does not support it. A deployment that wraps its schema changes in a single transaction gets atomic, all-or-nothing semantics — from the engine itself, not from an external wrapper’s approximation.
Savepoints. PostgreSQL supports nested transaction isolation via savepoints. A complex deployment can attempt a risky operation within a savepoint, catch the failure with an exception handler, roll back to the savepoint, and continue execution — all within a single outer transaction. The engine’s own MVCC implementation guarantees the isolation, not the migration tool’s retry logic.
PL/pgSQL. PostgreSQL ships with a Turing-complete procedural language specifically designed for this kind of conditional, stateful logic. Exception handling with GET STACKED DIAGNOSTICS. Loop constructs for batch operations. Direct access to system catalogs for runtime introspection. The language exists precisely so that procedural deployment logic can execute inside the engine, with full transactional awareness.
System catalogs. pg_catalog contains the ground truth of every table, column, index, constraint, function, and extension in the database. Code running inside PostgreSQL can query this catalog to make deployment decisions based on actual state — not on a tracking table’s approximation of state from the last successful run.
When an external tool orchestrates a deployment, it trades these native capabilities for its own reimplementation. A reimplementation that is necessarily less informed about the engine’s state, less integrated with its concurrency control, and less capable of atomic recovery.
Rethinking the Boundary
The alternative is not to abandon CI/CD pipelines. They remain the correct mechanism for triggering deployments — for authentication, for audit trails, for environment promotion. The question is what happens after the trigger.
Instead of a stateless runner downloading an external binary that orchestrates SQL from outside the engine, the pipeline could load deployment artifacts — schema files, reference data, configuration parameters — directly into the database session. Then hand execution control to SQL that runs natively within the engine.
In this model, the database’s own procedural language controls transaction boundaries. Its own savepoints provide isolation for multi-phase operations. Its own system catalogs inform conditional logic. Its own exception handling manages failures. If a step fails, the engine rolls back to its own savepoint — not to an external tool’s approximation of where the last safe state was. There is no divergence between tracking state and actual state, because the execution never left the engine.
The pattern has a natural name: a native execution fabric. Not a migration framework that manages the database from outside, but an execution environment that operates within the database’s own transactional boundaries, using the database’s own language, querying the database’s own catalogs.
PostgreSQL has supported every capability required for this pattern since version 9.0 — transactional DDL, savepoints, PL/pgSQL, comprehensive system catalogs. The technical foundation has existed for fifteen years. The question is not whether the engine is capable. The question is why we continue to interpose external orchestrators between the deployment pipeline and the one system that has complete, authoritative knowledge of its own state.
As deployment targets grow more ephemeral — serverless instances that scale to zero, compute that separates from storage, databases that serve autonomous workloads requiring strict transactional guarantees — the cost of managing state from outside the engine will only increase. The gap between what the external tool knows and what the database knows will only widen.
The database already knows how to orchestrate itself. We have been getting in its way.