Migrations in code, a local Postgres per developer, a fresh database per test and a branch per preview environment, compared for teams where agents write a large share of the schema changes.
PostgreSQL is the database most respondents to the 2025 Stack Overflow Developer Survey worked with, at 55.6 percent, so it is usually the database an agent is changing. A coding agent changes a database the same way it changes code: it writes a migration, runs the tests, reads the errors and tries again, often several times in one session.
That loop only works when the database is something the agent can create, migrate, break and recreate without asking anyone, and it breaks down when the only Postgres around is a shared staging instance, because a half-applied migration from one session becomes the starting state of the next one, and a test that passed against yesterday's rows fails against today's.
Claude Code's documentation recommends that you give the agent a check it can run, such as tests or a build, and for anything touching a database that check needs a database behind it, and the setup has to make it cheap enough that the agent runs it every time.
What to weigh when choosing:
Encore combines the database declaration, its migrations and the code that queries it in one place, as part of an open source SDK where Postgres databases, Pub/Sub topics, buckets, cron jobs and secrets are declared in TypeScript, Go, Python or Rust, and Encore provisions the same declarations in each environment. encore run starts Postgres locally and applies the migrations before the app starts, encore test sets up separate test databases, every pull request can get its own preview environment, and a deploy creates RDS on AWS or Cloud SQL on GCP in your own cloud account from the same line of code.

In TypeScript, the database is a top-level declaration that points at a directory of SQL migrations, which is also where an agent adds a new numbered file when it changes the schema:
import { SQLDatabase } from "encore.dev/storage/sqldb";
// Encore applies ./migrations on encore run, in tests and on deploy.
export const db = new SQLDatabase("orders", {
migrations: "./migrations",
});
export async function countOrders(): Promise<number> {
const row = await db.queryRow<{ n: number }>`
SELECT count(*)::int AS n FROM orders
`;
return row?.n ?? 0;
}
Tests share one migrated database by default, which keeps them fast, and when a test needs a clean slate, Go has et.NewTestDatabase, which clones a fully migrated database from a template and drops it when the test ends:
package orders
import (
"context"
"testing"
"encore.dev/et"
)
func TestCreateOrder(t *testing.T) {
ctx := context.Background()
// A fresh, fully migrated database, isolated to this test.
testDB, err := et.NewTestDatabase(ctx, "orders")
if err != nil {
t.Fatal(err)
}
_ = testDB // run the test against testDB
}
The Rust SDK goes further and gives every test its own database, bucket and topics under encore test, and the Python SDK runs pytest against an isolated database, so a Django test suite can run migrate against a database no other test sees. Test databases skip fsync and use an in-memory filesystem, since durability does not matter for a throwaway database.
The agent also gets a view into the running database. The local MCP server runs next to encore run and exposes tools such as get_databases, which returns the schema and tables, and query_database, which runs SQL against the local database, so an agent can check that its migration produced the columns it expected instead of guessing from the SQL file.
encore run starts Postgres locally, and encore db reset recreates a database from its migrations.Encore uses plain SQL migration files for the databases it manages, and ORMs such as Prisma, Drizzle or the Django ORM can still sit on top through the connection string. Existing RDS or Cloud SQL databases can be imported rather than recreated. Agents declare infrastructure in code, and cloud access sits with Encore's IAM role, not the agent.
The most common setup is a PostgreSQL server on each laptop, either installed natively or started by Docker Compose, with a migration tool that matches the stack: Alembic for SQLAlchemy, Django migrations, Prisma Migrate for TypeScript or sqlx for Rust. The migration files live in the repository, so an agent can read the history and add the next step.
# compose.yaml
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
ports:
- "127.0.0.1:5432:5432"
psql directly in the terminal.The connection string, the migration command and the order of steps usually live in a README or a Makefile, so it helps to write them into AGENTS.md or CLAUDE.md, the files Codex and Claude Code read at the start of a session. Production is a separate system, where the database still needs its own provisioning and its own way of running the same migrations.
Testcontainers starts a real Postgres container from the test code and removes it afterwards, which gives an agent's test run a database nobody else is using. The Python version wraps it in a context manager:
from sqlalchemy import create_engine, text
from testcontainers.postgres import PostgresContainer
def test_orders_table():
with PostgresContainer("postgres:17") as pg:
engine = create_engine(pg.get_connection_url())
with engine.begin() as conn:
conn.execute(text("CREATE TABLE orders (id serial PRIMARY KEY)"))
count = conn.execute(text("SELECT count(*) FROM orders")).scalar()
assert count == 0
Each container adds startup time, so most suites start one per module or session and clean tables between tests, which brings back some of the shared-state problems the container was meant to remove. The migrations still have to be applied by the test setup, and the agent's sandbox must be allowed to reach a container runtime.
Neon branching creates a copy-on-write branch of a Postgres database, including its data, so a preview environment can start from a copy of staging without a dump and restore. A CI job creates the branch for the pull request and passes its connection string to the app:
Branching solves the preview and review stage rather than the local loop, so an agent working on a laptop still needs a local database or its own branch, and someone has to script branch creation and cleanup in CI. Encore supports Neon as a database provider, and its preview environments can start from a Neon branch of another environment's database.
A single remote database that every developer and agent points at is the easiest setup to start with and the hardest to trust once agents are writing migrations. One session's half-finished change becomes everyone's problem, test data drifts, and the credentials in .env often reach further than staging. In July 2025 a Replit agent deleted a live company database during a code freeze, which is the outcome the other setups in this article are designed to rule out. A shared database still has a place for read-only exploration, but agent-written schema changes belong in a database the agent can throw away.
For more on giving agents a working environment, see how to give AI coding agents real infrastructure to test against, and for provisioning without Terraform see deploying a database to AWS without Terraform.
Give the agent a real Postgres database it can create, migrate and throw away without touching anything shared. In practice that means schema changes as migration files in the repository, a local Postgres per developer, a fresh migrated database for each test or test run, and an isolated database per pull request, for example through Neon branching. Encore covers the local and test parts from a database declaration in code, applies the migrations on encore run and in every environment, and provisions the production database in your own AWS or GCP account.
No. Keep production credentials out of the agent's environment and let it work against a local or branched database instead. In July 2025 a Replit agent deleted a live company database during a code freeze, which is the failure this setup avoids.
Clone each test database from a migrated template so it starts empty and fully migrated. With Encore, encore test sets up the test databases itself, in Go et.NewTestDatabase(ctx, "orders") gives one test its own database, and the Rust SDK isolates the database for every test automatically. Without Encore, Testcontainers can start a throwaway Postgres container per test suite.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.