Drizzle Postgres on Fly
Run Drizzle in a Fly.Service, with Fly Managed Postgres on the same organization’s private network. Start with Fly setup, the Service tutorial, and a src/schema.ts exporting Users from the Postgres client guide.
Install the database dependencies
Section titled “Install the database dependencies”pnpm add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-pg pgpnpm add -D drizzle-kit@1.0.0-rc.5-ab785fcThe Drizzle versions match the repository’s Effect integration.
Configure migration generation
Section titled “Configure migration generation”import { defineConfig } from "drizzle-kit";
export default defineConfig({ dialect: "postgresql", schema: "./src/schema.ts", out: "./drizzle", dbCredentials: { url: process.env.DATABASE_URL! },});DATABASE_URL is the direct Postgres URL used by the migration runner, not the pooled runtime URL.
Generate after changing the schema
Section titled “Generate after changing the schema”pnpm exec drizzle-kit generateReview the generated SQL and snapshots, including any rename decisions, before applying them.
Commit the migration
Section titled “Commit the migration”git add src/schema.ts drizzle.config.ts drizzlegit diff --cachedgit commit -m "Add users migration"Commit the schema, SQL, and snapshots together; CI deploys this reviewed history rather than generating it.
Declare the cluster
Section titled “Declare the cluster”import * as Fly from "alchemy/Fly";
export const Db = Fly.Postgres("Db", { region: "iad" });This provisions a billed Managed Postgres cluster, not an unmanaged Postgres Machine.
Keep the driver outside the bundle
Section titled “Keep the driver outside the bundle”// src/api.ts — the Service from the tutorial{ app: Site, main: import.meta.url, region: "iad", build: { install: ["pg"] },}pg must be installed in the Service image because bundling its CommonJS exports breaks the driver’s interop.
Bind the cluster
Section titled “Bind the cluster”import { Db } from "./db.ts";
Effect.gen(function* () { const conn = yield* Fly.ConnectPostgres(Db); return { fetch: Effect.succeed(HttpServerResponse.text("Hello from Fly!")), };}),}).pipe(Effect.provide(Fly.ConnectPostgresHttp)),The binding attaches the cluster to the Service’s App and supplies pooled and direct connection Effects. These excerpts use the tutorial’s minimal text handler until the database is migrated.
Provision before the first migration
Section titled “Provision before the first migration”pnpm alchemy deployKeep the tutorial’s yield* Api and public IP in the Stack, using Fly.providers(). This first deployment creates the database and a handler that does not yet query application tables.
Apply the committed SQL
Section titled “Apply the committed SQL”pnpm exec drizzle-kit migrateSupply the cluster’s connectionUri as the migration runner’s secret DATABASE_URL, and run where the direct hostname resolves and is reachable on the Fly organization network, such as a correctly configured WireGuard peer or a Machine on 6PN. A pooled URI is not the migration endpoint.
Open the runtime client
Section titled “Open the runtime client”import * as Drizzle from "alchemy/Drizzle/Postgres";
const conn = yield* Fly.ConnectPostgres(Db);const db = yield* Drizzle.Postgres(conn.connectionString);The pooled connection opens lazily when a request first queries the database.
Query from the Service
Section titled “Query from the Service”import { Users } from "./schema.ts";
return { fetch: Effect.succeed(HttpServerResponse.text("Hello from Fly!")), fetch: Effect.gen(function* () { const users = yield* db.select().from(Users).pipe(Effect.orDie); return yield* HttpServerResponse.json({ users }); }),};Queries run inside the request scope, rather than during infrastructure construction.
Deploy the application
Section titled “Deploy the application”pnpm alchemy deployFor subsequent schema changes, generate, review, and commit first, then apply the committed migrations before deploying compatible application code. alchemy deploy does not replace drizzle-kit migrate in this guide.
Choose one migration owner
Section titled “Choose one migration owner”Fly.Postgres also supports a migrations directory, but this guide deliberately leaves it unset and keeps Drizzle as the migration owner. Switching an existing database to Alchemy’s migration history is a separate migration adoption decision, not a command substitution.
Continue
Section titled “Continue”See the Managed Postgres guide, portable Postgres queries, and migration workflow. The fly-postgres example demonstrates the Service binding, but its test-only HTTP migration endpoint is not used here.