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
systemdkeeps 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¶
- Develop on any PC →
git pushto GitHub - 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.servicesystemd 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_HOSTandDEPLOY_USERsecrets to GitHub repo - [ ] Create
.github/workflows/deploy.ymlwith pinned SHA - [ ] Confirm dashboard loads on public URL after a push to main