Skip to content

Repository files navigation

Aspire Hosting for Railway

NuGet License: MIT

Aspire 13.5 hosting so aspire publish, aspire deploy, and aspire destroy can target Railway. Local aspire run stays the normal Aspire model and never needs a Railway token.

Status

Packages are on nuget.org and GitHub Packages. Pack also publishes a GitHub Release. nuget.org uses Trusted Publishing (OIDC, no stored key). Current version: 13.5.4 (from Directory.Build.props). MIT. Pinned to Aspire.Hosting 13.5.4 / net10.0. See CHANGELOG.md.

Packages

Package Role
IntrepidDeveloper.Aspire.Hosting.Railway Compute environment, pipeline, GraphQL client, PublishAsRailwayService
IntrepidDeveloper.Aspire.Hosting.Railway.PostgreSQL AddPostgres locally; PublishAsRailwayPostgres on deploy
IntrepidDeveloper.Aspire.Hosting.Railway.Redis AddRedis locally; PublishAsRailwayRedis on deploy
IntrepidDeveloper.Aspire.Hosting.Railway.Storage AddRailwayBucket: local S3-compatible container; Railway bucket on deploy
IntrepidDeveloper.Aspire.Railway.Storage Client: AddRailwayBucketClient registers IAmazonS3

AppHost extensions live in Aspire.Hosting. Resource types live in Aspire.Hosting.Railway / .PostgreSQL / .Redis / .Storage. Official Postgres and Redis templates are volume-backed, so Railway replicas cannot be used with them.

Quick start

using Aspire.Hosting.Railway;

var builder = DistributedApplication.CreateBuilder(args);

var ghcrUsername = builder.AddParameter("ghcr-username");
var ghcrPassword = builder.AddParameter("ghcr-password", secret: true);
var ghcr = builder.AddContainerRegistry("ghcr", "ghcr.io", "intrepid-developer/playground")
    .WithUsername(ghcrUsername)
    .WithPassword(ghcrPassword);
var railway = builder.AddRailwayEnvironment("railway")
    .WithContainerRegistry(ghcr);

var db = builder.AddPostgres("postgres").PublishAsRailwayPostgres(s =>
{
    s.VolumeBackupDaily = true;
    s.VolumeBackupWeekly = true;
});
var cache = builder.AddRedis("redis").PublishAsRailwayRedis();
var uploads = builder.AddRailwayBucket("uploads");

builder.AddProject<Projects.Api>("api")
    .WithReplicas(2)
    .WithHttpHealthCheck("/health")
    .WithReference(db)
    .WithReference(cache)
    .WithReference(uploads)
    .WaitFor(db)
    .WithExternalHttpEndpoints()
    .PublishAsRailwayService(s =>
    {
        s.Region = RailwayRegion.UsWest2;
        s.Cpu = 1;
        s.MemoryGb = 2;
        s.HealthcheckTimeoutSeconds = 120;
        s.RestartPolicy = RailwayRestartPolicy.OnFailure;
        s.RestartPolicyMaxRetries = 10;
        s.CustomDomains.Add("api.example.com");
    });

builder.Build().Run();

AddContainerRegistry is required (GHCR or Docker Hub; Railway has no registry). Pass owner/repo as the third argument. Aspire marks the registry APIs experimental (ASPIRECOMPUTE003). See Getting started for the rest.

In the API project:

builder.AddNpgsqlDataSource("postgres");
builder.AddRedisClient("redis");
builder.AddRailwayBucketClient("uploads"); // IAmazonS3

Restore from nuget.org. No extra feed or PAT.

dotnet add package IntrepidDeveloper.Aspire.Hosting.Railway
dotnet add package IntrepidDeveloper.Aspire.Hosting.Railway.PostgreSQL
dotnet add package IntrepidDeveloper.Aspire.Hosting.Railway.Redis
dotnet add package IntrepidDeveloper.Aspire.Hosting.Railway.Storage
dotnet add package IntrepidDeveloper.Aspire.Railway.Storage

GitHub Packages feed: see Getting started. Do not commit PATs or packageSourceCredentials.

AppHost (IntrepidDeveloper.Aspire.Hosting.Railway*):

<PackageReference Include="IntrepidDeveloper.Aspire.Hosting.Railway" Version="13.5.4" />
<PackageReference Include="IntrepidDeveloper.Aspire.Hosting.Railway.PostgreSQL" Version="13.5.4" />
<PackageReference Include="IntrepidDeveloper.Aspire.Hosting.Railway.Redis" Version="13.5.4" />
<PackageReference Include="IntrepidDeveloper.Aspire.Hosting.Railway.Storage" Version="13.5.4" />

API / consuming project (AddRailwayBucketClient plus the usual Aspire clients):

<PackageReference Include="IntrepidDeveloper.Aspire.Railway.Storage" Version="13.5.4" />
<PackageReference Include="Aspire.Npgsql" Version="13.5.4" />
<PackageReference Include="Aspire.StackExchange.Redis" Version="13.5.4" />

Auth

Use an account or workspace token. Project tokens cannot call projectCreate.

