Welcome to Limitless Trader Lab

Day 2 of 7 · Cohort intensive · 60 minutes

Deploy infrastructure.

Before any bot code, before any control panel, you need a service running on the internet with a URL you can hit from your phone. Today: deploy a hello-world FastAPI to Railway, mount a persistent volume, set env vars via CLI, and bind a public domain. Sixty minutes. Nothing trades yet. Tomorrow’s panel lives on top of this.

60 minutes 1 deliverable #trader-lab · [D2]

Today you’ll learn

You’ll learn how to deploy a hello-world FastAPI service to Railway, set env vars via the CLI, mount a persistent volume at /app/data, attach a custom domain, and confirm the whole thing works by opening the URL on your phone. This is the substrate the rest of the cohort lives on top of: tomorrow’s panel mounts to the same volume, Day 4’s first programmatic fill writes there, Day 6’s Claude loop runs there.

Section 01

Why deploy first.

Build the bot first and deployment becomes a Day 6 fire-drill, exactly when you have the least slack. So we invert the order: ship the infrastructure today, empty, before there’s a bot to break against it. Today the host exists, the volume exists, the URL exists. Tomorrow you drop a panel onto it. Day 4 the first real fill lands in that panel, on your phone, in seconds. The deploy story is the easy story when you do it before there’s anything to lose. Sixty minutes from now, you have a service answering JSON on the public internet from a URL you can text to a friend.

Off the laptop

Code that needs your laptop on isn’t a bot. The host runs without you, today is when that becomes true.

Persistent volume

Containers reset on every redeploy. The volume at /app/data survives, that’s where logs, fills, and panel state live for the rest of the week.

Phone-friendly URL

Public domain you can hit from anywhere. The receipt is the JSON in a mobile browser tab. Tomorrow’s panel uses the same URL.

API Academy · Module 01 · Infrastructure deep-dive

Section 02

Set up Railway.

Railway is the host we’ll deploy to today. Think of it as “a place on the internet that runs your code for you.” You hand it a folder of Python files, it builds a container, runs them, and gives you back a public URL. No servers to manage, no SSH, no nginx. The free / hobby tier covers the whole cohort week, you only pay if you scale beyond it later. Create the account now so it’s ready when the code is.

1

You are now: creating a Railway account

Open railway.com in a new tab and click Login. Sign in with GitHub (recommended) or email.

GitHub sign-in is one click and means you don’t need to remember a separate password. Email works too, you’ll just need to verify the address. Either way, you land on the Railway dashboard, that’s where your project will appear after the CLI deploy in Section 04.

2

You are now: confirming the free tier

Railway gives every new account a trial credit. No card needed to finish today.

If a card prompt appears, you can dismiss it. Today’s deploy fits inside the trial. We’ll talk about plan limits in Day 7 once you know what your service actually uses.

Section 03

The hello-world app.

A 25-line FastAPI app: one route at / that returns the build time and the data dir, one route at /health for the platform’s health check. No bot logic. Just enough to prove the deploy works end-to-end. Tomorrow you mount the panel on top.

1

You are now: creating the project folder

Make a fresh folder for this week’s deploy and step into it.

Run in terminal
$ mkdir limitless-lab $ cd limitless-lab

Same folder you’ll use the rest of the week. Day 3 adds the panel files here, Day 4 adds the order scripts, Day 6 wires Claude. Git isn’t required, the cohort doesn’t depend on it. If you have Git installed and want version history, git init in this folder is a one-line add anytime this week. (If git isn’t recognized when you try, install from git-scm.com/downloads; not a Day 2 blocker.)

2

You are now: writing the hello-world app

Save this as app.py in the new folder. Twenty-five lines.

app.py
# app.py, Day 2 hello-world. Tomorrow’s panel mounts on top. import os from datetime import datetime, timezone from pathlib import Path from fastapi import FastAPI DATA_DIR = Path(os.environ.get("ACADEMY_DATA_DIR", "./data")) DATA_DIR.mkdir(parents=True, exist_ok=True) BOOTED_AT = datetime.now(timezone.utc).isoformat() app = FastAPI(title="Limitless Lab") @app.get("/") def root(): return { "service": "limitless-lab", "booted_at": BOOTED_AT, "data_dir": str(DATA_DIR), "data_dir_writable": os.access(DATA_DIR, os.W_OK), } @app.get("/health") def health(): return {"status": "ok"}

You also need two tiny companion files in the same limitless-lab/ folder. They’re short, but Railway needs both, the first tells it which Python packages to install, the second tells it how to start your app.

3

You are now: writing requirements.txt

Create a new file named requirements.txt (exact name, no extension change) in the same folder as app.py. Paste the two lines below and save.

requirements.txt
fastapi uvicorn[standard]

That’s the entire file, two lines, no commas, no quotes, no version pins for the cohort. Railway runs pip install -r requirements.txt on every deploy to install whatever’s listed here.

4

You are now: writing the Procfile

Create another new file in the same folder named Procfile, exact spelling, capital P, no extension at all (not .txt, not .md). Paste the single line below and save.

Procfile
web: uvicorn app:app --host 0.0.0.0 --port $PORT

One line, no quotes around it. This tells Railway: “run a web process by starting uvicorn against the app object inside app.py, listening on every interface, on whatever port Railway assigns.” If your editor insists on adding .txt, save it anyway then rename in your file explorer to strip the extension, the deploy will fail if the file is named Procfile.txt.

5

You are now: smoke-testing locally

Make sure it starts on your laptop before you push to Railway.

Run in terminal
$ pip install -r requirements.txt $ uvicorn app:app --port 8080 # In another terminal: $ curl http://localhost:8080/ {"service":"limitless-lab","booted_at":"2026-...","data_dir":"./data","data_dir_writable":true}

