{
  "path": "/3m23cshg4e225",
  "site": "at://did:plc:57od6g2ic3e3b3kauctjmo3k/site.standard.publication/3lwagtcm36s2d",
  "$type": "site.standard.document",
  "title": "Deploying Statusphere to Railway from Tangled",
  "content": {
    "$type": "pub.leaflet.content",
    "pages": [
      {
        "$type": "pub.leaflet.pages.linearDocument",
        "blocks": [
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "Saw some discussion today about how to deploy Atproto apps for the less backend/DevOps-inclined. Railway seemed pretty popular, and I've been meaning to try it myself, so let's see what damage we can do."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 20,
                    "byteStart": 13
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                }
              ],
              "plaintext": "The absolute easiest way to deploy your first Atproto app is by using the Railway template @samuel.bsky.team created -- it only requires a couple of clicks!"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "src": "https://railway.com/deploy/atproto-statusphere-app?referralCode=e99Eop",
              "$type": "pub.leaflet.blocks.website",
              "title": "Deploy atproto statusphere app",
              "description": "Deploy atproto statusphere app on Railway with one click, start for free. A minimal demo of an end-to-end atproto application",
              "previewImage": {
                "$type": "blob",
                "ref": {
                  "$link": "bafkreibr4crcylrr5efr7l7tdaq6tz6aonooqh3jddxxf5tojg6zlggi3m"
                },
                "mimeType": "image/png",
                "size": 26907
              }
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "This is the first in a multi-part series:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "1. Deploying statusphere-react to Railway via CLI (you're here)"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 53,
                    "byteStart": 3
                  },
                  "features": [
                    {
                      "uri": "https://graham.leaflet.pub/3m2uniaiph22c",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "2. Automating Railway deployments via Tangled/Spindle"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "1. Clone statusphere-react"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "You can either clone the repo as-is for this tutorial, or you can fork it and clone your fork."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "src": "https://tangled.org/@samuel.bsky.team/statusphere-react",
              "$type": "pub.leaflet.blocks.website",
              "title": "@samuel.bsky.team/statusphere-react",
              "description": "the statusphere demo reworked into a vite/react app in a monorepo",
              "previewImage": {
                "$type": "blob",
                "ref": {
                  "$link": "bafkreieuqkru3fza7aow2j5wksutgbsyjhlqaexi5zpgvtismhpuc6afv4"
                },
                "mimeType": "image/png",
                "size": 13900
              }
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "2. Install Railway CLI"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 67,
                    "byteStart": 10
                  },
                  "features": [
                    {
                      "uri": "https://docs.railway.com/guides/cli",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 104,
                    "byteStart": 88
                  },
                  "features": [
                    {
                      "uri": "https://search.nixos.org/packages?channel=25.05&show=railway&query=railway",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "Check the docs for how to install the Railway CLI for your platform. For the Nix folks, there's a nixpkg."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "3. Log in to Railway"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 55,
                    "byteStart": 36
                  },
                  "features": [
                    {
                      "uri": "https://railway.app",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "I'm assuming you've already gone to https://railway.app and made an account."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "Navigate to your statusphere-react directory, and then run the following:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway login",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 28,
                    "byteStart": 14
                  },
                  "features": [
                    {
                      "uri": "https://docs.railway.com/guides/cli#authenticating-with-the-cli",
                      "$type": "pub.leaflet.richtext.facet#link"
                    }
                  ]
                }
              ],
              "plaintext": "Check out the CLI login docs if something goes wrong here."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "4. Create a new project"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 9,
                    "byteStart": 2
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                }
              ],
              "plaintext": "A Project is the top-level resource for organizing your app. Run the following, and give your project a name if you like:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway init",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "5. Create a new service"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 16,
                    "byteStart": 8
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                }
              ],
              "plaintext": "Next, a Service is an instance of your app. We're going to first create an empty service, so we can configure our environment variables later."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "Run the following, and select \"Empty Service\" when prompted. Don't worry about setting variables, or setting a name if you don't want to:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway add",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "6. Create a domain"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "To properly configure our OAuth client, we'll need a domain."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "Railway makes getting a service-specific domain easy; save this value and hold on to it for later:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway domain",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "7. Set up a volume"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "A volume ensures that our database persists between deployments and restarts."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 66,
                    "byteStart": 55
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Let's make one with the following command, and specify /persistent as the mount path when prompted:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway volume add",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "8. Set our environment variables"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "Okay, we should have everything we need now to deploy very own statusphere."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "We can set all of our basics at once:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway variables \\\n  --set NODE_ENV=\"production\" \\\n  --set PORT=\"8080\" \\\n  --set DB_PATH=\"../../../persistent/data.sqlite\"",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 27,
                    "byteStart": 14
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "We'll set our COOKIE_SECRET to a randomly-generated 32-character value:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway variables --set COOKIE_SECRET=$(openssl rand -base64 32)",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [
                {
                  "index": {
                    "byteEnd": 33,
                    "byteStart": 23
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 42,
                    "byteStart": 38
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 88,
                    "byteStart": 84
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 135,
                    "byteStart": 127
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 192,
                    "byteStart": 138
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#italic"
                    }
                  ]
                },
                {
                  "index": {
                    "byteEnd": 195,
                    "byteStart": 194
                  },
                  "features": [
                    {
                      "$type": "pub.leaflet.richtext.facet#code"
                    }
                  ]
                }
              ],
              "plaintext": "Finally, we'll set our PUBLIC_URL and HOST to that domain we generated earlier. For HOST, remove the protocol from the domain (https://). Make sure that neither value contains trailing slashes (/):"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway variables \\\n  --set PUBLIC_URL=\"<your url>\" \\\n  --set HOST=\"<your url minus protocol>\"",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.header",
              "level": 2,
              "facets": [],
              "plaintext": "9. Deploy!"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "Okay, we should be all good to go. Running the following command will deploy our app, and follow along with the logs:"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.code",
              "language": "shellscript",
              "plaintext": "railway up",
              "syntaxHighlightingTheme": "rose-pine"
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": ""
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "That wraps it all up! You should be able to navigate to your domain, sign in to your account, and watch the statuses roll in."
            }
          },
          {
            "$type": "pub.leaflet.pages.linearDocument#block",
            "block": {
              "$type": "pub.leaflet.blocks.text",
              "facets": [],
              "plaintext": "Now that we've done all the setup and heavy lifting, CI/CD on Tangled should be a breeze. I'll update this Leaflet with a link once I'm done."
            }
          }
        ]
      }
    ]
  },
  "bskyPostRef": {
    "cid": "bafyreibbh5ugpjvyvz7mpzjkgrldqopmxw2sapomwwvjravgo2uttwcuky",
    "uri": "at://did:plc:57od6g2ic3e3b3kauctjmo3k/app.bsky.feed.post/3m23csnd2as25",
    "commit": {
      "cid": "bafyreib4fpdu3swnaoto7dsijprxfksaclllv5a4xsabtc7dja3ey4n5fa",
      "rev": "3m23csnfhrc2l"
    },
    "validationStatus": "valid"
  },
  "description": "Part 1: Local machine to Railway",
  "publishedAt": "2025-09-30T19:59:33.448Z"
}