---
title: "Scaling a SaaS application on Railway"
description: "Time to launch, scaling your SaaS on Railway"
date: 2024-12-13T00:00:00.000Z
updated: 2026-10-29T11:13:31.893Z
topics: ["Guides"]
tags: []
url: https://railway.com/guides/scaling-a-saas-application
---

# Scaling a SaaS application on Railway

In the previous [tutorial](https://railway.com/guides/building-a-saas-application), you built a job board SaaS application using [Remix](https://remix.run/), [PostgreSQL](https://www.postgresql.org/), and [Railway](https://railway.com/). You also deployed it to the internet with a custom domain and Cloudflare proxy. In this article, you’ll learn to scale the SaaS to handle high traffic, improve performance, and tackle some of the challenges that come with design choices. You’ll also learn about various techniques to save cost of running your SaaS application, while keeping the user experience on par.

## Continuous deployment

Continuous deployment is an automated process in which code changes are tested, verified, and automatically pushed to the live environment. This ensures that updates or new features are continuously delivered to users without manual intervention, speeding up the development cycle.

Railway automatically deploys the latest commits to the `main` branch of the connected GitHub repository. But as your application grows and you ship more features, you need to ensure that the features are tested on a live environment before serving the customers to ensure they work properly outside your development machine.

To solve this problem, you can use the [Railway environments](https://docs.railway.com/environments) feature to create an isolated environment called **Staging** for your project that you can manage without affecting services in the **production** environment. You can link the **Staging** environment to the `stage` branch. All the new features are first merged into the `stage` branch and tested on the **staging** environment before serving the customer, as shown in the following diagram.

![Deployment pipeline in Railway](https://cms.railway.com/media/01c89a148d6e9608bed412b3ea7ff8136facd0024590e3eb421cd226634d2cb3.png)

To implement this solution, follow these steps:

- In your Git project, create a new branch named `stage` from the `main` branch.
- Push the `stage` branch to GitHub.
- In your Railway dashboard, go to your project page.
- Click **production > New Environment**: New Environment dialog opens.

![Creating a new environment in Railway](https://cms.railway.com/media/1de06c73d7a91b04ca3a5f010a033970626fd021cdd1c1fac84e8b2883d70c34.png)

- In the **Name** field, enter staging.
- Select **Duplicate Environment.**
- Click **Create Environment.**
- Click **Deploy.**

This creates a new **staging** environment and sets up the same services as the **production** environment. 

You’ll notice that Railway has automatically imported the environment variables from the **production** environment and replaced the `DATABASE_URL` with the database URL of the PostgreSQL instance of the **staging** environment. Railway has also assigned a public domain name to this environment and similar to the **production** environment you can also add a custom domain to it. 

Now in the **staging** environment

- Go to **job-board >** **Settings > Source > Branch connected to staging** and select `stage` branch.

![Updating the branch in staging](https://cms.railway.com/media/7bb254ec3b0b4492b5949318256fadc3ab47ad38a6b1f053211d9bdc87d50ff1.png)

Railway will automatically deploy your application for every new commit on the connected branch and you don’t have to worry about it because Railway performs zero downtime deployments, which means that the previous version of the application is kept running until the new version passes the health checks. If something goes wrong in a new deployment, you can also perform a one-click rollback to any previously deployed version of the application from the **Deployments** section.

## Handle high traffic

For any new SaaS product, it’s hard to calculate how many resources are required to serve the customers because the traffic patterns are unpredictable. The easiest solution to tackle this is to over-allocate resources, while it is rarely the best decision when you’re not making money off the product. 

There are two ways to scale your application deployment: 

1. **Vertically:** by adding more resources to a single machine.
2. **Horizontally:** by adding more machines that handle requests independently.

[Railway supports both approaches](https://docs.railway.com/deployments/scaling) and even mixes them up quite well for the best experience. 

### Vertical scaling

Railway auto-scales your application resources based on traffic and consumption. This is helpful for both upscaling to handle as many requests as you want and to downscaling when there is not many users for the application. This is all done automatically and you can check the resource usage metrics from **job-board > Metrics** tab to see the historical usage patterns for up to 30 days.

### Horizontal scaling

With Railway you can [add replicas](https://docs.railway.com/deployments/scaling#horizontal-scaling-with-replicas) to your service and distribute the incoming request load between multiple instances. Replicas are also auto-scaled vertically giving it the best of both worlds. Because the job board application does file upload processing which is a long-running task it is a good idea to have multiple instances of the application running.

To add the replica to the **production** environment -

- Go to **job-board > Settings > Replicas.**

Here you can increase the count to any number based on your requirement.

![Adding replicas to the Job Board service](https://cms.railway.com/media/cf77233b26f669e27665a1ef727f19dc82987c2013fcdf9af494d5e26f0f19a4.png)

You’ll notice that Railway doesn’t let you add replicas just yet because the job-board service is using a persistent volume. To tackle this issue, you need to decouple the storage from the job-board service by [adding an external storage to your service](#add-external-storage-for-horizontal-scaling). 

### Global distribution

To improve application performance and user experience of your application, you can also choose a deployment region closer to your primary customers. With Railway you can do so from the **job-board >** **Settings > Region** settings.

You can also use global CDN networks such as [Cloudflare](https://www.cloudflare.com/en-in/) or [AWS Cloudfront](https://aws.amazon.com/cloudfront/) to serve static content faster. More on this in the next section.

## Cost management

When starting a SaaS business, it’s important to regularly gauge the cost of running the application so that you can optimize the code or architecture choices to better use the resources available and keep the cost to a minimum. Railway provides many features out-of-the-box to reduce cost of running your application.

### Cache static content on using Cloudflare

It’s a good practice to cache the static content or public content that changes seldom on the CDN level. By doing so, you reduce the number of requests to your origin server (Railway) and in turn, reduce the resource consumption and load. 

The job details page in this project is a good example of such optimization. Because the job details are supposed to be changed seldom and this gives you the opportunity to leverage HTTP caching.

Remix lets your return route level [headers](https://app.notion.com/p/Scaling-a-SaaS-on-Railway-10d0e4c5456380aa8666e2a6da124949?pvs=21) that you can use to add the `Cache-Control` HTTP header for client-side and CDN level caching.

- Add the following code snippet to the `app/routes/jobs.$jobId.tsx` file. This will cache the job details pages for 3600 seconds or 1 hour.

```tsx
export const headers: HeadersFunction = () => {
  return {
    "Cache-Control": "public, max-age=3600, s-maxage=3600", // cache for 1 hour
  };
};
```

> Because you’ve not implemented cache purge on-page content changes, it’s important to inform your customers about the caching behavior to avoid confusion when they edit the page.
> 

After the application is deployed, visit the page to confirm if the caching is applied using the browser dev tools. 

![Observing caching behavior in DevTools](https://cms.railway.com/media/a6922dbf1d3406efca529a04db79c40162d54da98a72441c922dc2c706332a15.png)

You’ll notice that the response time is still quite high due to latency between the `us-west` region and India. You’ll also see that even though the `cache-control` header is returned in the response, Cloudflare didn’t utilize it. This is because you need to set up caching rules on the CDN level to activate the cache control header usage. This project is using Cloudflare proxy network but the same steps will apply to other CDNs as well.

To set up the cache rule in Cloudflare, follow these steps:

- Open your Cloudflare dashboard, go to **Site > Caching > Cache rules.**
- Click **Create rule.**
- In the **Name** field, enter `cache job details pages`.
- In **If incoming requests match…** section, select **Custom filter expression.**
    - For **Field,** select **URI Full.**
    - For **Operator,** select **wildcard.**
    - For **Value,** enter `https://YOUR_APP_DOMAIN_NAME/jobs/*`  because you only want this rule to apply to the job details page.
- In **Cache eligibility,** select **Eligible for cache**.
- In **Edge TTL,** select **Use cache-control header if present, bypass cache if not.** To cache the page at the CDN level.
- In **Browser TTL,** select **Respect origin TTL.** To cache the page in the user’s browser.
- Click **Save.**

After a few seconds, try to reload the page a few times and you’ll notice that the response is cached and response time also is much lower. You can confirm the cache hit status with the `cf-cache-status` response header.

![Observe new cache behavior in DevTools](https://cms.railway.com/media/4109fd2ce5eb5d14bc44bedfec8cb3c825b362ae8d0813bf65e7fa32f3feb10b.png)

### Save cost on Railway

Railway automatically handles scaling up and down for you, which means you’re never paying more than what your application is using. But there are ways in which you can reduce your costs even more. For example:

- **[App sleeping](https://docs.railway.com/deployments/serverless):** Railway will stop the container after a period of no traffic on your service and when the traffic resumes, the application is restarted automatically. This is a good way to reduce the cost of your secondary environment, in this case, **staging**, because it is only used to test changes and not by the customers.
- **Reduce egress**: It’s also a good idea to use [private networking](https://docs.railway.com/networking/private-networking) between services to avoid extra egress fees. When using distributed storage services, you should use pre-signed Get object URLs instead of downloading and returning the file from the Railway app to avoid double-egress fees: once from the storage provider and then by Railway.

## Add external storage for horizontal scaling

To tackle this issue, you can use a distributed storage service such as [AWS S3](https://aws.amazon.com/pm/serv-s3/?trk=b8b87cd7-09b8-4229-a529-91943319b8f5&sc_channel=ps&ef_id=CjwKCAjwmaO4BhAhEiwA5p4YLxdFyigw731Ap-5JttwysgtBPvc7TUijnw6jtGcE_sw3_tT1WSaH_xoC54IQAvD_BwE:G:s&s_kwcid=AL!4422!3!536324516040!e!!g!!aws%20s3!11539706604!115473954714&gbraid=0AAAAADjHtp_-NtTld_fjfSfwVdeW0PIDS&gclid=CjwKCAjwmaO4BhAhEiwA5p4YLxdFyigw731Ap-5JttwysgtBPvc7TUijnw6jtGcE_sw3_tT1WSaH_xoC54IQAvD_BwE) or [Cloudflare R2](https://developers.cloudflare.com/r2/), but they add up to the cost because of the egress fees. You can instead use an open-source alternative such as [MinIO](https://min.io/), which will also not incur any egress fees because you can deploy it within your Railway project. You can follow along this tutorial using this [GitHub repository](https://github.com/anshumanbhardwaj/job-board/tree/scaling).

To create a MinIO instance in your Railway project, follow these steps:

- Open the project page in your Railway dashboard.
- Click **Create.**
- Click **Template.**
- Search and select **MinIO.**

This will add a MinIO service group with the console and bucket to your project. Click on **Console** and open the console URL in a new browser tab.

![Locating the MinIO console URL](https://cms.railway.com/media/5a934e422905636ca4e87453bea30509fad922ed4bcbbf217fc28d0279db72f6.png)

On the MinIO Console login page, 

- In the username field, enter the value of the `USERNAME` variable from the **Console** service.
- In the password field, enter the value of the `PASSWORD` variable from the **Console** service.

This will open the MinIO console for you.

- In the left navigation menu, click **Buckets** and then click **Create Bucket.**
- Create a bucket called `job-board-prod` for the production environment and another one called `job-board-dev` to use for local development.

> For brevity, this tutorial uses the production environment instance of the MinIO for development but you should set it up locally as well.
> 

![MinIO Console](https://cms.railway.com/media/9a0cd0c31e3a067fc91790ae387901a18458850fbd33ee7706220727e8a11c9a.png)

- Now go to the **Access Keys** in the MinIO Console and click **Create Access Key** to create a new key.
- Copy the credentials and save them in the `.env` file of your local project as follows:

```bash
MINIO_ACCESS_KEY=<paste-the-value-here>
MINIO_SECRET_KEY=<paste-the-value-here>
MINIO_BUCKET=job-board-dev
```

> It’s recommended to apply strict access rules when creating access keys. To learn more about Policy-Based Access Control (PBAC), see [Access Management](https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html).
> 

Now go to your Railway project page

- Select **MinIO Bucket** service.
- From the **Variables** tab, copy the value of the `MINIO_PUBLIC_ENDPOINT` variable and add it to your local `.env` file.

You need to use the public endpoint only to access outside of the Railway network.

```bash
MINIO_ENDPOINT=<paste-the-value-here>
```

MinIO is S3 compatible so you can use it directly with the AWS S3 clients in your application.

- Run the following command in the local project to install the AWS S3 Typescript client

```bash
npm i @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```

- Update the [`app/routes/jobs.$jobId.tsx`](https://github.com/anshumanbhardwaj/job-board/blob/scaling/app/routes/jobs.%24jobId.tsx) file to configure the S3 client,
- Upload the file to MinIO and delete the uploaded file from the server disk.
- Update the resume download code in the [`app/routes/dashboard.$jobId.$applicantId.resume.tsx`](https://github.com/anshumanbhardwaj/job-board/blob/scaling/app/routes/dashboard.%24jobId.%24applicantId.resume.tsx) file, to return a pre-signed URL of the file from MinIO instead of returning it from the local disk.
- Merge the code to the `staging` branch.
- Add the MinIO service to the **staging** environment on Railway.
- Create a new bucket called `job-board-stage`.
- Add the MinIO credentials to the service environment variables.
- Create a new access key for the **staging** environment and update the `BUCKET_NAME` with `job-board-stage`.
- Test the application on staging and merge the `staging` branch into the `main` branch.
- Update the MinIO credentials on the **production** environment and set the `BUCKET_NAME` with `job-board-prod` .

After the deployment finishes your application can now work without persistent volumes, which means you can now add more replicas to your service and horizontally scale the SaaS application. 

To remove the volume, right-click it and select **Delete volume.** Confirm the deletion in the next step.

Now go to **job-board >** **Settings > Replicas,** update the replicas count to `4` and save the changes. You’ve now 4 instances of the **job-board** service running and Railway will automatically distribute the incoming requests to these instances.

![Adding replicas to the Job Board service in Railway](https://cms.railway.com/media/e64844232e27cc9de6839a194d825e1cc444ad0d57543febf0a357a73037e936.png)

## Conclusion

After completing this tutorial, you will have learned about scaling up a SaaS application on the Railway. You also learned about utilizing object storage and HTTP caching for better performance. It is important to understand that when getting starting speed is important to validate the ideas quickly and using services such as Railway helps you as a solopreneur to ship your ideas as fast as possible.