Drizzle Studio and Gateway on Railway behind Tailscale

This tutorial deploys a database browser and a PostgreSQL database on Railway, and keeps both off the public internet.

A Tailscale Forwarder joins your tailnet and forwards the UI port into Railway’s private network. After that, you open the browser UI over HTTPS from any device on your tailnet, with no public domain on either service.

You can use either Drizzle Studio or Drizzle Gateway. Both are covered in full below.

If you want the same private access for drizzle-kit and other database clients, see Connect to a private Railway database with Tailscale.

This guide assumes the following:
  • You have a Railway account.

  • You have a Tailscale account.

  • You have the Tailscale client installed on the machine you work from, and that machine is signed in to your tailnet. You can find the downloads on the Tailscale website.

How it works

Railway gives every service a private domain that ends in .railway.internal. Those domains resolve only inside the project. The Tailscale Forwarder runs as a service in the same project, so it can reach them. It also joins your tailnet as a machine, so your own devices can reach it.

Set up the Railway project

Create a Railway project

Log in to your Railway dashboard and click the New Project button.

Provision a PostgreSQL database

On the project canvas, click the New button in the top right corner and select DatabasePostgreSQL. You can also use the command palette (Cmd/Ctrl + K) and search for PostgreSQL.

Keep the database on the private network

Click on the PostgreSQL service, go to the Settings tab, and find the Networking section. Verify that public access is disabled. Under Private Networking you will see the private domain of the service, for example postgres.railway.internal. The forwarder will use that domain.

Choose Gateway or Studio

Drizzle has two Railway templates, designed for different use cases.

Drizzle StudioDrizzle Gateway
DatabasesOne, fixed to the DATABASE_URL you set in RailwayMany, you add and edit them in the UI
AccessPASSCODE generated by the templateMASTERPASS, plus passcodes you hand out

Pick Drizzle Studio when:


Pick Drizzle Gateway when:

Keep in mind that a Gateway user who knows MASTERPASS can add a connection to any database the service can reach.


Drizzle Studio
Drizzle Gateway

Deploy the Studio template into the same project

Press the button below to open the Drizzle Studio template.

Don’t deploy it right away - make sure to read this step to the end first and set the required variables.

Deploy on Railway

Open the Deploy to dropdown and select the project that holds your database. Both services must be in the same project, otherwise Studio won’t be able to reach the database over the private network, since it will be placed on a different private network.

The template comes with three variables. Set DATABASE_URL to ${{Postgres.DATABASE_URL}}, which resolves to the private connection string of the database, so Drizzle Studio reaches it over the Railway private network. PASSCODE guards access to the UI and the template generates it. PORT is set to 4983; leave it as it is, because the forwarder mapping you add later points at the same number. Click Deploy.

Remove the public domain

Go to the Settings tab of the Drizzle Studio service and find the Networking section. If Railway generated a public domain, remove it. Keep the private domain. After this step the UI is reachable only from inside the project, and soon from your tailnet.

Check MagicDNS and HTTPS certificates

Open the DNS page in the Tailscale admin console. Both MagicDNS and HTTPS Certificates must be enabled. Each section offers the opposite action, so a button that reads Disable MagicDNS... means MagicDNS is already on. MagicDNS gives the forwarder a stable machine name. HTTPS certificates let the forwarder serve the UI over HTTPS.

Create a reusable auth key

Go to the Keys page in the settings menu and click Generate auth key. Add a description and turn on Reusable. Leave the other settings as they are. A reusable key lets the forwarder register itself again when it starts without existing state.

Click Generate key. Copy the key now, because the admin console shows it only once.

Deploy the Tailscale Forwarder

The Tailscale Forwarder is a small TCP proxy. It joins your tailnet as a machine and forwards ports on that machine to services on the Railway private network. Railway publishes the template, and it runs the brody192/tailscale-forwarder image.

Deploy on Railway

Paste your auth key into TS_AUTHKEY. Then set CONNECTION_MAPPING_01 to the database mapping. The format is [https:]<source port>:<target host>:<target port>.

