Self-hosted expense tracking for Health Savings Accounts (HSA), Flexible Spending Accounts (FSA), and Dependent Care FSA (DCFSA)
β Enjoying this project? Buy me a coffee to support development!
π Deploy anywhere: Self-host on Fly.io, Railway, Render, or your own server!
While Google Sheets with linked Drive files works for tracking healthcare expenses, it has significant limitations:
- β Requires internet connection to access your sensitive financial data
- β Files scattered across cloud storage
- β Limited offline functionality
- β Privacy concerns with health information in the cloud
This tracker solves those problems:
- β 100% self-hosted - Your data stays on your server
- β Offline-first - Works without internet, your data is locally secured
- β Organized exports - CSV reports + all receipts in original file formats (PDF, JPG, PNG)
- β Reimport capability - Full backup/restore with database + receipts as organized ZIP
- β Privacy-focused - No third-party services, complete control over your health data
- HSA (Health Savings Account) - Unlimited, all-time tracking with balance management
- FSA (Flexible Spending Account) - Plan-year limited with election tracking
- DCFSA (Dependent Care FSA) - Dependent care expenses with separate plan years
- Full workflow: Open β Pending β Partially Reimbursed β Reimbursed
- Additional statuses: Rejected, Disputed, Closed
- Partial reimbursements - Track multiple partial payments for a single expense
- Claim tracking - Insurance claim numbers and reimbursement documentation
- Multiple attachments - Unlimited receipts per expense
- Dual attachment types:
- Original receipts (proof of expense)
- Proof-of-reimbursement documents (insurance documentation)
- Supported formats: PDF, JPG, JPEG, PNG (up to 16MB per file)
- Organized storage: Files automatically organized by user and year
- Secure access - Verified file serving ensures users only access their own documents
- Custom date ranges - Not limited to calendar years (e.g., April 1 - March 31)
- Election limits - Set annual election amounts for FSA/DCFSA
- Automatic linking - Expenses automatically associated with the correct benefit plan
- Spending tracking - Real-time tracking against election limits
- Overlap prevention - System prevents conflicting benefit plan configurations
- Track expenses by family member
- Filter and report per household member
- Useful for dependent care and family healthcare expenses
- Maintain reusable list of healthcare providers and merchants
- Quick dropdown selection when adding expenses
- Prevents typos and ensures consistency
- Account summaries - Total paid vs. reimbursed by category
- Benefit plan progress - Spending against election limits
- Pending reimbursements - Track outstanding claims
- Visual breakdowns - Category-based analytics
- Timezone-aware - Respects your local timezone for accurate date calculations
- AGPL-3.0 licensed - Free and open source
- Self-hosted only - No SaaS, no subscription, no data sharing
- User isolation - Multi-user support with complete data separation
- Authentication options:
- Local username/password authentication with secure hashing
- OIDC/SSO support - Sign in with Google, Microsoft, Okta, Auth0, Keycloak, GitLab, or any OpenID Connect provider
- Auto-provisioning for OIDC users
- PKCE and state validation for secure OAuth flows
- CSRF protection - All forms protected against cross-site request forgery
- Rate limiting - Prevents brute-force attacks
- Optional encryption - SQLCipher database encryption + Fernet file encryption
- CSV Export - Download filtered expense data for spreadsheet analysis
- Customizable columns (dates, amounts, providers, categories, status, etc.)
- Filter by year, category, status before exporting
- Full Database Backup - Complete ZIP export containing:
- SQLite database with all expense records
- All receipts organized by year in original file formats
- Reimport ready - Restore complete system from ZIP backup
- Organized file structure - Exports maintain clean folder hierarchy for easy navigation
- Responsive design - Works on desktop, tablet, and mobile
- Advanced filtering - Filter by year, category, status, provider
- Bi-directional sorting - Sort by date, amount, provider, status, claim number
- Accessibility features - ARIA labels, keyboard navigation
- Multi-currency support - 10 currencies: USD, EUR, GBP, CAD, AUD, JPY, CNY, INR, CHF, MXN
- Onboarding wizard - Guided setup for new users
Backend:
- Flask 3.0 (Python web framework)
- SQLite with WAL mode (database)
- Werkzeug (password hashing)
- Flask-WTF (CSRF protection)
- Flask-Limiter (rate limiting)
- Gunicorn (production WSGI server)
Frontend:
- Vanilla JavaScript (no framework dependencies)
- HTML5/CSS3
- Component-based architecture
Storage:
- Local filesystem organized by user and year
- SQLite database (simple, reliable, portable)
Docker provides the easiest and most consistent deployment experience across all platforms.
π Full Docker Documentation: For complete setup instructions, encryption, production deployment, and troubleshooting, see DOCKER.md
Prerequisites:
- Docker Engine 20.10+ and Docker Compose 2.0+ (Get Docker)
Quick Start (2 Minutes) - No Git Required!
-
Download configuration files
mkdir hsa-tracker && cd hsa-tracker curl -O https://raw.githubusercontent.com/JavaDogWebDesign/HealthReceipts/main/docker-compose.yml curl -O https://raw.githubusercontent.com/JavaDogWebDesign/HealthReceipts/main/.env.example
-
Configure environment
# Copy environment template cp .env.example .env # Generate a secure SECRET_KEY docker run --rm python:3.11-slim python -c 'import secrets; print(secrets.token_hex(32))' # Edit .env and paste the generated key nano .env # or use your preferred editor
-
Start the application
docker-compose up -d
-
Access the tracker Open http://localhost:4123 and create your account! π
For Portainer, Komodo, or other Docker management tools:
- Copy this docker-compose template:
services:
hsa-tracker:
image: ghcr.io/javadogwebdesign/healthreceipts:latest
container_name: hsa-tracker
restart: unless-stopped
ports:
- "0.0.0.0:${PORT:-4123}:4123"
environment:
- SECRET_KEY=${SECRET_KEY}
- FLASK_ENV=${FLASK_ENV:-production}
- PORT=${PORT:-4123}
- HOST=0.0.0.0
volumes:
- ${DATA_PATH:-./data}:/app/data
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:4123/login').read()"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true-
Set environment variables in Portainer/Komodo stack settings:
Generate a SECRET_KEY:
docker run --rm python:3.11-slim python -c 'import secrets; print(secrets.token_hex(32))'Add these environment variables in your stack:
SECRET_KEY=paste-your-generated-key-here FLASK_ENV=production PORT=4123 DATA_PATH=./dataOr create a
.envfile in the stack directory:SECRET_KEY=paste-your-generated-key-here FLASK_ENV=production PORT=4123 DATA_PATH=./data
-
Deploy the stack and access at
http://your-server-ip:4123
Optional customization:
- Change port: Update
PORTenvironment variable (e.g.,PORT=8080) - Custom data location: Change
DATA_PATH(e.g.,DATA_PATH=/mnt/storage/hsa-data)
Essential Docker Commands:
# View logs
docker-compose logs -f
# Stop
docker-compose down
# Restart
docker-compose restart
# Update to latest version
docker-compose pull && docker-compose up -dπ¦ Docker Compose Files:
- Pre-built image (easiest):
docker-compose.yml - Build from source (for developers):
docker-compose.build.yml
πΎ Data Persistence:
Your data is stored in ./data/:
data/hsa_tracker.db- SQLite databasedata/receipts/- Receipt files (organized by user/year)
./data/ before updates!
Development/Build from Source: If you want to build from source or customize the application:
git clone https://github.com/JavaDogWebDesign/HealthReceipts.git
cd HealthReceipts
cp .env.example .env
# Edit .env with your SECRET_KEY
docker-compose -f docker-compose.build.yml up -dAdvanced Setup: For production deployment with Nginx, SSL/TLS, encryption, network access, and more, see DOCKER.md.
Install directly on Ubuntu, Debian, CentOS, or any Linux distribution.
Prerequisites:
- Python 3.8 or higher (
python3 --version) - pip package manager
- Git
Steps:
-
Clone the repository
git clone https://github.com/yourusername/hsa-tracker-v2.git cd hsa-tracker-v2 -
Run the automated setup script (Linux/Mac)
chmod +x run.sh ./run.sh
This script will:
- Create a Python virtual environment
- Install all dependencies from
requirements.txt - Initialize the SQLite database
- Start the development server on http://localhost:4123
-
Access the tracker
Open http://localhost:4123 and create your account!
Manual Installation (if you prefer more control):
-
Create virtual environment
python3 -m venv venv source venv/bin/activate -
Install dependencies
pip install -r requirements.txt
-
Configure environment
cp .env.example .env
Generate and set a secure
SECRET_KEY:python -c 'import secrets; print(secrets.token_hex(32))'Edit
.env:FLASK_ENV=development SECRET_KEY=your-generated-key PORT=4123
-
Initialize the database
cd app python database.py cd ..
-
Run the application
Development mode:
cd app python app.pyProduction mode (recommended for server deployment):
# Update .env to set FLASK_ENV=production pip install gunicorn cd app gunicorn -w 4 -b 0.0.0.0:4123 app:app
-
Access the tracker
Navigate to http://localhost:4123 or http://your-server-ip:4123
Running as a service (systemd):
Create /etc/systemd/system/hsa-tracker.service:
[Unit]
Description=HSA/FSA Expense Tracker
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/path/to/hsa-tracker-v2/app
Environment="PATH=/path/to/hsa-tracker-v2/venv/bin"
ExecStart=/path/to/hsa-tracker-v2/venv/bin/gunicorn -w 4 -b 127.0.0.1:4123 app:app
Restart=always
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable hsa-tracker
sudo systemctl start hsa-tracker
sudo systemctl status hsa-trackerThis application can be deployed on various hosting platforms:
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Launch app (follow prompts)
fly launch
# Deploy
fly deploy
# Set secrets
fly secrets set SECRET_KEY=$(python -c 'import secrets; print(secrets.token_hex(32))')- Connect your GitHub repository
- Railway auto-detects the Python app
- Set environment variables in the dashboard:
SECRET_KEYFLASK_ENV=production
- Add a volume for persistent storage at
/app/data
- Create a new Web Service
- Connect your repository
- Set build command:
pip install -r requirements.txt - Set start command:
cd app && gunicorn -w 4 -b 0.0.0.0:$PORT app:app - Add environment variables (
SECRET_KEY,FLASK_ENV=production) - Add disk storage for
/app/data
- Create app from GitHub repository
- Configure as Python app
- Add managed database or use attached volume for SQLite
- Set environment variables
- Deploy!
- Minimum requirements: 512MB RAM, 1 CPU core, 5GB storage
- Use Docker Compose or manual installation
- Works on Ubuntu, Debian, CentOS, Fedora, etc.
- Set up Nginx reverse proxy for HTTPS (see production checklist below)
- Set
FLASK_ENV=productionin environment variables - Generate strong
SECRET_KEYwithsecrets.token_hex(32) - Enable HTTPS/TLS with Let's Encrypt or similar
- Use Gunicorn or similar production WSGI server
- Configure reverse proxy (Nginx or Apache) with SSL
- Set up automated backups (see Backup Strategy below)
- Configure firewall rules (allow only 80/443, block direct access to 4123)
- Review file permissions on
data/directory - Set up monitoring and logging
- Consider enabling optional encryption (SQLCipher + Fernet)
- Use strong passwords for user accounts
- Keep application updated (
git pull+ restart)
Install Nginx and Certbot:
sudo apt update
sudo apt install nginx certbot python3-certbot-nginxConfigure Nginx (/etc/nginx/sites-available/hsa-tracker):
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:4123;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Enable site and get SSL certificate:
sudo ln -s /etc/nginx/sites-available/hsa-tracker /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.comWhen you first log in, you'll be guided through:
- Currency & Timezone - Set your preferred currency and timezone
- HSA Balance - Enter your current HSA balance (optional)
- Benefit Plans - Configure FSA/DCFSA benefit plans with election amounts
- Family Members - Add household members for expense tracking
- Click "+ Add Expense" in the navigation
- Fill in expense details:
- Date of Service (required) - When the medical service occurred
- Amount Paid (required) - How much you paid out-of-pocket
- Category - HSA, FSA, or DCFSA
- Status - Open, Pending, Reimbursed, etc.
- Provider - Select from dropdown or add new
- Family Member - Who the expense is for
- Description - Notes about the expense
- Claim Number - Insurance claim reference
- Attach receipts - Upload PDF, JPG, or PNG files
- Click "Save Expense"
- Find the expense in your expenses list
- Click to edit
- Update status as your claim progresses:
- Open β Pending (submitted to insurance)
- Pending β Partially Reimbursed (received partial payment)
- Partially Reimbursed β Reimbursed (fully paid)
- Enter reimbursement amounts as you receive them
- Upload proof-of-reimbursement documents (insurance EOB, payment confirmation)
- Track pending and completed reimbursements on the dashboard
- Navigate to Dashboard
- Scroll to Benefit Plan Management section
- Click "Add Benefit Plan"
- Select account type (FSA or DCFSA)
- Set custom date range (e.g., January 1 - December 31, or April 1 - March 31)
- Enter annual election amount
- Save
Expenses within the benefit plan will automatically track against your election limit on the dashboard.
CSV Export (for spreadsheet analysis):
- Go to Dashboard or Expenses page
- Apply filters if desired (year, category, status)
- Click "Export CSV"
- Download opens in Excel, Google Sheets, LibreOffice, etc.
Full Backup (database + receipts):
- Go to Settings
- Scroll to Data Export section
- Click "Export DB"
- Download ZIP file containing:
- Complete SQLite database
- All receipts organized by year in original formats
- Reimport ready - Can be used to restore complete system
Backup/Restore Process:
# Backup (manual)
zip -r hsa-backup-$(date +%Y%m%d).zip data/
# Restore
unzip hsa-backup-20240101.zip
# Restart applicationConfigure via .env file:
# Required
FLASK_ENV=production # Use 'production' in production, 'development' for dev
SECRET_KEY=your-secret-key # Generate with: python -c 'import secrets; print(secrets.token_hex(32))'
# Optional
PORT=4123 # Port to run on (defaults to 4123)
APP_URL=http://localhost:4123 # Application URL (required for OIDC)
# OIDC Authentication (Optional)
OIDC_ENABLED=false # Enable OIDC/SSO authentication
OIDC_ALLOW_LOCAL_AUTH=true # Keep local auth as fallback (recommended)
# Example: Google OAuth
# GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
# GOOGLE_CLIENT_SECRET=your-client-secretThe application supports OpenID Connect (OIDC) authentication with multiple providers:
- Supported providers: Google, Microsoft, Okta, Auth0, Keycloak, GitLab, and any OIDC-compliant provider
- Auto-provisioning: New users are automatically created on first OIDC login
- Local fallback: Username/password auth can remain enabled as backup
- Secure: Implements PKCE, state validation, and secure session management
π Complete OIDC Setup Guide: See OIDC_SETUP.md for detailed instructions on configuring each provider.
Quick Example (Google):
- Create OAuth credentials at Google Cloud Console
- Set redirect URI:
https://your-domain.com/auth/oidc/callback/google - Add to
.env:OIDC_ENABLED=true APP_URL=https://your-domain.com GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=your-client-secret
- Run migration:
python app/migrations/001_add_oidc_fields.py - Restart application
Per-user preferences (configured in Settings page):
- Timezone - Defaults to America/New_York (supports all pytz timezones)
- Currency - Defaults to USD (supports 10 major currencies)
- HSA Balance - Track current HSA account balance
- Database:
data/hsa_tracker.db(SQLite) - Receipts:
data/receipts/USER_ID/YEAR/(organized by user and year)
This application implements multiple layers of security:
- β Password hashing - Werkzeug secure password hashing
- β CSRF protection - Flask-WTF tokens on all state-changing operations
- β Rate limiting - Prevents brute-force attacks (5 login attempts/min, 3 registrations/hour)
- β Session security - HttpOnly, SameSite cookies; secure flag in production
- β User isolation - Complete data separation between users
- β Verified file serving - Users can only access their own receipts
- β Security headers - HSTS, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
- β Optional encryption - SQLCipher database encryption + Fernet file encryption
Enable Optional Encryption:
# Generate encryption keys
cd app/utils
python encryption.py
# Add to .env file
DB_ENCRYPTION_KEY=your-generated-db-key
FILE_ENCRYPTION_KEY=your-generated-file-keyFor complete security documentation, see SECURITY.md.
Recommended: Set up automated backups to prevent data loss.
# Edit crontab
crontab -e
# Add daily backup at 2 AM
0 2 * * * cd /path/to/hsa-tracker-v2 && zip -r backups/backup_$(date +\%Y\%m\%d).zip data/Add to your docker-compose.yml:
services:
backup:
image: alpine:latest
volumes:
- ./data:/data:ro
- ./backups:/backups
command: sh -c "zip -r /backups/backup_$(date +%Y%m%d).zip /data"Run with cron:
0 2 * * * cd /path/to/hsa-tracker-v2 && docker-compose run --rm backupUse the built-in "Export DB" feature in Settings, or manually copy the data/ directory:
cp -r data/ backups/backup_$(date +%Y%m%d)/Core Tables:
- users - User accounts with authentication, timezone, currency preferences
- expenses - Expense records with status tracking, amounts, claim numbers
- attachments - Receipt and proof-of-reimbursement file references
- plan_years - FSA/DCFSA benefit plan tracking with custom date ranges
- providers - Healthcare providers and merchants
- family_members - Family member tracking for household expenses
Optimized with indexes for fast filtering by category, status, year, and date range.
Database doesn't exist:
cd app
python database.pyPort already in use:
Edit .env and change PORT=5001 (or any available port)
Can't access from other devices on network:
Ensure Flask is running with host='0.0.0.0' and your firewall allows the port:
sudo ufw allow 4123Docker container won't start:
# Check logs
docker-compose logs -f
# Common issues:
# - Missing SECRET_KEY in .env
# - Port 4123 already in use (change PORT in .env)
# - Permission issues with ./data directory (run: sudo chown -R 1000:1000 data/)File upload fails:
- Check file size (max 16MB per file)
- Supported formats: PDF, JPG, JPEG, PNG
- Verify
data/receiptsdirectory has write permissions
Forgot password: Currently no self-service password reset. Admin can reset via database:
cd app
python
>>> from werkzeug.security import generate_password_hash
>>> generate_password_hash('newpassword')
# Copy hash and update users table in databaseThis is an AGPL-3.0 licensed open source project. Contributions are welcome!
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development setup:
git clone https://github.com/yourusername/hsa-tracker-v2.git
cd hsa-tracker-v2
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cd app
python database.py
python app.pyAGPL-3.0 License - Free and open source software.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Important: Any modifications or network use requires making the source code available under the same license. This ensures the software remains free and open for everyone.
See LICENSE for full license text.
β Enjoying this project? Consider supporting development:
- Buy me a coffee
- Star this repository on GitHub
- Share with others who need healthcare expense tracking
- Contribute code or documentation improvements
Built with:
- Flask - Python web framework
- SQLite - Reliable embedded database
- Gunicorn - Production WSGI server
- Docker - Containerization platform
π Note: This application is designed for self-hosting. Always maintain regular backups of your data and receipts. Not affiliated with any financial institution or insurance provider.