Skip to content

Postgres

A Railway.Postgres is Postgres as a Service: the official ghcr.io/railwayapp-templates/postgres-ssl:16 image, a Volume at /var/lib/postgresql/data, POSTGRES_* / DATABASE_URL variables, and an optional TCP proxy for the public URL.

Private hostname is {name}.railway.internal. From a Service, yield ConnectPostgres. From a laptop, use publicConnectionUri.

Pass a Project. Alchemy generates a unique name, password, volume, and a public TCP proxy.

const site = yield* Railway.Project("Site");
const db = yield* Railway.Postgres("Db", { project: site });

Yield ConnectPostgres in Service init. Provide ConnectPostgresHttp. Pass conn.connectionString to Drizzle or SQL.

import * as Drizzle from "alchemy/Drizzle/Postgres";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
export default class Api extends Railway.Service<Api>()(
"Api",
{
project: Site,
main: import.meta.url,
build: { install: ["pg"] },
},
Effect.gen(function* () {
const conn = yield* Railway.ConnectPostgres(Db);
const db = yield* Drizzle.Postgres(conn.connectionString);
return {
fetch: Effect.gen(function* () {
const rows = yield* db.execute("select 1 as ok");
return HttpServerResponse.json({ rows });
}),
};
}).pipe(Effect.provide(Railway.ConnectPostgresHttp)),
) {}

connectionString is the private URI ({name}.railway.internal:5432). Both come from Railway.Postgres, packed into the Service env — not Config.redacted.

To store Railway’s ${{Db.DATABASE_URL}} template instead of a resolved URI, pass Railway.ref (Railway.ref(Db, "DATABASE_URL")) as a Variable value.

build.install: ["pg"] is required. Rolldown’s CJS interop breaks pg.Client if you bundle it.

public (default true) creates a TCP proxy on 5432. publicConnectionUri is {domain}:{proxyPort} for laptop access and deploy-time migrations. In-service connections always use the private hostname.

const db = yield* Railway.Postgres("Db", {
project: site,
public: false,
});

Attach a standalone TcpProxy when you need public TCP on a Redis service, or on Postgres created with public: false.

Default is Postgres 16 with SSL. Pass image to pin another tag.

const db = yield* Railway.Postgres("Db", {
project: site,
image: "ghcr.io/railwayapp-templates/postgres-ssl:17",
});

user (default postgres) and database (default railway) are create-only. Wrap password with Redacted.make(...). If omitted, a password is generated on first create.

import * as Redacted from "effect/Redacted";
const db = yield* Railway.Postgres("Db", {
project: site,
user: "app",
database: "app",
password: Redacted.make("…"),
});

Resource-valued props accept the resource or an Effect producing it.

src/db.ts
import * as Railway from "alchemy/Railway";
export const Site = Railway.Project("Site");
export const Db = Railway.Postgres("Db", { project: Site });

Regions lists codes. Services is the container that binds the cluster. MySQL and Mongo are the same shape for those engines. Drizzle on Postgres is the schema-to-query flow. Variables covers Config.redacted and Railway.ref. The Postgres reference lists every prop. The ConnectPostgres reference is the runtime binding. Example: railway-service is the complete runnable project.