Deploying Statusphere to Railway from Tangled

This is the second entry in a multi-part series about deploying the statusphere-react project to Railway, so you can host your first Atproto app!

1. Deploying statusphere-react to Railway via CLI

2. Automating Railway deployments via Tangled/Spindle (you're here)

Unfortunately, there isn't a way via GUI at time of writing to automatically deploy your Atproto app to Railway from Tangled, so we have to be a little hands-on. If you've already followed Part 1, you're in a good place.

Otherwise, before you start, you need to make sure you have a Railway Project created. We cover that in Part 1, and it isn't specific to statusphere-react -- it can be used with virtually any project!

0. Understand the goal

Before I guide you blindly into the unknown, it's important to understand what we're going to accomplish, and how we're going to do it.

Here's our goal: Every time we push changes to our own version of statusphere-react, we want to automatically apply those changes to our Railway deployment.

To accomplish this, we are going to leverage Tangled's excellent Spindle service to deploy our code. You can think of Spindle like GitHub Actions, if you're familiar -- or perhaps Travis or Jenkins, for the wizened.

There are three components to accomplishing this goal:

1. A YAML file, called a Workflow, containing our deployment commands

2. Environment variables, to tell the Railway CLI in our workflow where to deploy and who to deploy as

3. A Spindle, which will execute our Workflow

This tutorial will guide you through getting each of these components set up, in reverse order. Let's go!

1. Select a Spindle

Unlike GitHub Actions, Spindles can be self-hosted, so Tangled doesn't assume where you want to run your workflows. So, before we can do any deployments, we need to tell Tangled which Spindle to use!

As I'm assuming you're not the self-hosting type (if you are, more power to you!), we're going to use Tangled's generously-provided public Spindle. You can select it with the following steps:

1. Click on "settings"

2. Click on "pipelines"

3. Click on the dropdown under "spindle"

4. Select "spindle.tangled.sh" from the list

5. Click the checkmark button next to the dropdown to save your changes

For the visually-inclined, here's a screenshot of what you're looking for:

2. Create an API Token

As we'll be running Railway CLI commands in a remote environment, we won't be able to use railway login. Instead, we'll need to create an API token, and make it available to the Spindle as RAILWAY_TOKEN. This is called a Project Token .

You'll have to do this from from your Railway project dashboard. If you're coming in hot after Part 1, you can run the following from your terminal in your project directory:

railway open

However you manage to get there, you can create a token in Settings > Tokens. Go ahead and give your token a name -- something like "tangled" so you can remember what you created it for -- and click Create. You don't need to touch the environment dropdown, because we only have a production environment at this time.

Here's another little screenshot roadmap for your clicks:

Copy your token to your clipboard, then head back over in Tangled.

3. Add RAILWAY_TOKEN to Tangled

In our repository, we'll set our token as a Secret. Secrets are provided as environment variables to our Workflows whenever they run.

Right below where we selected our Spindle, you can click on the "+ add secret" button. You'll see a dialog like so:

Remember to name your secret RAILWAY_TOKEN, paste your token, and then click "+ add".

4. Get our Service ID

There's one other value we need from Railway to successfully deploy our project. When we deployed our project the first time, Railway created a Service for it. Since we want to replace this Service instead of deploying a new one on each push, we'll need to instruct Railway accordingly.

Getting your Service ID is slightly trickier than a Project Token. Follow these steps:

1. Go to your project's "Architecture" page

2. Select your Service

3. Navigate to your Service's "Variables" tab

4. Expand "8 Railway Provided Variables available"

5. Copy the value of RAILWAY_SERVICE_ID to your clipboard

Again, here's a screenshot to help you out:

5. Add RAILWAY_SERVICE_ID to Tangled

Again, we'll make this ID available to our Workflow via Secrets. Repeating what we did in Step 3, add RAILWAY_SERVICE_ID to and paste in your Service ID.

6. Add our Spindle Workflow

Okay, the stage is set:

We've enabled workflows by selecting a Spindle

We've set our environment up for Railway CLI to act on our behalf and target the correct Service

Now, all we need to do is add our Workflow, which tells the Spindle what to do! To do that, we need to provide a YAML file with our required packages and steps.

Create a file at .tangled/workflows/deploy.yml, and paste in the following contents:

when:
- event: ["push"]
branch: ["main"]

engine: "nixery"

dependencies:
nixpkgs:
- rustup
- gcc

steps:
- name: Install Rust toolchain
command: rustup default stable

- name: Install Railway CLI
command: cargo install railwayapp --locked

- name: Link `railway` executable
command: ln -s /tangled/home/.cargo/bin/railway /bin/railway

- name: Deploy to Railway
command: railway up --ci --service=$RAILWAY_SERVICE_ID

6.5. Digression

If you're feeling results-oriented, you can skip this.

Depending on your level of software development and/or Linux experience, you might look at the workflow in the previous step and be a bit puzzled. "Isn't the Railway CLI already packaged for Nix?" You might ask.

And you're right -- it is. My first attempt actually looked something like this:

# This workflow doesn't... work, at time of writing
when:
- event: ["push"]
branch: ["main"]

engine: "nixery"

dependencies:
nixpkgs:
- railway

steps:
- name: Deploy to Railway
command: railway up --ci --service=$RAILWAY_SERVICE_ID

I hoped it would just be that easy, but for some reason, I could not get the Railway CLI within a Nixery container to read the required RAILWAY_TOKEN environment variable. It seemed to work fine in nixOS, albeit with version 4.10 from nixpkgs-unstable. The only way I could make it work within a Spindle workflow was by compiling the Railway CLI from scratch via Cargo.

If you're more Nix proficient than I and would like to take a look, I will happily update this tutorial if you can figure out why and/or what I can do to simplify this workflow!

7. Commit and push!

The stage is set; now we just need to trigger our workflow. Save your new YAML file, make a Git commit, and then push it up to Tangled. If you head over to the "pipelines" tab in your hosted repository, you should see your new workflow running!

Once your workflow completes -- assuming it builds without any errors -- you should be able to preview your changes at your deployment URL.

At this point, you're all set to push changes to your statusphere and have them reflected by your Railway deployment.

What's next?

What we've accomplished in this tutorial is setting up a very simple version of Continuous Deployment for your Atproto app. As you're just starting out, this is a huge time saver if you want to see results!

In the long term, however, pushing updates straight to production isn't the best idea. You might push a devastating bug by mistake and have to deal with the consequences. Or, you might want to test out a feature without forcing it upon all of your users.

Here are a couple of ways you might save yourself some headache as you grow your Atproto app:

Gate your Railway deployment behind a test suite. This will help you catch bugs before you expose them to your users.

Set up a Dev environment in Railway, and set up Continuous Deployment to that environment from a different Git branch. This gives you a place to test out changes or get feedback from your dedicated users.

The steps from this tutorial can be modified to adopt either of these improvements. If you have any questions, or need help setting up your deployment workflow, feel free to DM or @ me on Bluesky, and I'll see what I can do to give you a hand.

Thanks for reading -- I can't wait to see what you'll build on AT Protocol!