Avatar of Faraz PatankarFaraz Patankar

Working with NX, Railway and CI/CD

Railway has first-party support for NX monorepos through Nixpacks, users may instead want to opt for GitHub Actions in order to only create deployments for the affected services whenever they push a change.

In this article we will walk you through deploying a sample project on Railway to achieve that exact workflow.

We will use the npm-libs repository by community member: IgnisDa as our sample project. Within this repository, we will deploy the remix-pagination-demo project.

You can start by forking that repository and deploying it within a project on Railway. Once you add the repository, go to the variables section and set the NIXPACKS_NX_APP_NAME variable to remix-pagination-demo.

Configuring the NX app name

Configuring the NX app name

Next, we can head over to the service settings and disable the GitHub trigger as we will be using GitHub actions to automatically trigger deployments for our project.

Disabling the GitHub trigger on Railway

Disabling the GitHub trigger on Railway

Next, create two separate environments. One for our main branch and one for the dev branch and head over to the project settings to create a project token for each environment.

Creating project tokens

Creating project tokens

Copy the project tokens from Railway and add them as repository secrets on GitHub. They should have the following naming scheme:

RAILWAY_TOKEN__<project_name>_<railway_environment>
Repository secrets on GitHub

Repository secrets on GitHub

Now, we need to set-up the workflow within GitHub to trigger a new deploy.

The GitHub action workflow will:

  • Run on every commit to the main and dev branches
  • Test if a particular project has been affected
  • If it has, deploy it to the correct environment on Railway
name: Deploy affected projects

on:
  push:
    branches:
      - main
      - dev

env:
  NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: ./.github/actions/setup
        name: Setup environment

      - name: Set base commit hash
        run: |
          #!/usr/bin/env bash
          commit=${{ github.event.before }}
          if git branch --contains "$commit"; then
            echo "No force push detected, continuing..."
          else
            # get the commit before this one
            commit=$(git log --format="%H" -n 2 | tail -n 1)
          fi
          echo "BASE_COMMIT=$commit" >> $GITHUB_ENV

      - name: Display base commit
        run: echo "$BASE_COMMIT"

      - name: Lint affected
        run: pnpm nx affected --target=lint --parallel=6 --base=$BASE_COMMIT --head=HEAD

      - name: Build affected
        run: pnpm nx affected --target=build --parallel=6 --base=$BASE_COMMIT --head=HEAD

      - name: Dump access secrets into file
        run: |
          mkdir -p ./dist
          echo '${{ toJSON(secrets) }}' >> ./dist/secrets.json

      - name: Deploy affected
        run: ./deployment/railway-trigger.sh

The main steps to note here are the:

  • Dump access secrets to file step which converts the secrets into json format and dumps them into a file. This can be a potentially exploitable step if used in public repositories, so please make sure you take the appropriate cautions.
  • Deploy affected step which triggers the railway-trigger.sh script shared below.
    • #!/usr/bin/env bash
      
      set -euo pipefail
      
      RAILWAY_BINARY="/tmp/railway"
      RAILWAY_VERSION="1.8.4"
      
      # Install the Railway CLI
      VERSION="$RAILWAY_VERSION" INSTALL_DIR="$RAILWAY_BINARY" sh -c "$(curl -sSL https://raw.githubusercontent.com/railwayapp/cli/master/install.sh)"
      $RAILWAY_BINARY version
      
      affected_projects=$(pnpm nx print-affected --base="$BASE_COMMIT" --head=HEAD --select=projects --type='app')
      for project in ${affected_projects//,/ }
      do
          echo "Processing: '$project'..."
          # convert hyphens to underscores
          parameterized_name=$(echo $project | tr '-' '_')
          branch_name="$(git branch --show-current)"
          # this is the key by which the required token is present in the secrets file
          env_variable=RAILWAY_TOKEN__"$parameterized_name"_"$branch_name"
          # convert the whole thing to upper case
          final_env_variable=$(echo "$env_variable" | awk '{print toupper($0)}')
          # get the actual value of the token from the secrets file
          railway_token="$(jq -r ."$final_env_variable" ./dist/secrets.json)"
          # trigger the deploy
          RAILWAY_TOKEN="$railway_token" $RAILWAY_BINARY up --detach --verbose
      done

      Now there is a lot going on above.

      The above script:

    • Downloads the Railway CLI binary
    • Finds all the deployable projects that were also affected by the changes pushed
    • Does some bash gymnastics to find the correct RAILWAY_TOKEN
    • Sets the RAILWAY_TOKEN environment variable
    • Triggers a deployment on Railway
    • đź’ˇ
      The --detach flag is used here because building containers might take a lot of time which could eat up free Github Actions quota provided. You can remove it if your project allows it.

With that, we have set up CI/CD to deploy an NX monorepo to Railway using GitHub actions. You should be able to use this as a reference to deploy your own NX monorepos. If you run into any issues, feel free to reach out to us on our Discord.

This post is a community post thanks to IgnisDa’s. This is based off of a post on their own blog. Click here to read the original post!