PostgreSQL on Railway: Simple Setup, Honest Costs

· updated

How I connected the oploy.eu Django and Wagtail site to Railway PostgreSQL, configured SSL and persistent connections, checked latency, and monitored the real deployment.

Written in November 2025. Since September 2026 oploy.eu no longer runs on Railway; it moved to a server I run myself. The Railway setup below is how it worked until then, and the costs are snapshots from that time.

The oploy.eu website runs on Django and Wagtail. When I wrote this, its PostgreSQL database was hosted on Railway. The important part is the application connection: Railway supplies one DATABASE_URL, Django parses it in wagtailio/settings/base.py, SSL is required, and persistent connections are kept for 600 seconds.

The screenshots show the service I actually deployed. They are evidence from one small workload, not a generic benchmark or a fixed price quote.

Why this deployment uses Railway

I compared Google Cloud SQL, AWS RDS, Heroku Postgres, Fly.io Postgres, and Railway. For this Wagtail workload, Railway gave me the shortest operational path from a managed PostgreSQL instance to an application connection string. I did not need to provision a VPC, subnet, or database security group.

That was the engineering tradeoff: less network control, but less infrastructure to operate for a small web application.

See the setup in 24 seconds

The short walkthrough shows how little interface work is needed to get a PostgreSQL service running.

Watch the video walkthrough on YouTube

Connect Django with DATABASE_URL

Railway provides a DATABASE_URL containing the host, port, database name, user, and password. In this project, wagtailio/settings/base.py passes that value to dj_database_url.config(), sets ssl_require=True, and uses conn_max_age=600 so Django can reuse connections instead of opening a new one for every request.

DATABASES = {
    "default": dj_database_url.config(
        conn_max_age=600,
        ssl_require=True,
    )
}

The credential belongs in the deployment environment. It should never be committed to Git or copied into an article. A different Django project can use the same pattern, then adjust connection lifetime and pooling for its own traffic.

Measure the deployed database

The snapshots below captured about $0.18 of usage so far and a $0.55 end-of-month resource estimate for this small database. These values are only a timestamped observation from my instance. Workload, storage, plan rules, and Railway pricing can all change the result.

Railway usage panel for the database: $0.1815 so far and $0.55 estimated, with CPU, RAM, egress and volume charts

Railway project cost table: memory, CPU, egress and volume lines adding up to $0.1815

The complete bill matters more than the resource estimate. Railway’s Hobby plan has a $5 monthly minimum with $5 of usage included (still true when I checked in October 2026). In the invoice shown later, the amount due was $6.05 including Dutch VAT. Always check the current plan and pricing rather than treating an old screenshot as a quote.

Region placement changed query latency

When the database was farther from the application and its users, latency was noticeable. Moving it to Railway’s Amsterdam region reduced the network distance and made response time more consistent for this European workload.

For another deployment, place the application and database in nearby regions, then measure the real request path. Provider choice cannot compensate for avoidable network distance.

Railway service settings showing the PostgreSQL database placed in the Amsterdam region

Monitoring and billing are easy to read

The service view puts CPU, memory, network traffic, storage, connections, and logs in one place. The workspace usage view then connects that activity to an estimated cost. It is enough visibility for this project without assembling several dashboards.

Railway Postgres metrics tab: CPU, memory and public network traffic over one hour

The invoice below is the useful reality check. It separates resource lines, the Hobby plan, included usage, and tax, so the total is not confused with the database-only estimate.

Railway invoice: disk, network, vCPU and memory lines, the $5 Hobby plan, $4.06 of included usage, 21% Dutch VAT, $6.05 due

How to repeat this

  1. In Railway, create a PostgreSQL service in the region closest to your application.
  2. Expose its DATABASE_URL to the application service as an environment variable. Do not copy it into code.
  3. In Django:
pip install dj-database-url
import dj_database_url

DATABASES = {
    "default": dj_database_url.config(conn_max_age=600, ssl_require=True)
}
  1. Deploy, open one page, and check the Postgres metrics tab for connections and memory.
  2. Wait for the first invoice and compare it with the usage estimate.

What went wrong

Two things surprised me. First, the database started in a region farther from the application and its users, and the latency was noticeable until I moved it to Amsterdam. Second, a database-only estimate of $0.55 said little about the bill: the invoice shown here was $6.05, mostly the plan minimum and VAT.

When I would use this pattern again

This pattern fits a small application that needs managed PostgreSQL, environment-based configuration, SSL, readable metrics, and a short deployment path.

For regulated workloads, strict availability targets, complex private networking, or large database operations, I would compare backup and recovery guarantees, compliance, connection pooling, support, and high availability before choosing a platform.

← All notes