Back to Blog

Self-Host Your Personal Finance Dashboard: Maybe Finance + Plaid + Cloudflare Tunnel on Proxmox

Notion
8 min read
Self-HostingPersonal FinanceDockerProxmoxCloudflarePlaid

Most personal finance apps like Mint, Monarch, or Copilot charge monthly fees and store your banking data on their servers. What if you could run your own personal finance dashboard on your home server, connected directly to your real bank accounts via Plaid, secured behind Cloudflare Zero Trust so only you can access it?

This guide walks you through deploying Maybe Finance — an open-source personal finance app — on a Proxmox LXC container with Docker, connecting it to real bank accounts via Plaid's production API, and locking it down with a Cloudflare Tunnel + Zero Trust access policy.


What You'll Build

By the end of this guide, you'll have:

  • A self-hosted personal finance dashboard with net worth tracking, transaction categorization, and AI-powered insights
  • Automatic bank sync via Plaid — pulling real transactions daily from all your bank accounts, credit cards, and loans
  • HTTPS access from anywhere via Cloudflare Tunnel (no port forwarding needed)
  • Zero Trust authentication — only your email can access the app, even if someone discovers the URL
  • OpenAI-powered auto-categorization of transactions

Prerequisites

  • A Proxmox VE server (or any Linux host with Docker support)
  • A Cloudflare account with a domain (free tier works)
  • A Plaid account with Production access (Pay As You Go plan — first 200 API calls free)
  • An OpenAI API key (optional, for AI features)

Step 1: Create an LXC Container on Proxmox

Create a new unprivileged Ubuntu 24.04 LXC container with Docker support. The key flags are nesting=1 and keyctl=1 which are required for Docker to run inside an LXC.

pct create 135 local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst \
  --hostname maybe-finance \
  --memory 2048 \
  --swap 512 \
  --cores 2 \
  --rootfs local-lvm:16 \
  --net0 name=eth0,bridge=vmbr0,ip=dhcp \
  --features nesting=1,keyctl=1 \
  --unprivileged 1 \
  --start 1 \
  --onboot 1

💡 2GB RAM and 2 cores is sufficient. Maybe Finance is a Rails app with PostgreSQL and Redis — it's lightweight.


Step 2: Install Docker

SSH into your new container and install Docker using the official repository:

apt-get update -qq
apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
 
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu noble stable" > /etc/apt/sources.list.d/docker.list
 
apt-get update -qq
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
docker --version

Step 3: Deploy Maybe Finance

Create the project directory

mkdir -p /opt/maybe && cd /opt/maybe

Download the Docker Compose file

curl -fsSL -o compose.yml \
  https://raw.githubusercontent.com/maybe-finance/maybe/main/compose.example.yml

Create your .env file

SECRET=$(openssl rand -hex 64)
 
cat > .env << EOF
SECRET_KEY_BASE=$SECRET
POSTGRES_PASSWORD=$(openssl rand -hex 16)
OPENAI_ACCESS_TOKEN=sk-your-openai-key-here
PLAID_CLIENT_ID=your_plaid_client_id
PLAID_SECRET=your_plaid_production_secret
PLAID_ENV=production
EOF

Add Plaid environment variables to compose.yml

The default compose.yml only passes through OPENAI_ACCESS_TOKEN. You need to add the Plaid variables to the x-rails-env anchor:

# Add these lines under OPENAI_ACCESS_TOKEN in the x-rails-env section:
  PLAID_CLIENT_ID: ${PLAID_CLIENT_ID}
  PLAID_SECRET: ${PLAID_SECRET}
  PLAID_ENV: ${PLAID_ENV:-sandbox}

⚠️ This is the most commonly missed step. Without adding Plaid vars to compose.yml, the containers won't see your Plaid credentials even if they're in .env.

Launch the stack

docker compose up -d
 
# Verify all 4 containers are running:
docker compose ps
# Should show: web, worker, db (postgres), redis — all healthy

Maybe Finance is now running on port 3000. Open http://YOUR_LXC_IP:3000 to verify, then register your admin account.


Step 4: Secure with Cloudflare Tunnel

Instead of exposing port 3000 to the internet, we'll use a Cloudflare Tunnel. This creates an encrypted outbound connection from your server to Cloudflare's edge — no port forwarding, no firewall changes, no dynamic DNS.

Install cloudflared

curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb \
  -o /tmp/cloudflared.deb
dpkg -i /tmp/cloudflared.deb

Authenticate with Cloudflare

cloudflared tunnel login
# Opens a URL — click it, select your domain, authorize

Create and configure the tunnel

# Create the tunnel
cloudflared tunnel create maybe-finance
 
# Route your subdomain to the tunnel
cloudflared tunnel route dns maybe-finance finance.yourdomain.com
 
# Create the config file
cat > /root/.cloudflared/config.yml << EOF
tunnel: YOUR_TUNNEL_ID
credentials-file: /root/.cloudflared/YOUR_TUNNEL_ID.json
 
