Drizzle Studio and Gateway on Railway with Tailscale

Deploy Drizzle Studio or Drizzle Gateway on Railway so that it’s reachable only on your tailnet.

If you already run Drizzle Studio or Drizzle Gateway on Railway, follow Add Tailscale to Drizzle Studio or Gateway on Railway instead.

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.

  • MagicDNS is on in your tailnet, because the address and the machine name depend on it. Tailnets created on or after 2022-10-20 have it on by default. If your tailnet is older, check the DNS page.

How it works

The template creates two services. One runs the database UI, either Drizzle Studio or Drizzle Gateway. Next to it runs a Tailscale service that joins your tailnet as its own machine and forwards its port to the database UI over Railway’s private network. Traffic goes from your device over the tailnet to that machine, and from there to the database UI.

The database UI reaches your database over Railway’s private network. The database keeps its *.railway.internal domain and stays unreachable from anywhere else.

A web browser reaches the Tailscale service over the tailnet. Inside the Railway private network, the Tailscale service reaches Drizzle Studio or Gateway, which reaches Postgres. A web browser reaches the Tailscale service over the tailnet. Inside the Railway private network, the Tailscale service reaches Drizzle Studio or Gateway, which reaches Postgres.

Set up the Railway project

Create a project with a PostgreSQL database that has no public access. If your project already has a database, skip to Choose Gateway or Studio.

Create a Railway project

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

Railway create menu with Database selected

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 ( K CtrlK ) and search for PostgreSQL.

Railway database menu with PostgreSQL selected

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.

Postgres Settings tab with no public access and the private domain postgres.railway.internal

Choose Gateway or Studio

Drizzle has two database UIs, and a Tailscale template for each.

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 give to other people
StorageNoneA volume holding saved connections

Pick Drizzle Studio when everyone on your tailnet should reach a single database and nothing else.

Pick Drizzle Gateway when you need to manage several databases, or give specific people access to specific databases. A Gateway user who knows MASTERPASS can add a connection to any database the service can reach.

Drizzle Studio
Drizzle Gateway

Open the template

Press the button below to open the Drizzle Studio with Tailscale template.

Don’t deploy it right away. Set the variables first.

Deploy on Railway

Open the Deploy to dropdown and select the project that holds your database.

Drizzle Studio with Tailscale deploy form with DATABASE_URL set to the Postgres reference

Set the variables

VariableWhat to set
DATABASE_URLRequired. The field is empty. Pick your database service from the list, then pick DATABASE_URL. For a service named Postgres the field then reads ${{Postgres.DATABASE_URL}}.
PASSCODELeave the generated value, or type your own passcode
TS_FORWARD_DATABASEOptional. If you also want to reach the database itself from your tailnet, for example with drizzle-kit, pick RAILWAY_PRIVATE_DOMAIN of your database service from the list. Port 5432 on the machine then forwards to your database over Railway’s private network. You can also set it later on the Tailscale service’s Variables tab.
TS_HOSTNAMELeave drizzle-studio, or pick another name for the machine

Some variables, such as TS_HOSTNAME, are in the collapsed Pre-Configured Environment Variables section of the card. Expand it to see or change them.

Deploy the template

On each service card, click Save Config. Then click Review and deploy. Railway opens your project with the template in a side panel. Click Deploy Template there.

Project canvas with the Tailscale and Drizzle Studio services next to Postgres

Sign in from the deploy log

Open the Tailscale service, then its Deployments tab, and read the deploy log. The last lines of the log hold a sign-in link:

This service is not on your tailnet yet. Open this link and sign in
with the account that owns your tailnet:
  https://login.tailscale.com/a/xxxxxxxxxxxx

Open the link and sign in. Anyone who opens that link first gets this machine on their tailnet, so do not forward it. The log is visible only to members of your Railway project.

Tailscale deploy log with the sign-in message and a hidden link

If your tailnet requires device approval, the log says so and the machine waits until you approve it on the Machines page.

Read the address from the deploy log

A few seconds after you sign in, the same log prints where to go:

joined tailnet: example.com
4983: ready
open https://drizzle-studio.example-com.ts.net from any device on your tailnet

Tailscale deploy log with the joined tailnet, open and 4983 ready lines

Check that the tailnet name is yours. If it is not, someone else opened the sign-in link first, and their tailnet can now reach this service. To take the service back:

  1. On the project canvas, click the volume under the Tailscale service, open its Settings tab, and click Wipe volume.
  2. Type the confirmation text and click Wipe. Railway deletes the machine’s key and redeploys the service.
  3. Open the sign-in link from the new deploy log yourself.
  4. Change PASSCODE on the Drizzle Studio service. If you set TS_FORWARD_DATABASE, also change the database user’s password.

If your tailnet already has a machine with that name, the machine name ends in -1, -2 and so on. Use the address the log prints.

If HTTPS certificates are off in your tailnet, the address starts with http:// and ends in :4983 instead. Tailscale encrypts the connection either way. To get the https:// address, enable HTTPS Certificates on the DNS page.

