Skip to main content

Overview

Deploying to a cloud platform provides 24/7 availability with all features enabled. This guide covers deploying to Render and Railway, plus general instructions for any platform supporting Python applications.
Cloud deployment enables bank sync, mutual funds, and persistent settings accessible from anywhere.

Platform Comparison

PlatformFree TierAuto-SleepPersistent StorageBest For
Render750 hrs/monthYes (15 min idle)Requires paid tierProduction
Railway$5 free creditNoYesDevelopment
HerokuDeprecated--Not recommended
Fly.io3 VMs freeNoYesGlobal deployment
Important: Most free tiers have auto-sleep. The Unfold binary and database will reset on restart unless you configure persistent storage.

Render Deployment

Render is recommended for production due to its simplicity and reliability.

Prerequisites

1

Create Render Account

Sign up at render.com
2

Prepare Repository

Your repository must be on GitHub, GitLab, or Bitbucket.

Deployment Steps

1

Create Web Service

  1. In Render Dashboard, click NewWeb Service
  2. Connect your repository
  3. Select branch: main
2

Configure Service

Basic Settings:
  • Name: spendwisely-george
  • Environment: Python 3
  • Region: Choose closest to your location
  • Branch: main
Build Settings:
  • Build Command:
    pip install fastapi uvicorn pyyaml requests
    
  • Start Command:
    python server.py
    
3

Configure Environment Variables

Add these in the Environment section:
PORT=10000  # Render default port
PYTHON_VERSION=3.11
Render automatically sets PORT. The server reads it via os.environ.get("PORT", 8000) (server.py:240).
4

Deploy

Click Create Web Service. Render will:
  1. Clone your repository
  2. Install dependencies
  3. Start the server
  4. Provide a public URL: https://spendwisely-george.onrender.com

Persistent Storage on Render

By default, Render’s free tier does not persist files between deploys. Your unfold_config.yaml, db.sqlite, and holdings.json will be lost on restart.
Solutions:
Render’s Persistent Disk feature (requires paid plan):
  1. In service settings, add a Disk
  2. Mount path: /opt/render/project/data
  3. Update server.py to use disk paths:
DATA_DIR = os.environ.get("DATA_DIR", ".")
UNFOLD_CONFIG = f"{DATA_DIR}/unfold_config.yaml"
DB_PATH = f"{DATA_DIR}/unfold/db.sqlite"
HOLDINGS_FILE = f"{DATA_DIR}/holdings.json"
  1. Set environment variable:
DATA_DIR=/opt/render/project/data

Render Configuration File

Create render.yaml in your repository root for infrastructure-as-code:
render.yaml
services:
  - type: web
    name: spendwisely-george
    env: python
    region: oregon
    plan: free
    branch: main
    buildCommand: pip install fastapi uvicorn pyyaml requests
    startCommand: python server.py
    envVars:
      - key: PYTHON_VERSION
        value: 3.11
Deploy with:
render blueprint sync

Railway Deployment

Railway offers simpler setup with better free tier storage.
1

Create Railway Project

  1. Sign up at railway.app
  2. Click New ProjectDeploy from GitHub repo
  3. Select your repository
2

Configure Service

Railway auto-detects Python. Verify settings:Build:
pip install -r requirements.txt
Start:
python server.py
3

Create requirements.txt

Add to your repository:
requirements.txt
fastapi==0.109.0
uvicorn[standard]==0.27.0
pyyaml==6.0.1
requests==2.31.0
4

Set Environment Variables

In Railway dashboard:
PORT=8000  # Optional, Railway auto-assigns
5

Deploy

Railway auto-deploys on push. Get your URL:
https://spendwiselygeorge-production.up.railway.app

Railway Persistent Storage

Railway provides persistent volumes on all tiers, including free.
  1. In Railway dashboard, go to Volumes
  2. Add volume:
    • Mount path: /app/data
    • Size: 1GB
  3. Update server.py:
DATA_DIR = os.environ.get("DATA_DIR", ".")
UNFOLD_CONFIG = f"{DATA_DIR}/unfold_config.yaml"
DB_PATH = f"{DATA_DIR}/unfold/db.sqlite"
HOLDINGS_FILE = f"{DATA_DIR}/holdings.json"
  1. Set environment variable:
DATA_DIR=/app/data

Fly.io Deployment

For global edge deployment with automatic region routing.
1

Install Flyctl

curl -L https://fly.io/install.sh | sh
flyctl auth login
2

Create Dockerfile

Dockerfile
FROM python:3.11-slim

WORKDIR /app

# Copy application files
COPY server.py index.html manifest.json sw.js ./
COPY unfold ./unfold

# Install dependencies
RUN pip install --no-cache-dir fastapi uvicorn pyyaml requests

# Ensure unfold binary is executable
RUN chmod +x unfold/unfold

# Create data directory
RUN mkdir -p /data && echo '[]' > /data/holdings.json

# Expose port
EXPOSE 8080

# Start server
CMD ["python", "server.py"]
3

Initialize Fly App

flyctl launch
# Follow prompts:
# App name: spendwisely-george
# Region: Choose closest
# Deploy now: Yes
4