5432:${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:${{Postgres.PGPORT}}

This forwards port 5432 on the forwarder to your database.

The template setup screen shows only the variables the template declares, and you cannot add new ones there. CONNECTION_MAPPING_01 is the only mapping you can set now. You will add the second one after the service exists.

Deploy and check the logs

Click Deploy. The template attaches a volume and sets TS_EPHEMERAL=false, so the machine keeps the same identity across redeploys instead of registering a duplicate.

Open the Deployments tab and read the deploy logs. Three lines tell you the forwarder started correctly:

  • tsnet starting with hostname "..." gives the machine name on your tailnet. You need it in a later step.
  • Starting tailscale_fwdr lists what it parsed, including ts-ephemeral: false and the source port, target address and target port of each mapping.
  • listening for connections confirms the forwarder accepts traffic on that source port.

Add the mapping for the UI

Open the Variables tab of the Tailscale Forwarder service and add a second mapping. Increase the number for every mapping you add, and keep the source ports unique.

VariableValue
CONNECTION_MAPPING_02https:4983:${{Drizzle Studio.RAILWAY_PRIVATE_DOMAIN}}:4983

This forwards port 4983 on the forwarder to the UI. Both numbers are 4983: the port the forwarder listens on, and the PORT the template sets. The https: prefix makes the forwarder terminate TLS with a certificate for its own machine name, which is why HTTPS certificates must be enabled on your tailnet.

Click Deploy in the top bar to apply the change.

Copy the forwarder domain

Open the Machines page in the Tailscale admin console. The forwarder appears there on its own once it starts with a valid auth key.

Click the machine to open its page. Under Machine Details, copy the Full domain value. That is the host you use to open the UI.

Open the Studio URL

Take the full domain you copied, add https:// in front and :4983 at the end, then open it in your browser.

https://<full-domain>:4983
IMPORTANT

Make sure you specify https:// - opening the URL via http:// will not work, and some browsers might redirect you to http:// if you omit the scheme.

Drizzle Studio asks for a passcode on first use.

Open the Drizzle Studio service in Railway, go to the Variables tab, and copy the value of PASSCODE. Paste it into the prompt.

Check the connection

Drizzle Studio opens on the database from DATABASE_URL, so there is nothing to connect by hand. It has no tables yet, because you have not applied a schema to it. The next section shows how to create them.

Next steps

“Connect Drizzle Kit to a private Railway database with Tailscale” tutorial points drizzle-kit at the same forwarder, so you can generate and apply migrations from your machine, again without public access. Your forwarder is already running, so you only need the last section of that tutorial.

Troubleshooting

The URL does not open

The URL does not open. Open the Studio/Gateway service in Railway, read its deploy logs, and find the line it prints on start. Drizzle Gateway prints:

Drizzle Gateway listening on http://0.0.0.0:4983, store at /app

The port on that line must match the target port in CONNECTION_MAPPING_02. Both browsers listen on the port in PORT, so if the two disagree, set PORT back to 4983 on the service and redeploy.

If the line is missing, the container is not serving at all. The forwarder then reports the target it could not reach:

[ERRO] failed to forward connection source_port=4983
  target_addr="drizzle-studio.railway.internal" target_port=4983
  err="failed to dial target: dial tcp 10.x.x.x:4983: connect: connection refused"

connection refused means the address is right and nothing is listening on that port. If the log instead shows an empty or literal ${{...}} target address, the reference variable did not resolve, so check the service name in your mapping.

The browser shows a certificate warning

The browser shows a certificate warning. The certificate covers the machine’s full domain. Open the Full domain value from the machine page, not the short name. Check that HTTPS certificates are enabled on the DNS page of the Tailscale admin console.

The forwarder is not in the Machines list

The forwarder is not in the Machines list. The auth key expired, or a key that was not reusable had already been used. Generate a new reusable key, update TS_AUTHKEY on the forwarder service, and redeploy.

The mapping variable is ignored

The mapping variable is ignored. Confirm the exact variable name on the forwarder service. The template declares CONNECTION_MAPPING_01, with a leading zero. Some older guides write CONNECTION_MAPPING_1. Use the form the config panel shows you, and keep the numbers unique.