//JorgenHoc
← All articles
.NET Hostingβ€’β€’7 min read

Deploy a .NET App to Railway in 10 Minutes

Step-by-step guide to deploying a .NET 8 app on Railway: Dockerfile setup, railway.json config, environment variables, custom domains, Railway CLI, pricing, and honest pros and cons.

#azure#dotnet#cloud#devops

Railway has become the go-to platform for developers who want Heroku-level simplicity with modern infrastructure. For .NET developers, it's the fastest path from a GitHub repo to a running production URL. This guide walks through a complete deployment.

What You Need

  • A .NET 8 ASP.NET Core project on GitHub
  • A Railway account (free at railway.app)
  • Railway CLI (optional but recommended)

Setting Up the Dockerfile

Railway can auto-detect .NET projects, but a Dockerfile gives you full control over the build process. Create one in your project root:

# Dockerfile
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
 
# Copy project file and restore dependencies first (better layer caching)
COPY *.csproj .
RUN dotnet restore
 
# Copy everything else and build
COPY . .
RUN dotnet publish -c Release -o /app/publish
 
# Stage 2: Runtime (much smaller image)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
 
# Non-root user for security
RUN adduser --disabled-password --gecos "" appuser
USER appuser
 
COPY --from=build /app/publish .
 
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
 
ENTRYPOINT ["dotnet", "MyApp.dll"]
πŸ’‘

Railway sets the PORT environment variable automatically. Configure your app to read it: ASPNETCORE_URLS=http://+:$PORT or use Railway's built-in port detection.

For a minimal API that reads PORT correctly:

// Program.cs
var builder = WebApplication.CreateBuilder(args);
 
// Railway sets PORT automatically; fall back to 8080 for local dev
var port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
builder.WebHost.UseUrls($"http://+:{port}");
 
// ... rest of your app configuration

The railway.json Config File

{
  "$schema": "https://railway.app/railway.schema.json",
  "build": {
    "builder": "DOCKERFILE",
    "dockerfilePath": "Dockerfile"
  },
  "deploy": {
    "startCommand": "dotnet MyApp.dll",
    "healthcheckPath": "/health",
    "healthcheckTimeout": 30,
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 3
  }
}

Add a health check endpoint to your app:

// Program.cs
app.MapGet("/health", () => Results.Ok(new { status = "healthy", timestamp = DateTime.UtcNow }));

Deploying from GitHub (No CLI)

  1. Go to railway.app and sign in
  2. Click New Project β†’ Deploy from GitHub repo
  3. Select your repository
  4. Railway detects your Dockerfile and starts building
  5. In ~2 minutes, you have a live URL

That's literally it for a basic deployment.

Railway CLI

The CLI gives you local development, log streaming, and environment management:

# Install
npm install -g @railway/cli
 
# Authenticate
railway login
 
# Link to an existing project (from your project directory)
railway link
 
# Or create a new project
railway init
 
# Deploy the current directory
railway up
 
# Watch logs in real-time
railway logs
 
# Open the running app in browser
railway open

Deploying with the CLI

# One-command deploy
railway up
 
# Deploy a specific service
railway up --service my-api
 
# Deploy and watch logs
railway up && railway logs

Environment Variables

Railway's environment variables are set per service per environment (Production, Staging, etc.):

Via Dashboard

In the Railway dashboard: Service β†’ Variables β†’ Add variable

Via CLI

# Set a variable
railway variables set DATABASE_URL="Server=...;Database=...;"
railway variables set ASPNETCORE_ENVIRONMENT="Production"
railway variables set JWT_SECRET="your-secret-here"
 
# List all variables
railway variables
 
# Delete a variable
railway variables delete OLD_VAR

Accessing in Your .NET App

Railway variables are just environment variables β€” they integrate perfectly with ASP.NET Core configuration:

// appsettings.json values are overridden by environment variables
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// This reads from ConnectionStrings__DefaultConnection env var (note double underscore)
 
// Or read directly
var jwtSecret = builder.Configuration["JWT_SECRET"]
    ?? throw new InvalidOperationException("JWT_SECRET not configured");

Adding a PostgreSQL Database

Railway has a native Postgres service that connects to your app automatically:

# Add Postgres to your project
railway add --plugin postgresql
 
# Railway automatically sets DATABASE_URL in your service's environment

