Skip to content

Deployment Plan — Laptop + Cloudflare Tunnel (Ubuntu)

Goal

Make the Market Pulse dashboard accessible from anywhere online, at zero cost, without migrating the database or changing the codebase.

Architecture

Internet → Cloudflare Tunnel → localhost:8000 (FastAPI + React static files)
                                      ↑
                               main.py (APScheduler + SQLite)
                                      ↑
                          git pull (triggered by push from dev PCs)

Everything runs on the laptop. Cloudflare provides the public HTTPS URL. Development happens on any PC — push to GitHub, the laptop pulls and restarts.

Why This Approach

  • No cloud hosting costs
  • No database migration (SQLite stays as-is)
  • No code changes required
  • Cloudflare handles the public URL and HTTPS
  • Works even if home IP changes
  • systemd keeps the app alive and restarts it on crash

Tradeoffs

  • Dashboard goes offline if the laptop loses power or internet
  • Single point of failure (the laptop)
  • SQLite data is local — not backed up automatically (see Future Upgrades)

Prerequisites on the Laptop

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3.11 python3.11-venv python3-pip git curl

Clone the repo and set up the virtualenv:

cd ~
git clone https://github.com/<you>/fintech.git
cd fintech
python3.11 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # then fill in API keys

1. Install cloudflared

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
sudo dpkg -i cloudflared.deb

2. Authenticate and create a tunnel

cloudflared tunnel login        # opens browser — link your Cloudflare account
cloudflared tunnel create market-pulse

Note the tunnel ID printed — you need it in the config below.

3. Create tunnel config

mkdir -p ~/.cloudflared
nano ~/.cloudflared/config.yml
tunnel: <tunnel-id>
credentials-file: /home/<you>/.cloudflared/<tunnel-id>.json

ingress:
  - hostname: market-pulse.<your-domain>.com
    service: http://localhost:8501
  - service: http_status:404

4. Route DNS

cloudflared tunnel route dns market-pulse market-pulse.<your-domain>.com

5. Test manually

# Terminal 1
cd ~/fintech && source .venv/bin/activate && python main.py

# Terminal 2
cloudflared tunnel run market-pulse

Visit the public URL to confirm the dashboard loads, then Ctrl+C both.


6. systemd Services

App service — market-pulse.service

sudo nano /etc/systemd/system/market-pulse.service
[Unit]
Description=Market Pulse Dashboard
After=network.target

[Service]
Type=simple
User=<you>
WorkingDirectory=/home/<you>/fintech
Environment="PATH=/home/<you>/fintech/.venv/bin"
EnvironmentFile=/home/<you>/fintech/.env
ExecStart=/home/<you>/fintech/.venv/bin/python main.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Tunnel service — cloudflared.service

sudo cloudflared service install
# This auto-creates the systemd unit — no manual file needed.

Enable and start both

sudo systemctl daemon-reload
sudo systemctl enable market-pulse cloudflared
sudo systemctl start market-pulse cloudflared

Useful commands

sudo systemctl status market-pulse     # check if running
sudo journalctl -u market-pulse -f     # live logs
sudo systemctl restart market-pulse    # manual restart

7. Laptop Power Settings

Prevent the laptop from sleeping and taking the dashboard offline:

sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

If using a desktop environment, also set display sleep to never in Settings → Power.


8. Deploy Workflow (pushing updates from dev PCs)

The flow

  1. Develop on any PC → git push to GitHub
  2. SSH into the laptop and run the deploy script, or set up a webhook so it happens automatically

Deploy script

Create ~/fintech/deploy.sh:

#!/bin/bash
set -e
cd /home/<you>/fintech
git pull origin main
source .venv/bin/activate
pip install -r requirements.txt -q
sudo systemctl restart market-pulse
echo "Deployed at $(date)"
chmod +x ~/fintech/deploy.sh

Run it from your dev PC over SSH after pushing:

ssh <you>@<laptop-ip> ~/fintech/deploy.sh

SSH access from dev PCs

On each dev PC, copy your SSH key to the laptop once:

ssh-copy-id <you>@<laptop-ip>

After that, ssh <you>@<laptop-ip> works without a password.

If the laptop is not on the same network, access it via Tailscale (see below) or through the Cloudflare Tunnel SSH feature.

Optional — Tailscale for private SSH access

Tailscale creates a private VPN between your devices so you can SSH into the laptop from anywhere without exposing SSH to the internet:

# On the laptop
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

# On each dev PC — install Tailscale, then:
ssh <you>@<laptop-tailscale-ip> ~/fintech/deploy.sh

9. GitHub Actions Auto-Deploy

On every push to main, a GitHub Action SSHes into the laptop and runs deploy.sh.

SSH key setup

Generate a dedicated deploy key on your dev PC (not the laptop):

ssh-keygen -t ed25519 -C "github-deploy" -f ~/.ssh/github_deploy -N ""

Add the public key to the laptop's authorized keys:

cat ~/.ssh/github_deploy.pub | ssh <you>@<laptop> "cat >> ~/.ssh/authorized_keys"

Add the private key to GitHub as a repository secret: - GitHub repo → Settings → Secrets and variables → Actions → New repository secret - Name: DEPLOY_SSH_KEY - Value: contents of ~/.ssh/github_deploy (the private key)

Add two more secrets: - DEPLOY_HOST — laptop's Tailscale IP (e.g. 100.x.x.x) - DEPLOY_USER — your Ubuntu username

Workflow file

Create .github/workflows/deploy.yml in the repo:

name: Deploy to laptop

on:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: SSH and deploy
        uses: appleboy/ssh-action@1d1cf8b3a865eb8ab6e2cad23b7d4c60cfe64e28  # v1.2.0, pinned to SHA
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: ~/fintech/deploy.sh

Security note: All third-party actions are pinned to a full commit SHA (not a floating tag like @v1) to prevent supply chain attacks where a tag is silently moved to malicious code. Verify the SHA matches the release you intend before committing.

How to verify the SHA

# Check the SHA for a specific tag on GitHub:
gh api repos/appleboy/ssh-action/git/refs/tags/v1.2.0 --jq '.object.sha'

Future Upgrades (if needed)

Need Solution
Survive laptop being off Migrate DB to Supabase/Neon (PostgreSQL) + deploy app to Railway/Fly.io
Automated deploy on push GitHub Actions webhook → calls deploy.sh over SSH
DB backups Nightly sqlite3 fintech.db .dump > backup.sql cron + copy to cloud storage
Multiple users, larger scale PostgreSQL via DATABASE_URL env var (already supported)

Status

  • [ ] Install Ubuntu on laptop
  • [ ] Install deps, clone repo, set up virtualenv
  • [ ] Install cloudflared, authenticate, create tunnel
  • [ ] Configure DNS route
  • [ ] Test manually
  • [ ] Create market-pulse.service systemd unit
  • [ ] Enable and start both systemd services
  • [ ] Disable sleep
  • [ ] Set up SSH key access from dev PCs
  • [ ] Create and test deploy.sh
  • [ ] Generate deploy SSH key, add public key to laptop, add private key to GitHub secrets
  • [ ] Add DEPLOY_HOST and DEPLOY_USER secrets to GitHub repo
  • [ ] Create .github/workflows/deploy.yml with pinned SHA
  • [ ] Confirm dashboard loads on public URL after a push to main