If you set TS_FORWARD_DATABASE, the log also prints 5432: ready.

Open Drizzle Studio

Open that address in your web browser. On the first https:// visit, the Tailscale service still fetches its certificate, so the page can fail or load slowly for about two minutes. If that happens, reload the page.

Drizzle Studio asks for a passcode on first use.

Drizzle Studio passcode prompt with a hint to check PASSCODE in Railway container variables

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

Drizzle Studio Variables tab with PASSCODE marked

Check the connection

Drizzle Studio opens on the database from DATABASE_URL.

Drizzle Studio on the tailnet with the posts table open

Use other ports

By default, TS_FORWARD_BROWSER uses port 4983 and TS_FORWARD_DATABASE uses port 5432, on the machine and on the service. To use other ports, write the value in the form [port:]host[:port]:

ValuePort on the machinePort on the service
hostThe default portThe default port
host:portportport
source_port:host:target_portsource_porttarget_port

For example, 5433:${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432 forwards port 5433 on the machine to port 5432 of the database service.

If the port on the machine for TS_FORWARD_BROWSER is not 4983, an http:// address in the log ends in that port.

Disable key expiry

A machine that joined by signing in has its key expire on your tailnet’s schedule, 180 days by default. When that happens, the Tailscale service prints a new sign-in link and waits. The address is unreachable until you click it.

To avoid that, open the Machines page once, find the machine, open the menu at the far right of its row, and choose Disable key expiry.

Tailscale Machines page menu with Disable key expiry

If you automate deployments and want no sign-in at all, set TS_AUTHKEY on the Tailscale service:

  • For an auth key, set TS_AUTHKEY to the key.
  • For an OAuth client secret, set TS_AUTHKEY to the secret with ?ephemeral=false&preauthorized=true appended. Without that suffix, Tailscale removes the machine when it disconnects, and the machine waits for approval before it joins. Also set TS_TAGS to a tag you own, for example tag:drizzle. A tagged machine never has its key expire.

The Tailscale service reads TS_AUTHKEY only before the machine joins. If the machine has already joined, remove the machine and wipe the volume first.

Troubleshoot the Tailscale setup

Open the entry that matches what you see.

A new sign-in link appeared in the log

The service restarted, for example after the key expired or the machine was removed. Open the newest link. The link works once.

The log says the machine is waiting for approval

Your tailnet has device approval turned on. Open the Machines page and approve it.

Studio opens but shows no tables

A new database has no tables until you apply a schema. Follow Connect Drizzle Kit to a private Railway database with Tailscale to create them. If your database already has tables, check DATABASE_URL on the Drizzle Studio service’s Variables tab. It can point at a service outside this project, or hold a connection string that does not match your database. Set it to ${{Postgres.DATABASE_URL}}, with your database service name in place of Postgres, and click Deploy.

The address does not open

Check these causes in order:

  • Your device is signed in to another tailnet. Run tailscale status on your device and compare its tailnet with the one the log named.
  • The log printed an http:// address. Use that exact address with the port, because https:// works only when HTTPS certificates are on in your tailnet.
  • The Tailscale service is still fetching its certificate on the first https:// visit. Wait about two minutes and reload the page.
  • MagicDNS is off, so the address does not resolve. Enable MagicDNS on the DNS page.

It worked for months and then stopped

The machine’s key expired. Open the Tailscale service’s deploy log; it is printing a fresh sign-in link. Click it, then disable key expiry on the machine so it does not happen again.

The machine name gained a -1 suffix

Your tailnet already had a machine with this name, for example from an earlier deploy. Tailscale keeps the suffix. If you want the short name, rename the machine on the Machines page. Otherwise, use the address the log prints.

The log prints no open line

TS_FORWARD_BROWSER is empty, or the Tailscale service skipped it. A reference resolves to an empty value when it names a service that was not in the project when the template deployed. A skipped variable has its own line in the log, and the next entry explains that line. Open the Tailscale service’s Variables tab, set TS_FORWARD_BROWSER to ${{"Drizzle Gateway".RAILWAY_PRIVATE_DOMAIN}} (with "Drizzle Studio" for Studio), and click Deploy in the top bar.

The log prints a line that ends in skipped

The line starts with the variable name, then gives the reason:

  • does not match [port:]host[:port]: the value has more than two colons.
  • has an empty target host: the value has a port but no host. A reference that names a service outside your project resolves to an empty host.
  • still contains a Railway reference that did not resolve: Railway did not replace the reference. Check the service name inside it.
  • has a port outside 1-65535: a port is not a number from 1 to 65535.
  • reuses port: an earlier forward already uses that port on the machine.
  • uses port 443, which the https listener owns: pick another port on the machine. The address of the database UI already uses 443 when HTTPS certificates are on.

Fix the value on the Tailscale service’s Variables tab and click Deploy in the top bar.

Next steps