Install the EF Core PostgreSQL provider:

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
// Program.cs β€” read the DATABASE_URL Railway sets
var databaseUrl = builder.Configuration["DATABASE_URL"]
    ?? throw new InvalidOperationException("DATABASE_URL not set");
 
// Parse Railway's postgres:// URL format
var databaseUri = new Uri(databaseUrl);
var userInfo = databaseUri.UserInfo.Split(':');
 
var connectionString = $"Host={databaseUri.Host};Port={databaseUri.Port};" +
                       $"Database={databaseUri.AbsolutePath.TrimStart('/')};" +
                       $"Username={userInfo[0]};Password={userInfo[1]};" +
                       $"SSL Mode=Require;Trust Server Certificate=true";
 
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(connectionString));

Or use the Npgsql connection string builder:

# Railway also provides individual connection variables:
PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD
var connectionString = new NpgsqlConnectionStringBuilder
{
    Host = builder.Configuration["PGHOST"],
    Port = int.Parse(builder.Configuration["PGPORT"] ?? "5432"),
    Database = builder.Configuration["PGDATABASE"],
    Username = builder.Configuration["PGUSER"],
    Password = builder.Configuration["PGPASSWORD"],
    SslMode = SslMode.Require
}.ConnectionString;

Custom Domains

# Add a custom domain via CLI
railway domain --service my-api add www.yourdomain.com
 
# Railway provides the CNAME to point at your domain registrar

Or via dashboard: Service β†’ Settings β†’ Domains β†’ Custom Domain.

Railway provides TLS certificates automatically for all custom domains.

Environments (Staging / Production)

Railway supports multiple environments per project:

# Create a staging environment
railway environment create staging
 
# Deploy to staging
railway up --environment staging
 
# Switch CLI context to staging
railway environment staging

Each environment has its own set of variables, databases, and deployments. Your staging environment can mirror production with a separate database.

Pricing

Railway uses a usage-based pricing model:

PlanMonthly BaseComputeMemory
Trial$5 credit (once)$0.000463/vCPU-minute$0.000231/GB-minute
Hobby$5/month includedSame ratesSame rates
Pro$20/month includedSame ratesSame rates

Typical costs for a small .NET API:

A minimal .NET API using ~0.1 vCPU and 256 MB RAM continuously:

  • Compute: 0.1 Γ— 43,800 min Γ— $0.000463 β‰ˆ $2.03/month
  • Memory: 0.25 Γ— 43,800 min Γ— $0.000231 β‰ˆ $2.53/month
  • Total: ~$4.56/month (within Hobby plan's $5 credit)

Adding Postgres: $0.000231/GB-minute for storage + compute for the DB instance.

πŸ’‘

Most small .NET APIs with a Postgres database run comfortably within Railway's $5/month Hobby plan. Estimate your costs at railway.app/pricing before scaling up.

Automatic Deployments

By default, every push to your main branch triggers a deployment. Configure branch-based deployments:

In the dashboard: Service β†’ Settings β†’ Deployments:

  • Watch Branch: main (or any branch)
  • Root Directory: / or a subdirectory if your .NET project isn't at the repo root

Pros and Cons

Pros

  • Zero config for basics β€” push code, get URL, no YAML manifests
  • Native Git integration β€” auto-deploy on push
  • Excellent DX β€” the dashboard is clean and intuitive
  • Native Postgres, Redis, MySQL β€” one click to add a database
  • Fair pricing β€” usage-based, easy to estimate
  • Custom domains with auto-TLS β€” included for all plans
  • Environment branching β€” staging/production separation built in

Cons

  • No SLA on Hobby plan β€” Pro plan required for uptime guarantees
  • US-centric regions β€” fewer regions than AWS/Azure (though expanding)
  • Limited enterprise features β€” no VPCs, private networking on lower plans
  • Build minutes can add up β€” large .NET solutions with many projects take time
  • No auto-scaling β€” vertical scaling only (upgrade instance size); horizontal scaling is manual

When to Choose Railway

Railway is the right choice when:

  • You're a solo developer or small team
  • You want to deploy quickly without infrastructure expertise
  • Your app is a standard web API + database combination
  • Budget is a concern and you want predictable, low costs
  • You're prototyping or running a side project

Migrate off Railway when:

  • You need compliance guarantees (SOC2, HIPAA with BAA, etc.)
  • You need complex networking (VPCs, private endpoints)
  • You need multi-region active-active deployments
  • Your traffic patterns require sophisticated auto-scaling