> ## Documentation Index
> Fetch the complete documentation index at: https://midplane.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect Railway Postgres to your AI agent

> Point an AI agent at Railway Postgres through Midplane — why the internal hostname fails, and the TCP proxy that fixes it.

Railway gives a Postgres service two addresses, and the one it shows you first is the one that won't work from outside Railway. That's the whole difficulty here; everything else is standard.

Once connected, your agent reaches Railway only through Midplane: every statement is parsed, checked against [policy](/docs/policies/overview), and written to the [audit trail](/docs/concepts/audit-trail) before it runs.

## The internal hostname won't work

Railway's `DATABASE_URL` points at the private network:

```
postgres://postgres:PASSWORD@postgres.railway.internal:5432/railway
```

`*.railway.internal` resolves only from services inside the same Railway project, on Railway's private IPv6 network. It doesn't resolve from your laptop, and it doesn't resolve from Midplane Cloud. Private address space is rejected before a connection is attempted, so **Test connection** reports:

```
Could not connect. Check the host, port, and that the database accepts
connections from the internet.
```

You have two ways forward, depending on whether you want the database exposed at all.

## Option 1 — expose a TCP proxy

Railway databases are private by default. To reach one from outside, open the Postgres service's **Settings → Networking** and add **Public Networking**. Railway creates a TCP proxy and populates a second variable:

```
DATABASE_PUBLIC_URL
```

Use that value — the host and port are assigned by Railway, so copy it from the service's **Variables** rather than assembling one by hand. Add `sslmode=require` and paste it into **Connect Postgres** in the [dashboard](https://app.midplane.ai).

<Warning>
  A TCP proxy is a public listener. Anyone with the credential can reach the database, so this is the moment to stop using the default `postgres` superuser and create the least-privilege role below.
</Warning>

## Option 2 — run Midplane inside the project

If you'd rather not expose the database at all, [self-host](/docs/self-host/deploy) Midplane as a service in the same Railway project. The internal hostname then resolves normally, the database stays private, and your agents connect to Midplane's endpoint instead of to Postgres.

This keeps the number of publicly reachable things at one, and the thing that's public is the layer that parses SQL and enforces policy — rather than the database itself.

## Create a least-privilege role

Railway hands you the `postgres` superuser. Connect once with it and create a role for Midplane:

```sql theme={null}
CREATE ROLE midplane LOGIN PASSWORD 'a-strong-password';
GRANT CONNECT ON DATABASE railway TO midplane;
GRANT USAGE ON SCHEMA public TO midplane;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO midplane;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO midplane;
```

Then swap the username and password in the connection string you copied. Leave Midplane's default access level at `read` and grant writes per table later — see [table access](/docs/concepts/table-access).

## Next steps

<Columns cols={2}>
  <Card title="Connect your agent" icon="plug" href="/docs/agents/overview" horizontal>
    Wire up Cursor, Claude Code, or any MCP client.
  </Card>

  <Card title="Write a policy" icon="shield-check" href="/docs/policies/overview" horizontal>
    Grant the reads and writes your agent actually needs.
  </Card>
</Columns>