Configure fly.toml

fly.toml
app = "spendwisely-george"
primary_region = "sin"

[build]

[http_service]
  internal_port = 8000
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0

[[mounts]]
  source = "data"
  destination = "/data"

[env]
  PORT = "8000"
  DATA_DIR = "/data"
5

Create Volume

flyctl volumes create data --size 1
6

Deploy

flyctl deploy
Access at: https://spendwisely-george.fly.dev

Unfold Binary Considerations

The pre-built Unfold binary (unfold/unfold) is compiled for Linux x86_64. Ensure your cloud platform matches this architecture.

Architecture Compatibility

PlatformDefault ArchitectureCompatible?
RenderLinux x86_64✓ Yes
RailwayLinux x86_64✓ Yes
Fly.ioLinux x86_64 (default)✓ Yes
AWS LambdaCustom runtime⚠️ Requires layer

Building for ARM (Apple Silicon)

If deploying to ARM-based platforms:
cd unfold

# Install Go cross-compilation tools
go install github.com/mitchellh/gox@latest

# Build for Linux ARM64
GOOS=linux GOARCH=arm64 go build -o unfold-arm64 main.go

Environment Variables Reference

Required

None - all configuration is file-based by default.

Optional

# Server Configuration
PORT=8000                    # Server port (auto-set by most platforms)

# Custom Paths (for persistent storage)
DATA_DIR=/app/data          # Base directory for data files
UNFOLD_BINARY=/app/unfold/unfold
UNFOLD_CONFIG=/app/data/unfold_config.yaml
DB_PATH=/app/data/db.sqlite
HOLDINGS_FILE=/app/data/holdings.json

Post-Deployment Setup

1

Access Your App

Visit the deployed URL provided by your platform.
2

Login to Fold

  1. Go to SettingsBank Sync
  2. Enter phone number and verify OTP
  3. The server saves tokens to unfold_config.yaml
3

Sync Transactions

Click Sync in the Transactions tab. First sync may take 1-2 minutes.
4

Configure Google Script (Optional)

Update the Google Apps Script URL in index.html if using manual expense tracking.

Monitoring and Logs

# View logs in dashboard or via CLI
render logs -s spendwisely-george

# Tail live logs
render logs -s spendwisely-george --tail

Troubleshooting

Cause: PORT environment variable not set correctlySolution: Remove hardcoded port in server.py. It should be:
port = int(os.environ.get("PORT", 8000))
Cause: Binary not committed to repository or incorrect pathSolution:
  1. Ensure unfold/unfold is in your repo (check .gitignore)
  2. Verify permissions:
    chmod +x unfold/unfold
    git add unfold/unfold
    git commit -m "Add unfold binary"
    
Cause: No persistent storage configuredSolution: See platform-specific persistent storage sections above.
Cause: Frontend trying to access different domainSolution: The server allows all origins (server.py:23-28). If still blocked:
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,  # Add this
    allow_methods=["*"],
    allow_headers=["*"],
)
Cause: Outbound HTTPS blocked or API rate limitingSolution:
  • Check platform firewall settings
  • Add retry logic in server.py:213
  • Consider caching NAV data

Security Best Practices

The current implementation has security limitations for production use:

Current Vulnerabilities

  1. No authentication - Anyone with the URL can access the app
  2. CORS allows all origins - Open to CSRF attacks
  3. Tokens stored in plaintext - unfold_config.yaml is not encrypted
1

Add Authentication

Implement basic auth or OAuth:
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import secrets

security = HTTPBasic()

def verify_credentials(credentials: HTTPBasicCredentials):
    username_correct = secrets.compare_digest(
        credentials.username, os.environ.get("AUTH_USERNAME")
    )
    password_correct = secrets.compare_digest(
        credentials.password, os.environ.get("AUTH_PASSWORD")
    )
    return username_correct and password_correct
2

Restrict CORS

Update allowed origins:
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://your-domain.com",
        "http://localhost:8000"
    ],
    allow_methods=["GET", "POST"],
    allow_headers=["*"],
)
3

Encrypt Sensitive Data

Use environment variables for tokens instead of YAML files:
FOLD_ACCESS_TOKEN = os.environ.get("FOLD_ACCESS_TOKEN")
FOLD_REFRESH_TOKEN = os.environ.get("FOLD_REFRESH_TOKEN")

Cost Estimation

Free Tier Limits

PlatformMonthly CostLimitations
Render Free$0750 hrs, sleeps after 15 min idle, no persistent disk
Railway Free0(0 (5 credit)~23 days runtime, persistent storage included
Fly.io Free$03 shared-cpu VMs, 160GB bandwidth, 3GB storage
For production use:
  • Render Starter: $7/month (persistent disk, no sleep, 24/7 uptime)
  • Railway Pro: $20/month (unlimited projects, priority support)
  • Fly.io Paid: ~$5/month (1 dedicated VM + volume)

Next Steps

API Reference

Explore all available endpoints

Local Setup

Run SpendWisely George on your local machine

Build docs developers (and LLMs) love