Deploy a Rails App to Railway and Get Preview Environments for Every Pull Request

Tadashi Shigeoka ·  Thu, February 5, 2026

When you open a pull request, you want a preview environment where reviewers can immediately verify changes. Railway offers PR Preview Environments as a built-in feature, automatically creating and destroying isolated environments (including databases) for each Pull Request (PR). No GitHub Actions workflows required.

I deployed a minimal Rails 8.1 + PostgreSQL app to Railway and verified that PR preview environments work end to end.

What is Railway

Railway is a cloud platform that simplifies application deployment and infrastructure management.

FeatureDescription
Auto BuildAutomatically detects build method from source code (Dockerfile, Nixpacks)
PR PreviewAutomatically creates and destroys isolated environments per PR
Visual CanvasGraphically manage connections between services
Built-in DatabasesAdd PostgreSQL, MySQL, Redis, MongoDB with one click
Auto ScalingCPU/RAM scaling and replica management
Pay-as-you-goPay only for what you use. Free tier available

Tech Stack

The tech stack used for this verification.

TechnologyVersion
Ruby3.4.8
Rails8.1.2
PostgreSQL17

Application Structure

A minimal app with a scaffolded Post CRUD.

Routing

Rails.application.routes.draw do
  resources :posts
  get "up" => "rails/health#show", as: :rails_health_check
  root "posts#index"
end
PathMethodDescription
/GETPost list (posts#index)
/postsGETPost list
/posts/newGETNew post form
/posts/:idGETPost detail
/posts/:id/editGETEdit form
/upGETHealth check (200 or 500)

/up is a built-in Rails health check endpoint that returns 200 if the app boots successfully. It can be used directly as Railway’s health check.

Dockerfile

We use the multi-stage Dockerfile generated by Rails 8.1 as-is.

ARG RUBY_VERSION=3.4.8
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
WORKDIR /rails
 
# ... (base packages, jemalloc, environment variables)
 
FROM base AS build
# Install build dependencies, gems, precompile assets
COPY . .
RUN bundle install
RUN bundle exec bootsnap precompile -j 1 app/ lib/
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
 
FROM base
# Non-root user, copy artifacts
COPY --chown=rails:rails --from=build /rails /rails
 
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
EXPOSE 3000
CMD ["./bin/rails", "server"]

The key point is the separation of ENTRYPOINT and CMD. bin/docker-entrypoint automatically runs db:prepare before starting rails server.

#!/bin/bash -e
 
if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then
  ./bin/rails db:prepare
fi
 
exec "${@}"

This mechanism becomes important when configuring Railway, as discussed later.

Deploying to Railway

1. Create railway.json

Add railway.json to the root of your repository.

{
  "$schema": "https://railway.com/railway.schema.json",
  "build": {
    "builder": "DOCKERFILE"
  },
  "deploy": {
    "healthcheckPath": "/up",
    "healthcheckTimeout": 300,
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 5
  }
}
SettingValueDescription
builderDOCKERFILEBuild using Dockerfile
healthcheckPath/upRails health check endpoint
healthcheckTimeout300Health check timeout in seconds
restartPolicyTypeON_FAILURERestart on failure
restartPolicyMaxRetries5Maximum retry count

2. Create a Project in the Dashboard

Follow these steps in the Railway Dashboard:

  1. Create a new project — Connect your GitHub repository
  2. Add PostgreSQL — From the project canvas, select “Add Service” → “PostgreSQL”
  3. Set environment variables — In the app service’s Variables tab, configure the following
VariableValue
DATABASE_URL${{Postgres.DATABASE_URL}}
RAILS_MASTER_KEYValue from config/master.key

For DATABASE_URL, use Railway’s variable reference syntax ${{Postgres.DATABASE_URL}}. This automatically injects the PostgreSQL service’s connection information.

3. Verify the Deployment

When you push to your GitHub repository, Railway automatically builds and deploys. Once deployment completes, you can access your app at a *.up.railway.app domain.

PR Preview Environments

This is the main topic. Railway has a built-in feature that automatically creates isolated preview environments for each PR.

Configuration

Simply enable Enable PR Environments in Project Settings → Environments. No GitHub Actions workflows needed.

How It Works

When a PR is created, Railway automatically performs the following:

StepDescription
1. Create environmentCreate an isolated environment dedicated to the PR
2. Provision DBProvision a new PostgreSQL instance
3. BuildBuild the Dockerfile with the PR branch code
4. DB migrationRun db:prepare (via docker-entrypoint)
5. Issue URLAssign a unique *.up.railway.app URL
6. Notify GitHubPost deployment results as a comment on the PR

When the PR is merged or closed, the preview environment is automatically deleted.

PR Preview in Action

In PR #5, the Railway bot automatically posted the following comment:

Deployed to the rails-preview-demo-pr-5 environment in rails-preview-demo

  • Service: rails-preview-demo — Status: Success
  • Web URL: https://rails-preview-demo-rails-preview-demo-pr-5.up.railway.app

Since a unique URL is issued for each PR, reviewers can verify changes simply by clicking the link. The database is also isolated, so you can safely create test data or perform destructive operations.

Bot PR Environments — A Note for AI Tool Users

When using AI tools like Claude Code or GitHub Copilot to create commits and PRs, Railway classifies them as Bot PRs. By default, preview environments are not created for Bot PRs.

To enable preview environments for AI-generated PRs, enable Bot PR Environments in Project Settings → Environments.

If you forget this, you’ll encounter a situation where preview environments don’t spin up only for PRs created by AI tools.

Gotcha — startCommand Conflicting with docker-entrypoint

Here’s an issue I encountered during testing.

Problem

Initially, I had set startCommand in railway.json.

{
  "deploy": {
    "startCommand": "bin/rails db:prepare && bin/rails server -b ::",
    "healthcheckPath": "/up"
  }
}

With this configuration, production deployment succeeded but preview environment deployment failed.

Cause

Railway passes startCommand as shell arguments to the Dockerfile’s ENTRYPOINT. This means the actual command executed looks like:

/rails/bin/docker-entrypoint "bin/rails db:prepare && bin/rails server -b ::"

docker-entrypoint checks whether the last two arguments are ./bin/rails and server to determine whether to run db:prepare. When startCommand is passed, the arguments become a single string, so the condition doesn’t match and db:prepare is skipped.

Solution

Remove startCommand and let the Dockerfile’s ENTRYPOINT + CMD handle everything. This is the same railway.json shown earlier, with startCommand removed.

  • ENTRYPOINT (docker-entrypoint) detects rails server and runs db:prepare
  • CMD (./bin/rails server) starts Puma
  • Puma binds to 0.0.0.0 by default and reads the PORT environment variable set by Railway

If the Dockerfile is properly configured, there’s no need to specify startCommand on the Railway side. The Dockerfile generated by Rails 8.1 works on Railway as-is.

Summary

With Railway, you can set up PR preview environments for your Rails app with minimal configuration.

  • Only one config file needed: railway.json
  • No GitHub Actions workflows required
  • Isolated environments with their own DB spin up automatically for each PR
  • Environments are automatically deleted on merge/close
  • Don’t forget to enable Bot PR Environments when using AI tools
  • Skip startCommand and let the Dockerfile’s ENTRYPOINT + CMD handle it

Having PR preview environments changes your review workflow. No more pulling branches locally to test — just click the PR link and verify the changes.

That’s all from the Gemba — deploying Rails to Railway and setting up per-PR preview environments.

References