| 1 | --- |
| 2 | title: Postgres |
| 3 | description: A billed Managed Postgres cluster. Bind Fly.ConnectPostgres and query with Drizzle or SQL. |
| 4 | --- |
| 5 | |
| 6 | A [`Fly.Postgres`](/providers/fly/postgres) is a Managed Postgres |
| 7 | (MPG) cluster. It is billed. Do not wrap unmanaged `fly postgres`. |
| 8 | |
| 9 | Create the cluster, pass `migrations` the same way you would on |
| 10 | Neon or PlanetScale, then bind |
| 11 | [`ConnectPostgres`](/providers/fly/connectpostgres) on a |
| 12 | [Service](/fly/compute/services). Pass |
| 13 | `conn.connectionString` to `Drizzle.Postgres` or `SQL.Postgres`. |
| 14 | |
| 15 | ## Create a cluster |
| 16 | |
| 17 | `region` is required. The cluster is regional. An |
| 18 | [App](/fly/compute/apps) is global. Alchemy generates a unique name |
| 19 | unless you pass one. |
| 20 | |
| 21 | ```typescript |
| 22 | const db = yield* Fly.Postgres("Db", { |
| 23 | region: "iad", |
| 24 | }); |
| 25 | ``` |
| 26 | |
| 27 | Alchemy defaults compute to [`iad`](/fly/compute/regions). Pass |
| 28 | another code to put the cluster somewhere else. |
| 29 | |
| 30 | :::caution[Billed] |
| 31 | Managed Postgres is billed. Basic is about $38 per month. |
| 32 | ::: |
| 33 | |
| 34 | :::caution[Changing `region` replaces the cluster] |
| 35 | A new cluster is created in the new region. The old cluster is |
| 36 | deleted. Data is not copied. |
| 37 | ::: |
| 38 | |
| 39 | ## Connect from a Service |
| 40 | |
| 41 | Yield `ConnectPostgres` in the Service's constructor. Provide |
| 42 | `ConnectPostgresHttp`. Pass the connection string to Drizzle or |
| 43 | SQL. |
| 44 | |
| 45 | ```typescript |
| 46 | import * as Drizzle from "alchemy/Drizzle/Postgres"; |
| 47 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 48 | |
| 49 | export default class Api extends Fly.Service<Api>()( |
| 50 | "Api", |
| 51 | { main: import.meta.url, port: 3000 }, |
| 52 | Effect.gen(function* () { |
| 53 | const conn = yield* Fly.ConnectPostgres(Db); |
| 54 | const db = yield* Drizzle.Postgres(conn.connectionString); |
| 55 | return { |
| 56 | fetch: Effect.gen(function* () { |
| 57 | const rows = yield* db.execute("select 1 as ok"); |
| 58 | return HttpServerResponse.json({ rows }); |
| 59 | }), |
| 60 | }; |
| 61 | }).pipe(Effect.provide(Fly.ConnectPostgresHttp)), |
| 62 | ) {} |
| 63 | ``` |
| 64 | |
| 65 | `connectionString` is the cluster's pooled PgBouncer URI Output. |
| 66 | `directConnectionString` is the direct URI. Both come from |
| 67 | `Fly.Postgres`, packed into the Service env — not `Config.Redacted`. |
| 68 | |
| 69 | ## Migrations |
| 70 | |
| 71 | Pass a directory, `{ dir, table? }`, or a `Drizzle.Schema` |
| 72 | resource. Alchemy applies pending files on deploy over the direct |
| 73 | URI and records them in `__alchemy_migrations`. |
| 74 | |
| 75 | ```typescript |
| 76 | const schema = yield* Drizzle.Schema("app-schema", { |
| 77 | schema: "./src/schema.ts", |
| 78 | out: "./migrations", |
| 79 | }); |
| 80 | |
| 81 | const db = yield* Fly.Postgres("Db", { |
| 82 | region: "iad", |
| 83 | migrations: schema, |
| 84 | }); |
| 85 | ``` |
| 86 | |
| 87 | ```typescript |
| 88 | const db = yield* Fly.Postgres("Db", { |
| 89 | region: "iad", |
| 90 | migrations: "./migrations", |
| 91 | importFiles: ["./seed.sql"], |
| 92 | }); |
| 93 | ``` |
| 94 | |
| 95 | MPG is on the org private network. Deploy-time apply needs a |
| 96 | route to that network. |
| 97 | |
| 98 | ## Plan |
| 99 | |
| 100 | `plan` is the hardware size: `basic`, `starter`, `launch`, |
| 101 | `scale`, `performance`. Default is `basic`. |
| 102 | |
| 103 | ```typescript |
| 104 | const db = yield* Fly.Postgres("Db", { |
| 105 | region: "iad", |
| 106 | plan: "starter", |
| 107 | }); |
| 108 | ``` |
| 109 | |
| 110 | :::note[Create-only] |
| 111 | Changing `plan` later is ignored. Fly has no cluster update API. |
| 112 | ::: |
| 113 | |
| 114 | ## Volume size |
| 115 | |
| 116 | `volumeSizeGb` is the initial disk. Fly defaults to 10 GB. |
| 117 | |
| 118 | ```typescript |
| 119 | const db = yield* Fly.Postgres("Db", { |
| 120 | region: "iad", |
| 121 | volumeSizeGb: 20, |
| 122 | }); |
| 123 | ``` |
| 124 | |
| 125 | :::note[Create-only] |
| 126 | Changing `volumeSizeGb` later is ignored. |
| 127 | ::: |
| 128 | |
| 129 | ## PostGIS |
| 130 | |
| 131 | `postgis: true` enables PostGIS at create. |
| 132 | |
| 133 | ```typescript |
| 134 | const db = yield* Fly.Postgres("Db", { |
| 135 | region: "iad", |
| 136 | postgis: true, |
| 137 | }); |
| 138 | ``` |
| 139 | |
| 140 | :::note[Create-only] |
| 141 | Flipping `postgis` later is ignored. |
| 142 | ::: |
| 143 | |
| 144 | ## Where next |
| 145 | |
| 146 | [Regions](/fly/compute/regions) lists codes. Default is `iad`. |
| 147 | [Services](/fly/compute/services) is the always-on Machine that |
| 148 | binds the cluster. |
| 149 | [Drizzle on Postgres](/sql/drizzle/postgres) is the schema-to-query |
| 150 | flow. |
| 151 | [Secrets](/fly/data/secrets) covers `Config.Redacted` for values |
| 152 | from `.env`. |
| 153 | The [`Postgres` reference](/providers/fly/postgres) lists every |
| 154 | prop. |
| 155 | The [`ConnectPostgres` reference](/providers/fly/connectpostgres) |
| 156 | is the runtime binding. |
| 157 | [Example: fly-postgres](https://github.com/alchemy-run/alchemy/tree/main/examples/fly-postgres) |
| 158 | is the complete runnable project. |