Where What
AppHost parameter railway-token (Aspire resource names cannot contain underscores)
Local / config RAILWAY_TOKEN
CI RAILWAY_API_TOKEN or RAILWAY_TOKEN
Adopt existing railway-project-id / railway-environment-id, bound from RAILWAY_PROJECT_ID / RAILWAY_ENVIRONMENT_ID

Local aspire run needs no token.

Publish vs deploy

Publish Deploy Destroy
Command aspire publish aspire deploy aspire destroy (--yes / --non-interactive --yes skip the prompt)
Talks to Railway? No Yes (GraphQL) Yes (GraphQL)
Output railway-plan.json plus a .env.example of captured parameter names Created or adopted Railway project, environment, services, templates, buckets Tears down resources this integration created in the mapped Railway environment
Secrets Parameter names and Railway expressions when you use AddParameter. WithEnvironment string literals are written as-is Resolves the token and parameter values in memory; never writes those to the plan or deployment state Same token rules as deploy; never writes secrets

AddRailwayEnvironment is the Railway project (compute environment). The Railway environment name is mapped from Aspire --environment: Production → production, Staging → staging (lowercase). Override with WithRailwayEnvironmentName.

Limits

  • Railway has no image registry. Push to GHCR or Docker Hub, then deploy sets source.image. Private GHCR pulls need WithUsername / WithPassword parameter refs (Railway Pro plan).
  • This integration does not shell out to railway up. Railpack has no .NET support; use an image or a Dockerfile.
  • aspire destroy tears down resources this integration created in the mapped Railway environment (production / staging). Adopted resources (AsExisting(), railway-project-id / railway-environment-id, or a live name match) are skipped. The Railway project is not deleted. Buckets are skipped — public GraphQL has no bucketDelete. This is not in-deploy overlap/drain.
  • PR / ephemeral Railway environments are not in this release.
  • MySQL, MongoDB, and HA / PgBouncer are later. PITR enable is HA-only and is not in this slice.
  • Railway buckets are private. Use S3 credentials or presigned URLs. They are not on private DNS.
Where Settings
Aspire-core WithReplicas, WithHttpHealthCheck, WithExternalHttpEndpoints
PublishAsRailwayService Region / ReplicaRegions, Serverless, Cpu / MemoryGb, HealthcheckTimeoutSeconds, RestartPolicy, StartCommand / PreDeployCommand, OverlapSeconds / DrainingSeconds, CronSchedule, CustomDomains
PublishAsRailwayPostgres VolumeBackupDaily / Weekly / Monthly

Full mapping and constraints (5-minute cron floor, no replicas with volumes, cron vs replicas/serverless, DNS TXT for custom domains, image start is exec form / wrap $PORT) live in Publish and deploy.

Kitchen-sink service: region, CPU/RAM, sleep-when-idle, healthcheck timeout, restart, start / pre-deploy, overlap / drain, and a custom hostname. Image start is exec form — wrap $PORT.

builder.AddProject<Projects.Api>("api")
    .WithReplicas(2)
    .WithHttpHealthCheck("/health")
    .WithComputeEnvironment(railway)
    .PublishAsRailwayService(s =>
    {
        s.Region = RailwayRegion.EuropeWest4;
        s.Cpu = 1;
        s.MemoryGb = 2;
        s.Serverless = true;
        s.HealthcheckTimeoutSeconds = 120;
        s.RestartPolicy = RailwayRestartPolicy.OnFailure;
        s.RestartPolicyMaxRetries = 10;
        s.StartCommand = "/bin/sh -c \"exec dotnet MyApp.dll --urls http://*:$PORT\"";
        s.PreDeployCommand = "dotnet MyApp.dll --migrate";
        s.OverlapSeconds = 60;
        s.DrainingSeconds = 10;
        s.CustomDomains.Add("api.example.com");
    });

Cron worker: five-field crontab, UTC. The service must exit. Do not combine with replicas greater than 1 or Serverless.

builder.AddProject<Projects.Worker>("nightly")
    .PublishAsRailwayService(s =>
    {
        s.CronSchedule = "0 3 * * *"; // 03:00 UTC
    });

Multi-region: ReplicaRegions wins over WithReplicas + Region.

builder.AddProject<Projects.Api>("api")
    .WithComputeEnvironment(railway)
    .PublishAsRailwayService(s =>
    {
        s.ReplicaRegions = new()
        {
            [RailwayRegion.UsWest2] = 2,
            [RailwayRegion.EuropeWest4] = 1
        };
        s.Serverless = false;
    });

Docs

  • Getting started — restore, AppHost, first publish/deploy, token setup
  • Publish and deploy — pipeline, plan vs apply, adopt, staging, images, settings
  • Storage — buckets, local S3Mock, IAmazonS3, connection strings
  • GraphQL — confirmed operations only
  • CHANGELOG.md — preview.11 and later
  • AGENTS.md — contract for coding agents working this repo
  • SECURITY.md — never commit secrets; how to report issues

License

MIT

About

Aspire hosting integrations for publishing and deploying distributed apps to Railway.

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages