Skip to content

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
└── .gitignore

Dockerfile

Create a production-ready Dockerfile for ExpenseOwl:

FROM node:20-alpine AS builder
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apk add --no-cache git python3 make g++
# Clone ExpenseOwl repository
RUN git clone https://github.com/your-org/expenseowl.git .
# Install dependencies
RUN npm ci --only=production
# Build the application
RUN npm run build
# Production stage
FROM node:20-alpine
# Install runtime dependencies
RUN apk add --no-cache \
tini \
curl \
ca-certificates
# Create app user for security
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Set working directory
WORKDIR /app
# Copy built application from builder
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./package.json
# Create directories for uploads and data
RUN mkdir -p /app/data /app/uploads && \
chown -R nodejs:nodejs /app/data /app/uploads
# Switch to non-root user
USER nodejs
# Expose application port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Use tini for proper signal handling
ENTRYPOINT ["/sbin/tini", "--"]
# Start the application
CMD ["node", "dist/server.js"]

Environment Variables Template

Create a .env.example file to document required configuration:

Terminal window
# Application Configuration
NODE_ENV=production
PORT=3000
APP_URL=https://example-app.klutch.sh
# Database Configuration (Required)
DATABASE_URL=postgresql://expenseowl:your_secure_password@your-postgres-app.klutch.sh:8000/expenseowl
DB_HOST=your-postgres-app.klutch.sh
DB_PORT=8000
DB_NAME=expenseowl
DB_USER=expenseowl
DB_PASSWORD=your_secure_password
DB_SSL=true
# Authentication & Security (Required)
JWT_SECRET=generate_with_openssl_rand_base64_64
SESSION_SECRET=generate_with_openssl_rand_base64_64
COOKIE_SECRET=generate_with_openssl_rand_base64_32
# Encryption for sensitive data
ENCRYPTION_KEY=generate_with_openssl_rand_hex_32
# Admin Account (Initial Setup)
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change_this_secure_password
ADMIN_NAME=Admin User
# Upload Configuration
UPLOAD_DIR=/app/uploads
MAX_UPLOAD_SIZE=10485760
ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,application/pdf
# Currency Configuration
DEFAULT_CURRENCY=USD
EXCHANGE_RATE_API_KEY=your_exchangerate_api_key
# SMTP Configuration (Optional but recommended)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your_app_password
SMTP_FROM=ExpenseOwl <noreply@example.com>
# Feature Flags
ENABLE_REGISTRATION=true
ENABLE_EMAIL_NOTIFICATIONS=true
ENABLE_MULTI_CURRENCY=true
ENABLE_RECEIPT_OCR=false
ENABLE_BUDGET_ALERTS=true
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Backup Configuration
BACKUP_ENABLED=true
BACKUP_SCHEDULE=0 2 * * *
BACKUP_RETENTION_DAYS=30
# Logging
LOG_LEVEL=info
LOG_FILE=/app/data/logs/expenseowl.log

