Deploying ExpenseOwl
Introduction
ExpenseOwl is a powerful, self-hosted expense tracking and budget management application designed for individuals, freelancers, and small businesses who want complete control over their financial data. Built with privacy and simplicity in mind, ExpenseOwl provides an intuitive interface for tracking expenses, categorizing transactions, managing budgets, and generating insightful financial reports—all while keeping your sensitive financial information under your control.
Unlike cloud-based expense tracking services that monetize your data, ExpenseOwl runs entirely on your infrastructure, ensuring that your financial information remains private and secure. Whether you’re tracking personal expenses, managing business costs, or monitoring multiple projects, ExpenseOwl offers the flexibility and features you need without the subscription fees or privacy concerns of traditional SaaS solutions.
Key Features
ExpenseOwl stands out with its comprehensive feature set:
- Complete Expense Tracking: Record and categorize all your expenses with customizable categories
- Budget Management: Set monthly budgets for different categories and track spending in real-time
- Receipt Management: Upload and attach receipt images to transactions for documentation
- Multi-Currency Support: Track expenses in different currencies with automatic conversion
- Recurring Expenses: Automatically log recurring bills and subscriptions
- Financial Reports: Generate detailed reports and visualizations of spending patterns
- Category Management: Create custom expense categories tailored to your needs
- Project Tracking: Organize expenses by projects for freelancers and businesses
- Tag System: Flexible tagging system for advanced expense organization
- Export Capabilities: Export data to CSV, Excel, or PDF for accounting and tax purposes
- User Management: Support for multiple users with role-based access control
- Mobile Responsive: Access your expense data from any device
- Dark Mode: Easy on the eyes for late-night expense tracking
- Search and Filter: Quickly find transactions with powerful search capabilities
- Dashboard Analytics: Visual summaries of spending trends and budget health
Why Deploy ExpenseOwl on Klutch.sh?
Deploying ExpenseOwl on Klutch.sh provides significant advantages for your financial tracking needs:
- Complete Privacy: Your financial data stays on your infrastructure—no third-party access
- Simple Deployment: Deploy directly from GitHub with automatic Docker detection
- Persistent Storage: Built-in volume support ensures your financial data is never lost
- Database Integration: Easy PostgreSQL setup for reliable data storage
- HTTPS by Default: Automatic SSL certificates keep your financial data encrypted in transit
- Secure Configuration: Environment-based secrets management for database credentials
- Scalable Resources: Adjust compute resources as your expense tracking needs grow
- High Availability: Reliable infrastructure ensures 24/7 access to your financial data
- Custom Domains: Use your own domain for a professional expense tracking solution
- Cost-Effective: No per-user licensing fees—pay only for infrastructure
- Backup Ready: Easy integration with backup solutions for data protection
- Zero Lock-in: Full control over your data with standard PostgreSQL database
Prerequisites
Before deploying ExpenseOwl, ensure you have:
- A Klutch.sh account
- A GitHub account with a repository for your deployment configuration
- A PostgreSQL database (see our PostgreSQL deployment guide)
- Basic familiarity with Docker and environment variables
- (Optional) SMTP credentials for email notifications and reports
Preparing Your Repository
Create a new GitHub repository for your ExpenseOwl deployment with the following structure:
expenseowl-deploy/├── Dockerfile├── .env.example├── docker-compose.yml (for local testing only)├── .dockerignore├── README.md└── .gitignoreDockerfile
Create a production-ready Dockerfile for ExpenseOwl:
FROM node:20-alpine AS builder
# Set working directoryWORKDIR /app
# Install system dependenciesRUN apk add --no-cache git python3 make g++
# Clone ExpenseOwl repositoryRUN git clone https://github.com/your-org/expenseowl.git .
# Install dependenciesRUN npm ci --only=production
# Build the applicationRUN npm run build
# Production stageFROM node:20-alpine
# Install runtime dependenciesRUN apk add --no-cache \ tini \ curl \ ca-certificates
# Create app user for securityRUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001
# Set working directoryWORKDIR /app
# Copy built application from builderCOPY --from=builder --chown=nodejs:nodejs /app/dist ./distCOPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modulesCOPY --from=builder --chown=nodejs:nodejs /app/package.json ./package.json
# Create directories for uploads and dataRUN mkdir -p /app/data /app/uploads && \ chown -R nodejs:nodejs /app/data /app/uploads
# Switch to non-root userUSER nodejs
# Expose application portEXPOSE 3000
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1
# Use tini for proper signal handlingENTRYPOINT ["/sbin/tini", "--"]
# Start the applicationCMD ["node", "dist/server.js"]Environment Variables Template
Create a .env.example file to document required configuration:
# Application ConfigurationNODE_ENV=productionPORT=3000APP_URL=https://example-app.klutch.sh
# Database Configuration (Required)DATABASE_URL=postgresql://expenseowl:your_secure_password@your-postgres-app.klutch.sh:8000/expenseowlDB_HOST=your-postgres-app.klutch.shDB_PORT=8000DB_NAME=expenseowlDB_USER=expenseowlDB_PASSWORD=your_secure_passwordDB_SSL=true
# Authentication & Security (Required)JWT_SECRET=generate_with_openssl_rand_base64_64SESSION_SECRET=generate_with_openssl_rand_base64_64COOKIE_SECRET=generate_with_openssl_rand_base64_32
# Encryption for sensitive dataENCRYPTION_KEY=generate_with_openssl_rand_hex_32
# Admin Account (Initial Setup)ADMIN_EMAIL=admin@example.comADMIN_PASSWORD=change_this_secure_passwordADMIN_NAME=Admin User
# Upload ConfigurationUPLOAD_DIR=/app/uploadsMAX_UPLOAD_SIZE=10485760ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,application/pdf
# Currency ConfigurationDEFAULT_CURRENCY=USDEXCHANGE_RATE_API_KEY=your_exchangerate_api_key
# SMTP Configuration (Optional but recommended)SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_SECURE=falseSMTP_USER=your-email@gmail.comSMTP_PASSWORD=your_app_passwordSMTP_FROM=ExpenseOwl <noreply@example.com>
# Feature FlagsENABLE_REGISTRATION=trueENABLE_EMAIL_NOTIFICATIONS=trueENABLE_MULTI_CURRENCY=trueENABLE_RECEIPT_OCR=falseENABLE_BUDGET_ALERTS=true
# Rate LimitingRATE_LIMIT_WINDOW_MS=900000RATE_LIMIT_MAX_REQUESTS=100
# Backup ConfigurationBACKUP_ENABLED=trueBACKUP_SCHEDULE=0 2 * * *BACKUP_RETENTION_DAYS=30
# LoggingLOG_LEVEL=infoLOG_FILE=/app/data/logs/expenseowl.logDocker Compose (Local Development Only)
Create a docker-compose.yml for local testing:
version: '3.9'
services: postgres: image: postgres:16-alpine container_name: expenseowl-postgres environment: POSTGRES_DB: expenseowl POSTGRES_USER: expenseowl POSTGRES_PASSWORD: devpassword volumes: - postgres_data:/var/lib/postgresql/data ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U expenseowl"] interval: 10s timeout: 5s retries: 5
expenseowl: build: . container_name: expenseowl-app depends_on: postgres: condition: service_healthy environment: NODE_ENV: development PORT: 3000 DATABASE_URL: postgresql://expenseowl:devpassword@postgres:5432/expenseowl JWT_SECRET: dev_jwt_secret_change_in_production SESSION_SECRET: dev_session_secret_change_in_production ADMIN_EMAIL: admin@example.com ADMIN_PASSWORD: admin123 volumes: - ./uploads:/app/uploads - ./data:/app/data ports: - "3000:3000"
volumes: postgres_data:Note: Docker Compose is only for local development. Klutch.sh does not support Docker Compose for deployments.
Docker Ignore File
Create a .dockerignore file:
.git.gitignore.env.env.**.mdREADME.mddocker-compose.yml.vscode.idea*.log.DS_Storenode_modulescoverage.nyc_output*.test.js*.spec.jstest/tests/__tests__distbuilduploads/*data/*!data/.gitkeep!uploads/.gitkeepGit Ignore File
Create a .gitignore file:
.env.env.local.env.production*.log.DS_Store.vscode/.idea/node_modules/dist/build/coverage/.nyc_output/uploads/*!uploads/.gitkeepdata/*!data/.gitkeep*.sqlite*.sqlite3postgres_data/Deploying on Klutch.sh
-
Set Up PostgreSQL Database
Before deploying ExpenseOwl, you need a PostgreSQL database. Follow our PostgreSQL deployment guide to set up a database on Klutch.sh.
When creating your PostgreSQL instance, configure the following:
- Database name:
expenseowl - Username:
expenseowl - Password: Use a strong, randomly generated password
- Connection details: Note your app URL (e.g.,
your-postgres-app.klutch.sh:8000)
After deployment, create the database:
-- Connect to PostgreSQL and create the databaseCREATE DATABASE expenseowl;CREATE USER expenseowl WITH PASSWORD 'your_secure_password';GRANT ALL PRIVILEGES ON DATABASE expenseowl TO expenseowl; - Database name:
-
Initialize the Database Schema
ExpenseOwl requires database tables for storing expenses, categories, users, and more. Connect to your PostgreSQL instance and run the initialization:
-- Connect to the expenseowl database\c expenseowl-- Grant schema privilegesGRANT ALL ON SCHEMA public TO expenseowl;-- ExpenseOwl will automatically create tables on first run-- Alternatively, you can run migrations manually if provided -
Generate Security Keys
Generate strong random secrets for your environment variables:
Terminal window # Generate JWT secret (64 bytes)openssl rand -base64 64# Generate session secret (64 bytes)openssl rand -base64 64# Generate cookie secret (32 bytes)openssl rand -base64 32# Generate encryption key (32 bytes hex)openssl rand -hex 32Save these values securely—you’ll need them for your Klutch.sh environment variables.
-
Create a New Project
Log in to the Klutch.sh dashboard and create a new project for your ExpenseOwl deployment.
-
Connect Your GitHub Repository
In your Klutch.sh project:
- Navigate to the Apps section
- Click “Create New App”
- Select GitHub as your source
- Choose the repository containing your ExpenseOwl Dockerfile
- Select the branch you want to deploy (typically
mainormaster)
-
Configure Build Settings
Klutch.sh will automatically detect your Dockerfile in the repository root. No additional build configuration is needed.
-
Set Traffic Type
In the deployment settings:
- Select HTTP as the traffic type
- Set the internal port to 3000
- Klutch.sh will automatically route traffic to your ExpenseOwl application
-
Configure Environment Variables
In your Klutch.sh app settings, add the following environment variables:
Required Variables:
Terminal window NODE_ENV=productionPORT=3000APP_URL=https://example-app.klutch.shDATABASE_URL=postgresql://expenseowl:your_secure_password@your-postgres-app.klutch.sh:8000/expenseowlJWT_SECRET=your_generated_jwt_secret_64_bytesSESSION_SECRET=your_generated_session_secret_64_bytesCOOKIE_SECRET=your_generated_cookie_secret_32_bytesENCRYPTION_KEY=your_generated_encryption_key_hexADMIN_EMAIL=admin@example.comADMIN_PASSWORD=your_admin_passwordOptional Variables (Email):
Terminal window SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_USER=your-email@gmail.comSMTP_PASSWORD=your_app_passwordSMTP_FROM=ExpenseOwl <noreply@example.com>Optional Variables (Features):
Terminal window DEFAULT_CURRENCY=USDENABLE_REGISTRATION=trueENABLE_EMAIL_NOTIFICATIONS=trueENABLE_MULTI_CURRENCY=trueENABLE_BUDGET_ALERTS=trueMAX_UPLOAD_SIZE=10485760Mark all sensitive values like
DATABASE_URL,JWT_SECRET,SESSION_SECRET,ENCRYPTION_KEY,ADMIN_PASSWORD, andSMTP_PASSWORDas secrets in the Klutch.sh dashboard. -
Attach Persistent Volumes
ExpenseOwl needs persistent storage for receipt uploads and application data:
Upload Volume:
- In your Klutch.sh app settings, navigate to Volumes
- Click “Add Volume”
- Set the mount path to
/app/uploads - Choose an appropriate size (start with 10GB for receipt storage)
- Save the volume configuration
Data Volume:
- Click “Add Volume” again
- Set the mount path to
/app/data - Choose an appropriate size (start with 5GB for logs and application data)
- Save the volume configuration
-
Deploy the Application
Click “Deploy” to start the deployment process. Klutch.sh will:
- Pull your code from GitHub
- Build the Docker image using the multi-stage Dockerfile
- Configure networking and volumes
- Start your ExpenseOwl server
The initial deployment typically takes 3-5 minutes depending on the build complexity.
-
Verify Deployment
Once deployed, verify your ExpenseOwl instance:
Terminal window # Check health endpointcurl https://example-app.klutch.sh/health# Expected response{"status": "healthy","database": "connected","uptime": 123}
Initial Configuration
First-Time Setup
-
Access the Application
Navigate to your ExpenseOwl URL (e.g.,
https://example-app.klutch.sh) -
Admin Login
Log in with the admin credentials you configured:
- Email: The value from
ADMIN_EMAIL - Password: The value from
ADMIN_PASSWORD
- Email: The value from
-
Change Admin Password
Immediately change the default admin password:
- Go to Settings → Account
- Click “Change Password”
- Enter a strong, unique password
- Save changes
-
Configure Application Settings
- Navigate to Settings → General
- Set your organization name
- Configure default currency
- Set date and time format preferences
- Configure fiscal year start date
Set Up Expense Categories
-
Navigate to Settings → Categories
-
Create your expense categories:
Personal Categories:
- Housing (Rent/Mortgage, Utilities, Maintenance)
- Transportation (Fuel, Public Transit, Car Maintenance)
- Food (Groceries, Dining Out, Coffee)
- Healthcare (Insurance, Medical, Pharmacy)
- Entertainment (Movies, Subscriptions, Hobbies)
- Personal Care (Clothing, Grooming)
- Education (Books, Courses, Tuition)
- Savings & Investments
Business Categories:
- Office Supplies
- Travel & Transportation
- Meals & Entertainment
- Professional Services
- Software & Subscriptions
- Marketing & Advertising
- Equipment & Hardware
- Insurance
-
Assign category colors for easy visual identification
-
Set default budgets for each category (optional)
Configure Budgets
- Navigate to Budgets → Create Budget
- Set monthly budget limits:
- Select category
- Set monthly budget amount
- Enable alerts at 80% and 100% thresholds
- Create budgets for all your active categories
- Review and adjust budgets monthly based on spending patterns
Using ExpenseOwl
Adding Expenses
Manual Entry:
- Click “Add Expense” from the dashboard
- Fill in expense details:
- Amount: Enter the expense amount
- Date: Select the transaction date
- Category: Choose the appropriate category
- Description: Add details about the expense
- Payment Method: Select payment method (Cash, Credit Card, etc.)
- Tags: Add tags for additional organization
- Project: Associate with a project (if applicable)
- Receipt: Upload receipt image or PDF
- Click “Save”
Quick Add:
Use the quick add feature for rapid expense entry:
- Click the floating ”+” button
- Enter amount and category
- Click “Add”
- Details can be edited later
Bulk Import:
Import expenses from CSV or Excel:
- Navigate to Import → Upload File
- Download the CSV template
- Fill in your expense data
- Upload the completed file
- Map columns to ExpenseOwl fields
- Review and confirm import
Managing Receipts
Upload Receipts:
- Open an expense
- Click “Attach Receipt”
- Upload image (JPEG, PNG, GIF) or PDF
- Receipt is automatically attached to the expense
View Receipts:
- Click on any expense to view attached receipt
- Zoom in to view details
- Download original receipt file
OCR Processing (if enabled):
- ExpenseOwl can extract data from receipt images
- Automatically populate amount, date, and merchant
- Review and confirm extracted data
Creating Reports
Expense Reports:
- Navigate to Reports → Expense Report
- Configure report parameters:
- Date range (This Month, Last Month, Year to Date, Custom)
- Categories to include
- Tags to filter by
- Projects to include
- Generate report
- View visual charts and summaries
- Export to PDF or Excel
Budget Reports:
- Navigate to Reports → Budget Report
- View budget vs. actual spending
- Identify over-budget categories
- Analyze spending trends
- Export for review
Tax Reports:
- Navigate to Reports → Tax Report
- Select tax year
- Filter business-related expenses
- Include receipt attachments
- Export comprehensive tax package
Multi-Currency Tracking
Enable Multi-Currency:
Set ENABLE_MULTI_CURRENCY=true in your environment variables.
Add Currencies:
- Navigate to Settings → Currencies
- Add currencies you use
- Set your primary currency
- Configure exchange rate update frequency
Track Foreign Expenses:
- When adding an expense, select the transaction currency
- Enter the amount in the foreign currency
- ExpenseOwl converts to your default currency using current exchange rates
- Both amounts are stored for accurate record-keeping
Sample Code and API Integration
API Authentication
ExpenseOwl provides a REST API for integrations and automation.
Login and Get Token:
// Node.js exampleconst fetch = require('node-fetch');
const EXPENSEOWL_URL = "https://example-app.klutch.sh";
async function login(email, password) { const response = await fetch(`${EXPENSEOWL_URL}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password }), });
if (!response.ok) { throw new Error(`Login failed: ${response.statusText}`); }
const data = await response.json(); return data.token;}
// Usageconst token = await login('user@example.com', 'password');console.log('Authentication token:', token);Create Expense via API
Node.js Example:
async function createExpense(token, expenseData) { const response = await fetch(`${EXPENSEOWL_URL}/api/expenses`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify(expenseData), });
if (!response.ok) { throw new Error(`Failed to create expense: ${response.statusText}`); }
return await response.json();}
// Create an expenseconst expense = await createExpense(token, { amount: 42.50, currency: 'USD', date: '2025-12-19', categoryId: 5, description: 'Coffee with client', paymentMethod: 'Credit Card', tags: ['business', 'client-meeting'],});
console.log('Expense created:', expense);Python Example:
import requestsfrom datetime import datetime
EXPENSEOWL_URL = "https://example-app.klutch.sh"
def login(email, password): """Authenticate and get token""" response = requests.post( f"{EXPENSEOWL_URL}/api/auth/login", json={"email": email, "password": password} ) response.raise_for_status() return response.json()["token"]
def create_expense(token, expense_data): """Create a new expense""" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" }
response = requests.post( f"{EXPENSEOWL_URL}/api/expenses", json=expense_data, headers=headers ) response.raise_for_status() return response.json()
# Usagetoken = login("user@example.com", "password")
expense = create_expense(token, { "amount": 125.00, "currency": "USD", "date": datetime.now().isoformat(), "categoryId": 8, "description": "Office supplies", "paymentMethod": "Company Card", "tags": ["business", "office"]})
print(f"Expense created: {expense['id']}")Fetch Expenses
JavaScript (Fetch API):
async function getExpenses(token, filters = {}) { const params = new URLSearchParams(filters);
const response = await fetch( `${EXPENSEOWL_URL}/api/expenses?${params}`, { headers: { 'Authorization': `Bearer ${token}`, }, } );
if (!response.ok) { throw new Error(`Failed to fetch expenses: ${response.statusText}`); }
return await response.json();}
// Get expenses for December 2025const expenses = await getExpenses(token, { startDate: '2025-12-01', endDate: '2025-12-31', categoryId: 5,});
console.log(`Found ${expenses.length} expenses`);expenses.forEach(expense => { console.log(`${expense.date}: ${expense.description} - $${expense.amount}`);});Generate Report via API
cURL Example:
# Get monthly expense reportcurl -X POST https://example-app.klutch.sh/api/reports/expenses \ -H "Authorization: Bearer your_token_here" \ -H "Content-Type: application/json" \ -d '{ "startDate": "2025-12-01", "endDate": "2025-12-31", "groupBy": "category", "includeCharts": true }'
# Download PDF reportcurl -X POST https://example-app.klutch.sh/api/reports/expenses/pdf \ -H "Authorization: Bearer your_token_here" \ -H "Content-Type: application/json" \ -d '{ "startDate": "2025-12-01", "endDate": "2025-12-31" }' \ --output expense-report-december-2025.pdfUpload Receipt via API
Node.js with FormData:
const FormData = require('form-data');const fs = require('fs');
async function uploadReceipt(token, expenseId, receiptPath) { const formData = new FormData(); formData.append('receipt', fs.createReadStream(receiptPath));
const response = await fetch( `${EXPENSEOWL_URL}/api/expenses/${expenseId}/receipt`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, }, body: formData, } );
if (!response.ok) { throw new Error(`Failed to upload receipt: ${response.statusText}`); }
return await response.json();}
// Usageconst result = await uploadReceipt(token, 123, './receipt.jpg');console.log('Receipt uploaded:', result.receiptUrl);Webhook Integration
Set Up Webhook for Budget Alerts:
// Express.js webhook receiverconst express = require('express');const app = express();
app.use(express.json());
app.post('/webhooks/expenseowl', (req, res) => { const { event, data } = req.body;
switch (event) { case 'budget.exceeded': console.log(`Budget exceeded for category: ${data.category}`); console.log(`Spent: $${data.spent} of $${data.budget}`); // Send notification, email, or Slack message break;
case 'expense.created': console.log(`New expense: ${data.description} - $${data.amount}`); break;
case 'budget.warning': console.log(`Budget warning: ${data.category} at ${data.percentage}%`); break;
default: console.log(`Unknown event: ${event}`); }
res.status(200).json({ received: true });});
app.listen(3001, () => { console.log('Webhook receiver listening on port 3001');});Advanced Configuration
Custom Domain Setup
Use your own domain for your ExpenseOwl instance:
-
Add Custom Domain in Klutch.sh:
- Navigate to your app in the Klutch.sh dashboard
- Go to Domains section
- Click “Add Custom Domain”
- Enter your domain (e.g.,
expenses.example.com)
-
Configure DNS:
-
Add a CNAME record in your DNS provider:
Type: CNAMEName: expensesValue: example-app.klutch.shTTL: 3600
-
-
Update Environment Variable:
Terminal window APP_URL=https://expenses.example.com -
Redeploy:
- Redeploy your application for changes to take effect
- Wait for DNS propagation (typically 5-30 minutes)
Email Configuration for Notifications
Set up email notifications for budget alerts and reports:
Using Gmail:
SMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_SECURE=falseSMTP_USER=your-email@gmail.comSMTP_PASSWORD=your-app-specific-passwordSMTP_FROM=ExpenseOwl <noreply@example.com>Note: Use an App Password for Gmail.
Using SendGrid:
SMTP_HOST=smtp.sendgrid.netSMTP_PORT=587SMTP_USER=apikeySMTP_PASSWORD=your-sendgrid-api-keySMTP_FROM=ExpenseOwl <noreply@example.com>Using AWS SES:
SMTP_HOST=email-smtp.us-east-1.amazonaws.comSMTP_PORT=587SMTP_USER=your-ses-smtp-usernameSMTP_PASSWORD=your-ses-smtp-passwordSMTP_FROM=ExpenseOwl <noreply@example.com>Multi-User Setup
Configure user management and permissions:
-
Enable User Registration:
Terminal window ENABLE_REGISTRATION=trueOr keep it
falseand create users manually through the admin panel. -
Create Additional Users:
- Log in as admin
- Navigate to Settings → Users
- Click “Add User”
- Enter user details and role
- Send invitation email
-
Configure Roles:
- Admin: Full access to all features and settings
- Manager: Can view all expenses and create reports
- User: Can only manage their own expenses
- Viewer: Read-only access for auditing
Automated Backups
Set up automated database backups:
Enable Backup in Environment:
BACKUP_ENABLED=trueBACKUP_SCHEDULE=0 2 * * *BACKUP_RETENTION_DAYS=30Manual Backup Script:
#!/bin/bashDATE=$(date +%Y%m%d_%H%M%S)BACKUP_DIR="/backups"BACKUP_FILE="expenseowl_backup_${DATE}.sql"
# Backup PostgreSQL databasepg_dump -h your-postgres-app.klutch.sh -p 8000 \ -U expenseowl -d expenseowl > "${BACKUP_DIR}/${BACKUP_FILE}"
# Compress backupgzip "${BACKUP_DIR}/${BACKUP_FILE}"
# Upload to S3 or backup service# aws s3 cp "${BACKUP_DIR}/${BACKUP_FILE}.gz" s3://your-bucket/expenseowl/
# Clean up old backups (keep last 30 days)find "${BACKUP_DIR}" -name "expenseowl_backup_*.sql.gz" -mtime +30 -delete
echo "Backup completed: ${BACKUP_FILE}.gz"Exchange Rate Configuration
For multi-currency tracking, configure automatic exchange rate updates:
Using Free API (exchangerate-api.com):
EXCHANGE_RATE_API_KEY=your-free-api-keyEXCHANGE_RATE_UPDATE_FREQUENCY=dailyUsing Paid API (currencyapi.com):
EXCHANGE_RATE_API_KEY=your-api-keyEXCHANGE_RATE_PROVIDER=currencyapiEXCHANGE_RATE_UPDATE_FREQUENCY=hourlyProduction Best Practices
Security Hardening
Environment Variables:
- Never commit
.envfiles to version control - Use Klutch.sh’s secret management for all sensitive values
- Rotate secrets quarterly:
- JWT_SECRET
- SESSION_SECRET
- ENCRYPTION_KEY
- Database passwords
Generate New Secrets:
# Rotate all security keysJWT_SECRET=$(openssl rand -base64 64)SESSION_SECRET=$(openssl rand -base64 64)COOKIE_SECRET=$(openssl rand -base64 32)ENCRYPTION_KEY=$(openssl rand -hex 32)Database Security:
- Use strong database passwords (minimum 16 characters)
- Enable SSL connections to PostgreSQL (
DB_SSL=true) - Restrict database access to ExpenseOwl’s IP only
- Regular security updates for PostgreSQL
Application Security:
- Keep ExpenseOwl and dependencies updated
- Enable rate limiting to prevent abuse
- Use HTTPS only (handled by Klutch.sh)
- Implement strong password policies
- Enable two-factor authentication for admin accounts
- Regular security audits
Performance Optimization
Database Optimization:
-
Create Indexes:
-- Connect to database\c expenseowl-- Create indexes for common queriesCREATE INDEX idx_expenses_user_date ON expenses(user_id, date DESC);CREATE INDEX idx_expenses_category ON expenses(category_id);CREATE INDEX idx_expenses_date_range ON expenses(date);CREATE INDEX idx_expenses_tags ON expenses USING GIN(tags);-- Analyze tablesANALYZE expenses;ANALYZE categories;ANALYZE users; -
Regular Maintenance:
-- Run weeklyVACUUM ANALYZE expenses;VACUUM ANALYZE categories;-- Check table sizesSELECTschemaname,tablename,pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS sizeFROM pg_tablesWHERE schemaname = 'public'ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
Application Caching:
# Enable caching for better performanceCACHE_ENABLED=trueCACHE_TTL=3600REDIS_URL=redis://your-redis-app.klutch.sh:8000File Upload Optimization:
- Compress uploaded receipt images
- Use image optimization libraries
- Set appropriate max file sizes
- Implement cleanup for orphaned uploads
Monitoring and Alerts
Health Monitoring:
Create a monitoring script:
#!/bin/bashHEALTH_URL="https://example-app.klutch.sh/health"ALERT_EMAIL="admin@example.com"
check_health() { response=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL")
if [ "$response" != "200" ]; then echo "ExpenseOwl health check failed: HTTP $response" # Send alert echo "ExpenseOwl is down!" | mail -s "ExpenseOwl Alert" "$ALERT_EMAIL" return 1 fi
echo "ExpenseOwl is healthy" return 0}
check_healthKey Metrics to Monitor:
- Application uptime and response times
- Database connection pool usage
- Disk space for uploads and data volumes
- Memory and CPU usage
- Failed login attempts
- API error rates
- Background job status
Log Monitoring:
Access logs through Klutch.sh dashboard or set up log aggregation:
# View recent errorstail -f /app/data/logs/expenseowl.log | grep ERROR
# Monitor API requeststail -f /app/data/logs/expenseowl.log | grep "POST /api/"Backup and Recovery
Database Backup Strategy:
-
Automated Daily Backups:
Terminal window # Add to cron (runs at 2 AM daily)0 2 * * * /path/to/backup-expenseowl.sh -
Test Restore Procedure:
Terminal window # Restore from backupgunzip expenseowl_backup_20251219.sql.gzpsql -h your-postgres-app.klutch.sh -p 8000 \-U expenseowl -d expenseowl < expenseowl_backup_20251219.sql -
Off-Site Backup Storage:
- Store backups in S3, Backblaze B2, or similar
- Use lifecycle policies for retention
- Encrypt backups before uploading
Upload Backup:
# Sync uploads directory to backup locationrsync -avz /app/uploads/ /backup/uploads/
# Or use S3aws s3 sync /app/uploads/ s3://your-bucket/expenseowl/uploads/Disaster Recovery Plan:
- Document restoration procedures
- Keep environment variables in secure password manager
- Test recovery process quarterly
- Maintain list of dependencies and versions
- Keep contact information for critical services
Scaling Considerations
Vertical Scaling:
Start with recommended resources and scale as needed:
- Small (1-10 users): 1GB RAM, 1 CPU
- Medium (10-50 users): 2GB RAM, 2 CPU
- Large (50-200 users): 4GB RAM, 4 CPU
- Enterprise (200+ users): 8GB RAM, 8+ CPU
Database Scaling:
- Start with shared PostgreSQL instance
- Scale to dedicated instance for 50+ users
- Add read replicas for reporting queries
- Consider database connection pooling (PgBouncer)
Storage Scaling:
- Monitor volume usage regularly
- Scale volumes as needed (receipts grow over time)
- Implement cleanup for old receipts if needed
- Consider S3 for long-term receipt storage
Troubleshooting
Application Won’t Start
Check Database Connection:
# Test PostgreSQL connectivitycurl -v telnet://your-postgres-app.klutch.sh:8000Verify Environment Variables:
- Ensure
DATABASE_URLis correctly formatted - Check that all required secrets are set
- Verify JWT_SECRET length (minimum 64 characters)
- Confirm PORT is set to 3000
Check Logs:
Review deployment logs in Klutch.sh dashboard for specific error messages:
- Database connection errors
- Missing environment variables
- Port binding issues
- Node.js runtime errors
Database Connection Issues
Common Problems:
-
Wrong connection string format:
Terminal window # Correct formatDATABASE_URL=postgresql://user:pass@host:8000/dbname# Alternative formatDB_HOST=your-postgres-app.klutch.shDB_PORT=8000DB_NAME=expenseowlDB_USER=expenseowlDB_PASSWORD=your_password -
SSL Issues:
If you see SSL errors, try:
Terminal window DB_SSL=false# OrDATABASE_URL=postgresql://user:pass@host:8000/dbname?sslmode=disable -
Connection Pool Exhaustion:
Increase pool size:
Terminal window DB_POOL_MIN=2DB_POOL_MAX=10
Receipt Upload Failures
Check Volume Mount:
Ensure /app/uploads volume is properly mounted:
# Inside containerls -la /app/uploads# Should be writable by nodejs userCheck File Size Limits:
MAX_UPLOAD_SIZE=10485760 # 10MB in bytesVerify File Types:
ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,application/pdfStorage Space:
Monitor upload volume usage and scale if needed.
Email Delivery Problems
SMTP Configuration:
Test SMTP settings:
# Use a tool like swaks to testswaks --to recipient@example.com \ --from noreply@example.com \ --server smtp.gmail.com:587 \ --auth LOGIN \ --auth-user your-email@gmail.com \ --auth-password your-app-passwordCommon Issues:
- Gmail requires App Passwords, not regular passwords
- Check SMTP port (587 for TLS, 465 for SSL)
- Verify SMTP credentials are correct
- Check spam folders for test emails
- Ensure firewall allows outbound SMTP connections
Performance Issues
Slow Queries:
-- Find slow queriesSELECT query, mean_exec_time, callsFROM pg_stat_statementsORDER BY mean_exec_time DESCLIMIT 10;Database Optimization:
-- Run VACUUM regularlyVACUUM ANALYZE;
-- Check for missing indexesSELECT schemaname, tablename, attname, n_distinct, correlationFROM pg_statsWHERE schemaname = 'public'ORDER BY abs(correlation) DESC;Memory Issues:
- Check Node.js heap size
- Monitor for memory leaks
- Increase container memory if needed
- Review large query results
Login Issues
Password Reset:
If you can’t log in:
-- Connect to database and reset admin password\c expenseowl
-- Update admin password (replace with bcrypt hash)UPDATE usersSET password = '$2b$10$newhashedpassword'WHERE email = 'admin@example.com';Generate new bcrypt hash:
const bcrypt = require('bcrypt');const hash = bcrypt.hashSync('newpassword', 10);console.log(hash);Session Issues:
Clear sessions by restarting the application or:
-- Clear all sessions (if using database sessions)DELETE FROM sessions;Additional Resources
- ExpenseOwl GitHub Repository
- Official ExpenseOwl Documentation
- PostgreSQL Deployment Guide
- Persistent Volumes Documentation
- Deployment Configuration
- Networking and Traffic Types
Conclusion
Deploying ExpenseOwl on Klutch.sh gives you a powerful, privacy-focused expense tracking solution with complete control over your financial data. Whether you’re managing personal expenses, tracking business costs, or monitoring multiple projects, ExpenseOwl provides the features and flexibility you need without subscription fees or privacy concerns. With Klutch.sh’s managed infrastructure, persistent storage, and database integration, your financial data is always secure, accessible, and under your control.
Start tracking your expenses with complete privacy by deploying ExpenseOwl on Klutch.sh today and take control of your financial future.