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.
Create Postgres
Section titled “Create Postgres”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 });Connect from a Service
Section titled “Connect from a Service”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 TCP
Section titled “Public TCP”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",});Superuser
Section titled “Superuser”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("…"),});Module-scope declarations
Section titled “Module-scope declarations”Resource-valued props accept the resource or an Effect producing it.
import * as Railway from "alchemy/Railway";
export const Site = Railway.Project("Site");export const Db = Railway.Postgres("Db", { project: Site });Where next
Section titled “Where next”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.