ingress:
  - hostname: finance.yourdomain.com
    service: http://localhost:3000
  - service: http_status:404
EOF

Run as a system service

cloudflared service install
systemctl enable cloudflared
systemctl start cloudflared

Your app is now accessible at https://finance.yourdomain.com with automatic HTTPS.


Step 5: Add Zero Trust Authentication

Even though the app has its own login, adding Cloudflare Access creates an additional authentication layer. Anyone who discovers your URL will hit Cloudflare's login wall before they even see the app.

  • Go to https://one.dash.cloudflare.com (Zero Trust dashboard)
  • Navigate to Access → Applications → Add an Application
  • Choose "Self-hosted"
  • Application name: Maybe Finance
  • Application domain: finance.yourdomain.com
  • Add a policy: Allow — Emails — your@email.com
  • Save Now accessing finance.yourdomain.com requires a one-time email PIN or Google OAuth before the app loads. Two layers of auth: Cloudflare Access + Maybe Finance login.

Step 6: Set Up Plaid Production

Plaid is the API that connects to your real bank accounts. Here's how to go from zero to production:

6.1 — Create a Plaid account

  • Sign up at https://dashboard.plaid.com/signup
  • You'll get a Client ID and Sandbox secret immediately

6.2 — Apply for Production access

  • In the Plaid Dashboard, click "Request access" next to Production secret
  • Select products: Transactions, Balance, Auth (minimum needed)
  • Choose "Pay As You Go" plan — no commitment, first 200 calls free
  • Fill out the security questionnaire (straightforward if you have Cloudflare Zero Trust + HTTPS)
  • Submit and wait for approval (usually same day)

6.3 — Configure redirect URIs

This is critical. In Plaid Dashboard → Developers → API → Allowed redirect URIs, add:

https://finance.yourdomain.com/accounts

⚠️ Maybe Finance uses /accounts as its OAuth redirect URI, NOT /callback or /plaid_items/callback. Getting this wrong is the #1 reason Plaid Link fails to open.

6.4 — Update your .env

# In /opt/maybe/.env, update:
PLAID_SECRET=your_production_secret_here
PLAID_ENV=production
 
# Restart:
cd /opt/maybe && docker compose down && docker compose up -d

6.5 — Connect your banks

In Maybe Finance, click "Link Account" → search for your bank (Chase, Navy Federal, Amex, etc.) → sign in with your real banking credentials through Plaid's secure interface. Plaid pulls your accounts, balances, and transaction history automatically.


What Does This Cost?

Here's the realistic monthly cost for running this setup with 4 bank connections:

  • Proxmox LXC: Free (uses your existing server)
  • Docker + Maybe Finance: Free (open source)
  • Cloudflare Tunnel + Access: Free (up to 50 users)
  • Plaid Transactions: $0.30/account/month = $1.20 for 4 banks
  • Plaid Balance: $0.10/call (optional, use sparingly)
  • OpenAI (for AI categorization): ~$0.50/month at gpt-4.1-mini rates
  • Total: ~$2-3/month vs $10-15/month for Monarch Money or similar

Architecture Overview

Browser (anywhere)
  → Cloudflare Access (email auth gate)
    → Cloudflare Tunnel (encrypted)
      → Proxmox LXC Container
        → Docker Compose
          ├── Maybe Finance (Rails, port 3000)
          ├── Sidekiq Worker (background jobs)
          ├── PostgreSQL 16 (data)
          └── Redis (cache/jobs)
        → Plaid API (bank sync)
        → OpenAI API (categorization)

Tips and Gotchas

  • The Maybe Finance repo was archived in July 2025 but remains fully functional for self-hosting
  • Always add Plaid env vars to compose.yml — .env alone is not enough since compose.yml controls what gets passed to containers
  • Cloudflare Access may interfere with Plaid webhooks — if bank sync stops working, add a bypass rule for /webhooks/* paths
  • PostgreSQL data persists in a Docker volume. Back it up: docker compose exec db pg_dump -U maybe_user maybe_production > backup.sql
  • To update Maybe Finance: docker compose pull && docker compose up -d
  • The AI features (auto-categorization, chat) use gpt-4.1-mini for categorization and gpt-4.1 for chat — budget accordingly

Conclusion

You now have a fully self-hosted personal finance dashboard that rivals commercial apps like Monarch Money or Copilot — at a fraction of the cost, with full control over your financial data. Your bank credentials go through Plaid's secure infrastructure (never stored on your server), your app is behind two layers of authentication, and everything runs on hardware you own.

The entire setup takes about 30 minutes once you have Plaid Production access. And because it's all Docker-based, you can back it up, migrate it, or tear it down with a single command.

Share this post

Help this article travel further

8share actions ready

One tap opens the share sheet or pre-fills the post for the platform you want.