- Node.js 22+ (tested with v22.20.0)
- Claude Code with an active Claude subscription (
claudecommand in PATH) for AI features - Optional: a YNAB account with a personal access token, only if you want to connect YNAB
- SQLite (bundled via better-sqlite3, no separate install)
git clone git@github.com:rollecode/dough.git
cd dough
npm installCopy the example env file:
cp .env.local.example .env.localEdit .env.local:
SESSION_SECRET— random string for JWT signingYNAB_ACCESS_TOKEN— optional, can be set via settings UI insteadYNAB_BUDGET_ID— optional, can be set via settings UI insteadCLAUDE_PATH— path to claude CLI binary, defaults toclaudein PATH
Set env vars and run the seed script:
USER1_EMAIL=yourname USER1_PASSWORD=yourpassword USER1_NAME="Your Name" \
USER2_EMAIL=partner USER2_PASSWORD=partnerpassword USER2_NAME="Partner" \
npx tsx scripts/seed.tsnpm run build
npm start -- -p 3001The app runs at http://localhost:3001.
- Log in with the credentials you set in the seed script
- Set your name, household size, and add your accounts (or connect YNAB - see below)
- Link your spending account so the daily budget knows what you pay from
- Add income sources and recurring bills, and set up budget categories and targets
- Optionally connect Synci to import bank transactions automatically (see the timer section below)
Dough chooses its mode automatically: without a YNAB token and budget it runs standalone (its own
accounts, transactions, and envelope budgeting); with them it mirrors YNAB. To connect YNAB, paste a
personal access token in settings (or .env.local) and select your budget.
To expose the app publicly:
cloudflared tunnel create dough
cloudflared tunnel route dns dough your-domain.example.comCreate a config file at ~/.cloudflared/config-dough.yml:
tunnel: <tunnel-id>
credentials-file: ~/.cloudflared/<tunnel-id>.json
ingress:
- hostname: your-domain.example.com
service: http://localhost:3001
- service: http_status:404Create user services for auto-start:
# ~/.config/systemd/user/dough.service
[Unit]
Description=Dough personal finance app
After=network-online.target
Wants=network-online.target
[Service]
WorkingDirectory=/path/to/dough
ExecStart=/path/to/node node_modules/.bin/next start -p 3001
Restart=on-failure
RestartSec=2
TimeoutStopSec=5
KillMode=mixed
Environment=NODE_ENV=production
[Install]
WantedBy=default.targetsystemctl --user enable dough
systemctl --user start doughSynci sync is idempotent (it skips anything already imported or added manually), so it is safe to
run on a timer. Set a cron_secret household setting, then install a service + timer that polls it:
# ~/.config/systemd/user/dough-synci.service
[Unit]
Description=Dough Synci sync
[Service]
Type=oneshot
ExecStart=/usr/bin/curl -fsS -X POST http://127.0.0.1:3001/api/synci/sync -H "X-Cron-Secret: YOUR_CRON_SECRET"# ~/.config/systemd/user/dough-synci.timer
[Unit]
Description=Run Dough Synci sync every 30 minutes
[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
Persistent=true
[Install]
WantedBy=timers.targetsystemctl --user enable --now dough-synci.timer
systemctl --user list-timers dough-synci.timer # verify it is scheduledThe SQLite database lives at data/dough.db. Back it up regularly:
sqlite3 data/dough.db ".backup /path/to/backup/dough-$(date +%Y%m%d).db"