Docker 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.*
*.md
README.md
docker-compose.yml
.vscode
.idea
*.log
.DS_Store
node_modules
coverage
.nyc_output
*.test.js
*.spec.js
test/
tests/
__tests__
dist
build
uploads/*
data/*
!data/.gitkeep
!uploads/.gitkeep

Git 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/.gitkeep
data/*
!data/.gitkeep
*.sqlite
*.sqlite3
postgres_data/

Deploying on Klutch.sh

  1. 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 database
    CREATE DATABASE expenseowl;
    CREATE USER expenseowl WITH PASSWORD 'your_secure_password';
    GRANT ALL PRIVILEGES ON DATABASE expenseowl TO expenseowl;
  2. 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 privileges
    GRANT ALL ON SCHEMA public TO expenseowl;
    -- ExpenseOwl will automatically create tables on first run
    -- Alternatively, you can run migrations manually if provided
  3. 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 32

    Save these values securely—you’ll need them for your Klutch.sh environment variables.

  4. Create a New Project

    Log in to the Klutch.sh dashboard and create a new project for your ExpenseOwl deployment.

  5. Connect Your GitHub Repository

    In your Klutch.sh project:

    1. Navigate to the Apps section
    2. Click “Create New App”
    3. Select GitHub as your source
    4. Choose the repository containing your ExpenseOwl Dockerfile
    5. Select the branch you want to deploy (typically main or master)
  6. Configure Build Settings

    Klutch.sh will automatically detect your Dockerfile in the repository root. No additional build configuration is needed.

  7. Set Traffic Type

    In the deployment settings:

    1. Select HTTP as the traffic type
    2. Set the internal port to 3000
    3. Klutch.sh will automatically route traffic to your ExpenseOwl application
  8. Configure Environment Variables

    In your Klutch.sh app settings, add the following environment variables:

    Required Variables:

    Terminal window
    NODE_ENV=production
    PORT=3000
    APP_URL=https://example-app.klutch.sh
    DATABASE_URL=postgresql://expenseowl:your_secure_password@your-postgres-app.klutch.sh:8000/expenseowl
    JWT_SECRET=your_generated_jwt_secret_64_bytes
    SESSION_SECRET=your_generated_session_secret_64_bytes
    COOKIE_SECRET=your_generated_cookie_secret_32_bytes
    ENCRYPTION_KEY=your_generated_encryption_key_hex
    ADMIN_EMAIL=admin@example.com
    ADMIN_PASSWORD=your_admin_password

    Optional Variables (Email):

    Terminal window
    SMTP_HOST=smtp.gmail.com
    SMTP_PORT=587
    SMTP_USER=your-email@gmail.com
    SMTP_PASSWORD=your_app_password
    SMTP_FROM=ExpenseOwl <noreply@example.com>

    Optional Variables (Features):

    Terminal window
    DEFAULT_CURRENCY=USD
    ENABLE_REGISTRATION=true
    ENABLE_EMAIL_NOTIFICATIONS=true
    ENABLE_MULTI_CURRENCY=true
    ENABLE_BUDGET_ALERTS=true
    MAX_UPLOAD_SIZE=10485760

    Mark all sensitive values like DATABASE_URL, JWT_SECRET, SESSION_SECRET, ENCRYPTION_KEY, ADMIN_PASSWORD, and SMTP_PASSWORD as secrets in the Klutch.sh dashboard.

  9. Attach Persistent Volumes

    ExpenseOwl needs persistent storage for receipt uploads and application data:

    Upload Volume:

    1. In your Klutch.sh app settings, navigate to Volumes
    2. Click “Add Volume”
    3. Set the mount path to /app/uploads
    4. Choose an appropriate size (start with 10GB for receipt storage)
    5. Save the volume configuration

    Data Volume:

    1. Click “Add Volume” again
    2. Set the mount path to /app/data
    3. Choose an appropriate size (start with 5GB for logs and application data)
    4. Save the volume configuration
  10. Deploy the Application

    Click “Deploy” to start the deployment process. Klutch.sh will:

    1. Pull your code from GitHub
    2. Build the Docker image using the multi-stage Dockerfile
    3. Configure networking and volumes
    4. Start your ExpenseOwl server

    The initial deployment typically takes 3-5 minutes depending on the build complexity.

  11. Verify Deployment

    Once deployed, verify your ExpenseOwl instance:

    Terminal window
    # Check health endpoint
    curl https://example-app.klutch.sh/health
    # Expected response
    {
    "status": "healthy",
    "database": "connected",
    "uptime": 123
    }

Initial Configuration

First-Time Setup

    1. Access the Application

      Navigate to your ExpenseOwl URL (e.g., https://example-app.klutch.sh)

    2. Admin Login

      Log in with the admin credentials you configured:

      • Email: The value from ADMIN_EMAIL
      • Password: The value from ADMIN_PASSWORD
    3. Change Admin Password

      Immediately change the default admin password:

      • Go to SettingsAccount
      • Click “Change Password”
      • Enter a strong, unique password
      • Save changes
    4. Configure Application Settings

      • Navigate to SettingsGeneral
      • Set your organization name
      • Configure default currency
      • Set date and time format preferences
      • Configure fiscal year start date

Set Up Expense Categories

    1. Navigate to SettingsCategories

    2. 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
    3. Assign category colors for easy visual identification

    4. Set default budgets for each category (optional)

Configure Budgets

    1. Navigate to BudgetsCreate Budget
    2. Set monthly budget limits:
      • Select category
      • Set monthly budget amount
      • Enable alerts at 80% and 100% thresholds
    3. Create budgets for all your active categories
    4. Review and adjust budgets monthly based on spending patterns

Using ExpenseOwl

Adding Expenses

Manual Entry:

    1. Click “Add Expense” from the dashboard
    2. 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
    3. Click “Save”

Quick Add:

Use the quick add feature for rapid expense entry:

  1. Click the floating ”+” button
  2. Enter amount and category
  3. Click “Add”
  4. Details can be edited later

Bulk Import:

Import expenses from CSV or Excel:

    1. Navigate to ImportUpload File
    2. Download the CSV template
    3. Fill in your expense data
    4. Upload the completed file
    5. Map columns to ExpenseOwl fields
    6. Review and confirm import

Managing Receipts

Upload Receipts:

    1. Open an expense
    2. Click “Attach Receipt”
    3. Upload image (JPEG, PNG, GIF) or PDF
    4. 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:

    1. Navigate to ReportsExpense Report
    2. Configure report parameters:
      • Date range (This Month, Last Month, Year to Date, Custom)
      • Categories to include
      • Tags to filter by
      • Projects to include
    3. Generate report
    4. View visual charts and summaries
    5. Export to PDF or Excel

Budget Reports:

    1. Navigate to ReportsBudget Report
    2. View budget vs. actual spending
    3. Identify over-budget categories
    4. Analyze spending trends
    5. Export for review

Tax Reports:

    1. Navigate to ReportsTax Report
    2. Select tax year
    3. Filter business-related expenses
    4. Include receipt attachments
    5. Export comprehensive tax package

Multi-Currency Tracking

Enable Multi-Currency:

Set ENABLE_MULTI_CURRENCY=true in your environment variables.

Add Currencies:

    1. Navigate to SettingsCurrencies
    2. Add currencies you use
    3. Set your primary currency
    4. Configure exchange rate update frequency

Track Foreign Expenses:

    1. When adding an expense, select the transaction currency
    2. Enter the amount in the foreign currency
    3. ExpenseOwl converts to your default currency using current exchange rates
    4. 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 example
const 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;
}
// Usage
const 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 expense
const 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 requests
from 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()
# Usage
token = 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 2025
const 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:

Terminal window
# Get monthly expense report
curl -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 report
curl -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.pdf

Upload 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();
}
// Usage
const result = await uploadReceipt(token, 123, './receipt.jpg');
console.log('Receipt uploaded:', result.receiptUrl);

Webhook Integration

Set Up Webhook for Budget Alerts:

// Express.js webhook receiver
const 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:

    1. 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)
    2. Configure DNS:

      • Add a CNAME record in your DNS provider:

        Type: CNAME
        Name: expenses
        Value: example-app.klutch.sh
        TTL: 3600
    3. Update Environment Variable:

      Terminal window
      APP_URL=https://expenses.example.com
    4. 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:

Terminal window
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-specific-password
SMTP_FROM=ExpenseOwl <noreply@example.com>

Note: Use an App Password for Gmail.

Using SendGrid:

Terminal window
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASSWORD=your-sendgrid-api-key
SMTP_FROM=ExpenseOwl <noreply@example.com>

Using AWS SES:

Terminal window
SMTP_HOST=email-smtp.us-east-1.amazonaws.com
SMTP_PORT=587
SMTP_USER=your-ses-smtp-username
SMTP_PASSWORD=your-ses-smtp-password
SMTP_FROM=ExpenseOwl <noreply@example.com>

Multi-User Setup

Configure user management and permissions:

    1. Enable User Registration:

      Terminal window
      ENABLE_REGISTRATION=true

      Or keep it false and create users manually through the admin panel.

    2. Create Additional Users:

      • Log in as admin
      • Navigate to SettingsUsers
      • Click “Add User”
      • Enter user details and role
      • Send invitation email
    3. 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:

Terminal window
BACKUP_ENABLED=true
BACKUP_SCHEDULE=0 2 * * *
BACKUP_RETENTION_DAYS=30

Manual Backup Script:

backup-expenseowl.sh
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"
BACKUP_FILE="expenseowl_backup_${DATE}.sql"
# Backup PostgreSQL database
pg_dump -h your-postgres-app.klutch.sh -p 8000 \
-U expenseowl -d expenseowl > "${BACKUP_DIR}/${BACKUP_FILE}"
# Compress backup
gzip "${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):

Terminal window
EXCHANGE_RATE_API_KEY=your-free-api-key
EXCHANGE_RATE_UPDATE_FREQUENCY=daily

Using Paid API (currencyapi.com):

Terminal window
EXCHANGE_RATE_API_KEY=your-api-key
EXCHANGE_RATE_PROVIDER=currencyapi
EXCHANGE_RATE_UPDATE_FREQUENCY=hourly

Production Best Practices

Security Hardening

Environment Variables:

  • Never commit .env files 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:

Terminal window
# Rotate all security keys
JWT_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:

    1. Create Indexes:

      -- Connect to database
      \c expenseowl
      -- Create indexes for common queries
      CREATE 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 tables
      ANALYZE expenses;
      ANALYZE categories;
      ANALYZE users;
    2. Regular Maintenance:

      -- Run weekly
      VACUUM ANALYZE expenses;
      VACUUM ANALYZE categories;
      -- Check table sizes
      SELECT
      schemaname,
      tablename,
      pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
      FROM pg_tables
      WHERE schemaname = 'public'
      ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

Application Caching:

Terminal window
# Enable caching for better performance
CACHE_ENABLED=true
CACHE_TTL=3600
REDIS_URL=redis://your-redis-app.klutch.sh:8000

File 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:

monitor-expenseowl.sh
#!/bin/bash
HEALTH_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_health

Key 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:

Terminal window
# View recent errors
tail -f /app/data/logs/expenseowl.log | grep ERROR
# Monitor API requests
tail -f /app/data/logs/expenseowl.log | grep "POST /api/"

Backup and Recovery

Database Backup Strategy:

    1. Automated Daily Backups:

      Terminal window
      # Add to cron (runs at 2 AM daily)
      0 2 * * * /path/to/backup-expenseowl.sh
    2. Test Restore Procedure:

      Terminal window
      # Restore from backup
      gunzip expenseowl_backup_20251219.sql.gz
      psql -h your-postgres-app.klutch.sh -p 8000 \
      -U expenseowl -d expenseowl < expenseowl_backup_20251219.sql
    3. Off-Site Backup Storage:

      • Store backups in S3, Backblaze B2, or similar
      • Use lifecycle policies for retention
      • Encrypt backups before uploading

Upload Backup:

Terminal window
# Sync uploads directory to backup location
rsync -avz /app/uploads/ /backup/uploads/
# Or use S3
aws s3 sync /app/uploads/ s3://your-bucket/expenseowl/uploads/

Disaster Recovery Plan:

    1. Document restoration procedures
    2. Keep environment variables in secure password manager
    3. Test recovery process quarterly
    4. Maintain list of dependencies and versions
    5. 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:

Terminal window
# Test PostgreSQL connectivity
curl -v telnet://your-postgres-app.klutch.sh:8000

Verify Environment Variables:

  • Ensure DATABASE_URL is 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:

  1. Wrong connection string format:

    Terminal window
    # Correct format
    DATABASE_URL=postgresql://user:pass@host:8000/dbname
    # Alternative format
    DB_HOST=your-postgres-app.klutch.sh
    DB_PORT=8000
    DB_NAME=expenseowl
    DB_USER=expenseowl
    DB_PASSWORD=your_password
  2. SSL Issues:

    If you see SSL errors, try:

    Terminal window
    DB_SSL=false
    # Or
    DATABASE_URL=postgresql://user:pass@host:8000/dbname?sslmode=disable
  3. Connection Pool Exhaustion:

    Increase pool size:

    Terminal window
    DB_POOL_MIN=2
    DB_POOL_MAX=10

Receipt Upload Failures

Check Volume Mount:

Ensure /app/uploads volume is properly mounted:

Terminal window
# Inside container
ls -la /app/uploads
# Should be writable by nodejs user

Check File Size Limits:

Terminal window
MAX_UPLOAD_SIZE=10485760 # 10MB in bytes

Verify File Types:

Terminal window
ALLOWED_FILE_TYPES=image/jpeg,image/png,image/gif,application/pdf

Storage Space:

Monitor upload volume usage and scale if needed.

Email Delivery Problems

SMTP Configuration:

Test SMTP settings:

Terminal window
# Use a tool like swaks to test
swaks --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-password

Common 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 queries
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

Database Optimization:

-- Run VACUUM regularly
VACUUM ANALYZE;
-- Check for missing indexes
SELECT schemaname, tablename, attname, n_distinct, correlation
FROM pg_stats
WHERE 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 users
SET 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

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.