1---
2title: Drizzle Postgres on Railway
3description: Query Railway Postgres from an Effect-native Service and apply reviewed Drizzle migrations separately.
4---
5
6Run Drizzle in a Railway container beside [Railway Postgres](/railway/data/postgres). Start with [Railway setup](/railway/setup), the [Service tutorial](/railway/tutorial/part-2), and a `src/schema.ts` exporting `Users` from the [Postgres client guide](/sql/drizzle/postgres).
7
8## Install the database dependencies
9
10```sh
11pnpm add drizzle-orm@1.0.0-rc.5-ab785fc @effect/sql-pg pg
12pnpm add -D drizzle-kit@1.0.0-rc.5-ab785fc
13```
14
15The Drizzle versions match the repository's Effect integration.
16
17## Configure migrations
18
19```typescript
20// drizzle.config.ts
21import { defineConfig } from "drizzle-kit";
22
23export default defineConfig({
24 dialect: "postgresql",
25 schema: "./src/schema.ts",
26 out: "./drizzle",
27 dbCredentials: { url: process.env.DATABASE_URL! },
28});
29```
30
31`DATABASE_URL` here belongs to your migration runner; the deployed Service receives its own private connection through a binding.
32
33## Generate after changing the schema
34
35```sh
36pnpm exec drizzle-kit generate
37```
38
39Review the generated SQL and snapshots, including any rename decisions, before applying them.
40
41## Commit the migration
42
43```sh
44git add src/schema.ts drizzle.config.ts drizzle
45git diff --cached
46```
47
48```sh
49git commit -m "Add users migration"
50```
51
52Commit the schema, SQL, and snapshots together rather than generating migrations during deployment.
53
54## Declare Postgres
55
56```typescript
57// src/db.ts
58import * as Railway from "alchemy/Railway";
59import { Site } from "./project.ts";
60
61export const Db = Railway.Postgres("Db", { project: Site });
62```
63
64Use the same Project and environment as the Service; omitting `environment` on both selects the Project's primary environment.
65
66## Install the driver in the Service image
67
68```diff lang="typescript"
69 // src/api.ts
70 {
71 project: Site,
72 main: import.meta.url,
73+ build: { install: ["pg"] },
74 }
75```
76
77Use `Railway.Service`, whose image can install `pg`, rather than assuming the single-file Canvas `Railway.Function` has the same packaging support.
78
79## Bind Postgres
80
81```diff lang="typescript"
82+import { Db } from "./db.ts";
83
84 Effect.gen(function* () {
85+ const conn = yield* Railway.ConnectPostgres(Db);
86 return {
87 fetch: Effect.succeed(HttpServerResponse.text("Hello from Railway!")),
88 };
89-}),
90+}).pipe(Effect.provide(Railway.ConnectPostgresHttp)),
91```
92
93The binding injects the private URI into the Service environment; `conn.connectionString` is an Effect yielding a redacted URL, not a query client. These excerpts use the tutorial's minimal text handler until the database is migrated.
94
95## Provision before the first migration
96
97```sh
98pnpm alchemy deploy
99```
100
101Keep `yield* Api` in the tutorial's Stack with `Railway.providers()`. This initial deployment provisions Postgres while the handler still avoids application tables.
102
103## Apply the committed SQL
104
105```sh
106pnpm exec drizzle-kit migrate
107```
108
109For an external CI runner, supply the database's `publicConnectionUri` as secret `DATABASE_URL`; the default `public: true` creates that TCP proxy. With `public: false`, run migrations inside the same Railway environment using `connectionUri` instead.
110
111## Review public TLS
112
113The generated Railway Postgres URLs currently include `sslmode=no-verify`; a public TCP proxy does not provide certificate verification. For production, configure a verifiable server certificate and migration-client trust, or run migrations within the private environment.
114
115## Open the runtime client
116
117```diff lang="typescript"
118+import * as Drizzle from "alchemy/Drizzle/Postgres";
119
120 const conn = yield* Railway.ConnectPostgres(Db);
121+const db = yield* Drizzle.Postgres(conn.connectionString);
122```
123
124Runtime traffic stays on Railway's private network, regardless of whether a public migration endpoint exists.
125
126## Query from the Service
127
128```diff lang="typescript"
129+import { Users } from "./schema.ts";
130
131 return {
132- fetch: Effect.succeed(HttpServerResponse.text("Hello from Railway!")),
133+ fetch: Effect.gen(function* () {
134+ const users = yield* db.select().from(Users).pipe(Effect.orDie);
135+ return yield* HttpServerResponse.json({ users });
136+ }),
137 };
138```
139
140The request opens the pool lazily and closes it when its scope ends.
141
142## Deploy the application
143
144```sh
145pnpm alchemy deploy
146```
147
148For later changes, generate, review, and commit the schema, SQL, and snapshots before running migrations and deploying compatible code. `Railway.Postgres` has no `migrations` prop, so deployment does not replace `drizzle-kit migrate`.
149
150## Continue
151
152See the [SQL database comparison](/sql/databases), [Postgres client guide](/sql/drizzle/postgres), and [migration workflow](/sql/drizzle/migrations). The [railway-service example](https://github.com/alchemy-run/alchemy/tree/main/examples/railway-service) contains the Service, `pg` installation, and Postgres binding used here.