If you see the JSON, you’re ready to deploy. If you see import errors, paste them into Claude: “Got this error running uvicorn: [paste]. Help me debug.”

Section 04

Five Railway commands.

Railway is the lowest-friction host for a Python service. Five commands and you have a public URL. The first one (railway login) is interactive, the rest run unattended. Free / hobby tier is plenty for the cohort week; verify Railway’s current limits in their dashboard before you commit beyond Day 7.

1

You are now: installing the Railway CLI

macOS: brew install railway. Anywhere with Node: npm i -g @railway/cli. Windows: download the MSI from docs.railway.com/guides/cli.

2

You are now: deploying

From inside limitless-lab/:

Run in terminal
# 1. Log in (opens a browser tab; if it hangs, use --browserless) $ railway login # 2. Create the project $ railway init # name it limitless-lab # 3. First deploy (uploads + builds + runs) $ railway up # 4. Set env vars (PANEL_TOKEN ships with Day 3; pre-stage it now) # macOS / Linux: $ railway variables --set "PANEL_TOKEN=$(openssl rand -hex 32)" # Windows PowerShell (no openssl by default, generate hex via .NET): PS> $token = -join ((48..57) + (97..102) | Get-Random -Count 64 | ForEach-Object { [char]$_ }) PS> railway variables --set "PANEL_TOKEN=$token" # 5. Bind a public domain $ railway domain limitless-lab-production.up.railway.app

If railway login hangs in a sandboxed terminal, use railway login --browserless, it prints a URL + code to paste.

3

You are now: hitting the URL

Open the domain Railway gave you in your laptop browser. You should see the JSON from /, service name, boot timestamp, data dir.

If you see a build error or a 502, run railway logs and paste the last 30 lines into Claude. The fix is usually one of: missing requirements.txt, wrong port binding (must use $PORT), or a typo in the Procfile.

Section 05

Mount the volume.

Every redeploy gives you a fresh container with a fresh filesystem. Anything written during one deploy is gone after the next push. The fix is a volume, a chunk of storage that survives redeploys, mounted at /app/data. Day 3’s panel reads seed JSON from here. Day 4’s first fill writes here. Day 6’s Claude loop logs here.

1

You are now: provisioning the volume

From the Railway dashboard: Service → Volumes → New. Mount path /app/data. Size 1 GB (more than enough for the cohort). Or via CLI on newer versions:

Run in terminal
$ railway volume add --mount-path /app/data --size 1 $ railway variables --set "ACADEMY_DATA_DIR=/app/data"
2

You are now: proving the volume survives

Touch a file on the volume, redeploy, read it back. If it’s still there, the volume is wired correctly.

Run in terminal
$ railway run "echo first-deploy > /app/data/touch.txt" $ railway up # redeploy $ railway run "cat /app/data/touch.txt" # should print: first-deploy

If the file is gone, the volume isn’t mounted at /app/data. Recheck the dashboard, mount path matters more than the volume name.

3

You are now: confirming the env var lands in code

Hit your URL again. The data_dir field in the JSON should now read /app/data (not ./data) and data_dir_writable should be true.

If data_dir_writable is false, the volume isn’t mounted to the path the env var points at. Same fix as above.

Section 06

Open it on your phone.

The deploy isn’t real until you can hit it from a device that isn’t your laptop. Pull out your phone, type the URL, see the JSON. That’s the moment the rest of the cohort cashes in on. The panel tomorrow lands at the same URL; the kill-switch on Day 7 is one tap away from this same address.

1

You are now: opening the URL on your phone

Same domain Railway gave you. Type it (or text it to yourself). You should see the same JSON your laptop browser shows.

Bookmark it. Pin it to your home screen. From Day 3 onwards, this is the muscle memory: phone in hand, panel in tab, bot in the air.

2

You are now: screenshotting it

Phone screenshot of the JSON in a mobile browser. Crop to remove anything sensitive. The screenshot is the deliverable.

Section 07

Today’s deliverable.

A phone screenshot of a JSON URL is the cohort’s permanent receipt that you shipped infrastructure before you shipped a bot. Most builders never get past local development. You did, on Day 2 of week 1. Post the proof.

Day 2 · Deliverable

Post in #trader-lab.

Phone screenshot of your deployed URL showing the JSON response (service, data_dir, data_dir_writable: true).

Post it in #trader-lab with a [D2] tag in the caption so the cohort can scan back to today’s posts.

Caption: “[D2] Day 2 done. Host: Railway. Volume mounted at /app/data. URL: [your domain]. Snags: [none / brief].”

Coach pins one or two clean deploys as exemplars. If you got stuck on the $PORT binding or the volume mount, post that too, the cohort learns more from the snags than the wins.

Day 2 complete

Service in the air.

A FastAPI service is running on Railway with a public domain, a persistent volume mounted at /app/data, and env vars set via the CLI. The phone test is the receipt.

01

A 25-line app.py + requirements.txt + Procfile on your laptop and pushed to a Railway project, the substrate the rest of the week mounts on top of.

02

A persistent volume at /app/data that survived a redeploy, addressed in code via ACADEMY_DATA_DIR. Day 3 seeds it with panel data; Day 4 writes the first real fill there.

03

A phone-bookmarkable URL posted in #trader-lab with a [D2] tag, the same address tomorrow’s panel renders to.

Tomorrow: Drop the control panel on top of this infrastructure. Bonus pack drops mid-day, coach pins the link in #trader-lab. Mid-week live Google Meet at the time pinned in your acceptance email: coach demos the panel against seed data, runs an intervention drill (kill switch from phone), and unblocks anyone stuck.

Day 3 unlocks tomorrow at 9am local · bonus pack drops mid-morning · live call mid-week