Skip to content

Deploying Espial

Espial is an open-source, web-based bookmarking server built with Haskell and PureScript that prioritizes simplicity, performance, and data ownership. Designed for self-hosting scenarios, Espial stores bookmarks in a lightweight SQLite database, making deployment and maintenance remarkably straightforward. Whether you’re managing personal bookmarks, organizing research materials, curating content for a team, or building a knowledge base, Espial offers a clean, efficient platform with multi-user support, instant bookmark saving via bookmarklet, fast full-text search, tag-based organization, import from Pinboard and Firefox formats, complete REST API access, and mobile support through an Android app. With its minimal resource footprint and robust architecture, Espial delivers a powerful bookmarking solution that respects your data and runs efficiently on modern infrastructure.

Why Espial?

Espial stands out as a premier choice for self-hosted bookmark management:

  • Multi-user Support: Host bookmarks for multiple users on a single instance
  • SQLite Storage: Lightweight database requiring no separate database server
  • Bookmarklet Integration: One-click bookmark saving from any browser
  • Fast Search: Full-text search across all bookmarks with instant results
  • Tag Organization: Flexible tagging system for categorizing bookmarks
  • Privacy & Tags: Mark bookmarks as private and control visibility
  • Import Capabilities: Import from Pinboard JSON and Firefox bookmarks
  • Export Options: Export your data anytime in standard formats
  • REST API: Complete API for automation and third-party integrations
  • Android App: Native Android app for sharing bookmarks
  • Modern UI: Clean, responsive interface built with PureScript
  • Minimal Dependencies: Self-contained application with few external requirements
  • Performance: Built with Haskell for excellent performance and reliability
  • Open Source: AGPL-3.0 licensed with active development
  • Self-hosted Control: Complete ownership of your bookmark data
  • No Tracking: Privacy-first design with no analytics or data collection
  • Low Resource Usage: Runs efficiently with minimal CPU and memory
  • Easy Backup: Single SQLite file makes backups trivial
  • Custom Domains: Use your own domain for professional bookmark management
  • User Profiles: Public bookmark profiles at /u:username URLs

Espial is ideal for individuals seeking privacy-focused bookmark management, small teams sharing curated resources, researchers organizing sources and references, content curators building collections, developers bookmarking documentation and code examples, and anyone wanting complete control over their bookmark data. With persistent storage on Klutch.sh, your bookmarks are always secure, searchable, and accessible.

Prerequisites

Before deploying Espial, ensure you have:

  • A Klutch.sh account
  • A GitHub repository for your Espial deployment configuration
  • Basic familiarity with Docker and Git
  • Sufficient storage for bookmark database (typically 5-20GB depending on collection size)
  • HTTPS enabled for secure access (automatically provided by Klutch.sh)
  • Optional: Existing Pinboard or Firefox bookmarks for import
  • Optional: Custom domain for professional bookmark URL
  • Understanding of user management for multi-user scenarios

Important Considerations

Database Storage: Espial uses SQLite which stores everything in a single file. Regular backups of this file are essential. The database grows based on the number of bookmarks, tags, and users.

User Management: Users are created via CLI commands. Plan your user structure before deployment if hosting for multiple people. Username changes require database modifications.

Import Process: Importing large bookmark collections (10,000+ bookmarks) can take several minutes. Import operations should be done during low-traffic periods.

Bookmarklet Setup: Each user needs to configure the bookmarklet from their Settings page after account creation. This enables one-click bookmark saving from any website.

API Access: The REST API requires authentication. Store API keys securely if using automated bookmark management or integrations.

Installation and Setup

Step 1: Create Your Project Directory

Create a new directory for your Espial deployment:

Terminal window
mkdir espial-klutch
cd espial-klutch
git init

Step 2: Create the Dockerfile

Create a Dockerfile that sets up Espial with the pre-built image:

FROM jonschoning/espial:espial
WORKDIR /app
# Create data directory for SQLite database
RUN mkdir -p /app/data
# Copy migration binary for user management
COPY --from=jonschoning/espial:espial /app/migration /app/migration
# Expose port 3000 (Espial's default HTTP port)
EXPOSE 3000
# Set environment variables with defaults
ENV PORT=3000
ENV SQLITE_DATABASE=/app/data/espial.sqlite3
ENV IP_FROM_HEADER=true
ENV SSL_ONLY=false
ENV DETAILED_LOGGING=false
ENV SHOULD_LOG_ALL=false
# Health check to ensure Espial is running
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/ || exit 1
# Start Espial server
CMD ["./espial", "+RTS", "-T"]

Step 3: Create Initialization Script

Create an init.sh script for initial setup tasks:

#!/bin/bash
set -e
echo "Espial Initialization Script"
echo "=============================="
# Wait for data directory to be available
while [ ! -d "/app/data" ]; do
echo "Waiting for data volume..."
sleep 2
done
echo "Data directory available at /app/data"
# Check if database exists
if [ ! -f "$SQLITE_DATABASE" ]; then
echo "Creating new Espial database..."
./migration createdb --conn "$SQLITE_DATABASE"
echo "Database created successfully!"
else
echo "Existing database found at $SQLITE_DATABASE"
fi
echo "Espial is ready to start!"
echo "Use './migration createuser' to create users"
echo "Database path: $SQLITE_DATABASE"
# Execute the main command
exec "$@"

Make it executable:

Terminal window
chmod +x init.sh

Step 4: Create Environment Configuration

Create a .env.example file for reference:

Terminal window
# Espial Configuration
# Server Configuration
PORT=3000
SQLITE_DATABASE=/app/data/espial.sqlite3
# Security Settings
IP_FROM_HEADER=true
SSL_ONLY=false
# Logging Configuration
DETAILED_LOGGING=false
SHOULD_LOG_ALL=false
# Optional: SOCKS Proxy for Archive.org
# ARCHIVE_SOCKS_PROXY_HOST=localhost
# ARCHIVE_SOCKS_PROXY_PORT=8888
# Optional: Custom Source Code URI
# SOURCE_CODE_URI=https://github.com/jonschoning/espial
# Optional: Allow non-HTTP URL schemes
# ALLOW_NON_HTTP_URL_SCHEMES=false

Step 5: Create .gitignore

Create a .gitignore file:

.env
.env.local
*.sqlite3
*.sqlite3-journal
*.sqlite3-wal
*.sqlite3-shm
data/
logs/
backups/
.DS_Store
node_modules/
*.log

Step 6: Create README

Create a README.md file:

# Espial Deployment on Klutch.sh
This repository contains the configuration for deploying Espial on Klutch.sh.
## Deployment
1. Push this repository to GitHub
2. Create a new app on Klutch.sh
3. Connect to this repository
4. Configure environment variables
5. Attach persistent volume to `/app/data`
6. Deploy the application
## Creating Users
After deployment, create users using the migration CLI:
```bash
# Access the container via Klutch.sh terminal or docker exec
./migration createuser --conn /app/data/espial.sqlite3 --userName myusername --userPassword mypassword

Importing Bookmarks

Import from Pinboard JSON format:

Terminal window
./migration importbookmarks --conn /app/data/espial.sqlite3 --userName myusername --bookmarkFile /app/data/bookmarks.json

Import from Firefox:

Terminal window
./migration importfirefoxbookmarks --conn /app/data/espial.sqlite3 --userName myusername --bookmarkFile /app/data/firefox-bookmarks.json

Environment Variables

See .env.example for configuration options.

Access

After deployment, access Espial at: https://your-app.klutch.sh

User profiles available at: https://your-app.klutch.sh/u:username

### Step 7: Commit and Push to GitHub
Initialize your repository and push to GitHub:
```bash
git add .
git commit -m "Initial Espial deployment configuration"
git branch -M main
git remote add origin https://github.com/yourusername/espial-klutch.git
git push -u origin main

Deploying to Klutch.sh

Deployment Steps

    1. Log in to Klutch.sh

      Navigate to klutch.sh/app and sign in to your account.

    2. Create a New Project

      Click “Create Project” and give it a descriptive name like “Espial Bookmarks” or “Personal Bookmark Server”.

    3. Create a New App

      Navigate to “Create App” within your project:

      • App Name: Give it a clear name like “espial-production”
      • Git Source: Select GitHub
      • Repository: Choose your Espial repository
      • Branch: Select main or your deployment branch
    4. Configure Traffic Type

      • Traffic Type: Select HTTP (Espial serves web pages)
      • Internal Port: Set to 3000 (Espial’s default HTTP port)
    5. Set Environment Variables

      Configure these essential environment variables in the Klutch.sh dashboard:

      Core Configuration:

      • PORT: Set to 3000 (Espial’s listening port)
      • SQLITE_DATABASE: Set to /app/data/espial.sqlite3 (database location)
      • IP_FROM_HEADER: Set to true (required for reverse proxy)
      • SSL_ONLY: Set to false (Klutch.sh handles SSL termination)

      Optional - Logging:

      • DETAILED_LOGGING: Set to true for verbose logs (useful for troubleshooting)
      • SHOULD_LOG_ALL: Set to true to log all requests

      Optional - Archive Integration:

      • ARCHIVE_SOCKS_PROXY_HOST: SOCKS proxy host for Archive.org integration
      • ARCHIVE_SOCKS_PROXY_PORT: SOCKS proxy port (typically 8888)

      Optional - Customization:

      • SOURCE_CODE_URI: Link to your fork if you’ve customized Espial
      • ALLOW_NON_HTTP_URL_SCHEMES: Set to true to allow bookmarking non-HTTP URLs
    6. Attach Persistent Volume

      Espial requires persistent storage for the SQLite database:

      Database Volume:

      • Mount Path: /app/data
      • Size: Start with 10GB minimum (20GB+ recommended for large collections)

      This volume stores:

      • SQLite database file (espial.sqlite3)
      • Database journal files
      • Imported bookmark files
      • User data and preferences

      Important: Without persistent storage, all bookmarks will be lost when the container restarts.

    7. Configure Additional Settings

      • Region: Select the region closest to your users for optimal performance
      • Compute Resources:
        • Minimum: 1 CPU, 1GB RAM (suitable for personal use, 1-2 users)
        • Recommended: 1 CPU, 2GB RAM (up to 5 users with moderate usage)
        • High Usage: 2 CPU, 4GB RAM (10+ users or large bookmark collections)
    8. Deploy Your Application

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

      • Detect your Dockerfile automatically
      • Build the Docker image with Espial
      • Attach the persistent volume to /app/data
      • Start your Espial container
      • Provision SSL certificate for HTTPS access
      • Assign a URL for accessing your instance
    9. Access Your Espial Instance

      Once deployment completes, access Espial at your assigned URL:

      • Main Application: https://example-app.klutch.sh/
      • API Endpoint: https://example-app.klutch.sh/api/

      Note: You’ll need to create user accounts before you can log in.

Creating Users

After deployment, create user accounts using the migration CLI tool. Access your container via the Klutch.sh terminal or SSH:

Create a User

Terminal window
./migration createuser \
--conn /app/data/espial.sqlite3 \
--userName alice \
--userPassword secure_password_here

Create Multiple Users

Terminal window
# Create admin user
./migration createuser --conn /app/data/espial.sqlite3 --userName admin --userPassword admin_password
# Create team members
./migration createuser --conn /app/data/espial.sqlite3 --userName bob --userPassword bob_password
./migration createuser --conn /app/data/espial.sqlite3 --userName carol --userPassword carol_password

Security Note: Change default passwords immediately after creation. Use strong, unique passwords for each user.

Importing Bookmarks

Espial supports importing bookmarks from Pinboard and Firefox formats.

Import from Pinboard

Export your Pinboard bookmarks to JSON format, upload to your container, then import:

Terminal window
# Upload bookmarks.json to /app/data/
# Then import for a specific user
./migration importbookmarks \
--conn /app/data/espial.sqlite3 \
--userName alice \
--bookmarkFile /app/data/pinboard-bookmarks.json

Pinboard JSON Format: Each line should contain a bookmark object with fields: href, description, extended, time, shared, toread, tags.

Example pinboard-bookmarks.json:

[
{"href":"https://example.com/article","description":"Example Article","extended":"Detailed notes about this article","time":"2024-01-15T10:30:00Z","shared":"yes","toread":"no","tags":"technology programming"},
{"href":"https://example.org/tutorial","description":"Tutorial: Getting Started","extended":"","time":"2024-01-16T14:20:00Z","shared":"yes","toread":"yes","tags":"tutorial learning"}
]

Import from Firefox

Export bookmarks from Firefox as JSON, then import:

Terminal window
# In Firefox: Bookmarks → Manage Bookmarks → Import and Backup → Export Bookmarks to JSON
# Upload firefox-bookmarks.json to /app/data/
# Import into Espial
./migration importfirefoxbookmarks \
--conn /app/data/espial.sqlite3 \
--userName alice \
--bookmarkFile /app/data/firefox-bookmarks.json

Import from Multiple Sources

You can import from multiple sources for the same user. Duplicate URLs are typically handled gracefully:

Terminal window
# Import Pinboard bookmarks
./migration importbookmarks --conn /app/data/espial.sqlite3 --userName alice --bookmarkFile /app/data/pinboard.json
# Import Firefox bookmarks
./migration importfirefoxbookmarks --conn /app/data/espial.sqlite3 --userName alice --bookmarkFile /app/data/firefox.json

Using Espial

Adding Bookmarks via Bookmarklet

  1. Log in to your Espial instance
  2. Navigate to Settings page
  3. Drag the bookmarklet to your browser’s bookmarks bar
  4. When browsing any page, click the bookmarklet to save it to Espial
  5. Add tags and notes in the popup form
  6. Save the bookmark

Organizing with Tags

  • Use tags to categorize bookmarks: technology, programming, tutorial, reference
  • Multiple tags supported per bookmark: python tutorial web-development
  • Search by tag using the tag cloud or search interface
  • Tag-based filtering combines multiple criteria

Searching Bookmarks

Espial provides powerful search capabilities:

  • Full-text Search: Search across titles, descriptions, and URLs
  • Tag Filtering: Click tags to filter bookmarks
  • Combined Search: Use search terms with tag filters simultaneously
  • Quick Results: Search results appear instantly as you type

Privacy Controls

  • Private Bookmarks: Mark bookmarks as private (visible only to you)
  • Public Profiles: Share your public bookmarks at /u:yourusername
  • Shared Collections: Other users can discover your public bookmarks
  • Visibility Toggle: Change bookmark privacy settings anytime

REST API Access

Espial provides a complete REST API for automation and integrations.

Base URL: https://example-app.klutch.sh/api/

Authentication: Most endpoints require authentication. Obtain an API key from your Settings page.

Common Endpoints:

  • GET /api/bookmarks - List all bookmarks
  • POST /api/bookmarks - Add a new bookmark
  • GET /api/bookmarks/:id - Get specific bookmark
  • PUT /api/bookmarks/:id - Update bookmark
  • DELETE /api/bookmarks/:id - Delete bookmark
  • GET /api/tags - List all tags

Mobile Access via Android App

Install the Espial Share Android app to save bookmarks directly from your Android device using the Share intent.

Setup:

  1. Install the app from the repository
  2. Configure your Espial instance URL
  3. Authenticate with your credentials
  4. Use Android’s Share menu to send URLs to Espial

Sample API Integration Code

Node.js Example

const axios = require('axios');
const ESPIAL_URL = 'https://example-app.klutch.sh';
const API_KEY = 'your-api-key-here';
// Add a bookmark
async function addBookmark(url, title, description, tags, isPrivate = false) {
try {
const response = await axios.post(`${ESPIAL_URL}/api/bookmarks`, {
href: url,
description: title,
extended: description,
tags: tags,
private: isPrivate
}, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
});
console.log('Bookmark added:', response.data);
return response.data;
} catch (error) {
console.error('Error adding bookmark:', error.response?.data || error.message);
}
}
// Search bookmarks
async function searchBookmarks(query) {
try {
const response = await axios.get(`${ESPIAL_URL}/api/bookmarks`, {
params: { q: query },
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
console.log('Search results:', response.data);
return response.data;
} catch (error) {
console.error('Error searching bookmarks:', error.response?.data || error.message);
}
}
// Usage
(async () => {
await addBookmark(
'https://example.com/article',
'Interesting Article',
'Notes about this article',
'technology programming',
false
);
await searchBookmarks('programming');
})();

Python Example

import requests
import json
ESPIAL_URL = 'https://example-app.klutch.sh'
API_KEY = 'your-api-key-here'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
# Add a bookmark
def add_bookmark(url, title, description, tags, is_private=False):
data = {
'href': url,
'description': title,
'extended': description,
'tags': tags,
'private': is_private
}
response = requests.post(
f'{ESPIAL_URL}/api/bookmarks',
headers=headers,
json=data
)
if response.status_code == 200:
print('Bookmark added:', response.json())
return response.json()
else:
print('Error:', response.status_code, response.text)
return None
# Get all bookmarks
def get_bookmarks(tag=None, limit=100):
params = {'limit': limit}
if tag:
params['tag'] = tag
response = requests.get(
f'{ESPIAL_URL}/api/bookmarks',
headers=headers,
params=params
)
if response.status_code == 200:
return response.json()
else:
print('Error:', response.status_code, response.text)
return None
# Delete a bookmark
def delete_bookmark(bookmark_id):
response = requests.delete(
f'{ESPIAL_URL}/api/bookmarks/{bookmark_id}',
headers=headers
)
if response.status_code == 200:
print(f'Bookmark {bookmark_id} deleted successfully')
return True
else:
print('Error:', response.status_code, response.text)
return False
# Usage
if __name__ == '__main__':
# Add a new bookmark
add_bookmark(
'https://example.com/tutorial',
'Python Tutorial',
'Comprehensive Python learning resource',
'python tutorial programming',
False
)
# Get bookmarks with tag filter
bookmarks = get_bookmarks(tag='python')
print(f'Found {len(bookmarks)} Python bookmarks')
# Delete a bookmark (replace with actual ID)
# delete_bookmark(123)

Go Example

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
EspialURL = "https://example-app.klutch.sh"
APIKey = "your-api-key-here"
)
type Bookmark struct {
Href string `json:"href"`
Description string `json:"description"`
Extended string `json:"extended"`
Tags string `json:"tags"`
Private bool `json:"private"`
}
// Add a bookmark
func AddBookmark(url, title, description, tags string, isPrivate bool) error {
bookmark := Bookmark{
Href: url,
Description: title,
Extended: description,
Tags: tags,
Private: isPrivate,
}
jsonData, err := json.Marshal(bookmark)
if err != nil {
return fmt.Errorf("error marshaling JSON: %w", err)
}
req, err := http.NewRequest("POST", EspialURL+"/api/bookmarks", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+APIKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Bookmark added: %s\n", string(body))
return nil
}
// Get bookmarks
func GetBookmarks(tag string) ([]Bookmark, error) {
url := fmt.Sprintf("%s/api/bookmarks", EspialURL)
if tag != "" {
url += fmt.Sprintf("?tag=%s", tag)
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+APIKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error sending request: %w", err)
}
defer resp.Body.Close()
var bookmarks []Bookmark
if err := json.NewDecoder(resp.Body).Decode(&bookmarks); err != nil {
return nil, fmt.Errorf("error decoding response: %w", err)
}
return bookmarks, nil
}
func main() {
// Add a bookmark
err := AddBookmark(
"https://example.com/go-tutorial",
"Go Programming Tutorial",
"Comprehensive guide to Go",
"golang programming tutorial",
false,
)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
// Get bookmarks by tag
bookmarks, err := GetBookmarks("golang")
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Found %d Go bookmarks\n", len(bookmarks))
}
}

Database Management

Backup the Database

Regular backups are essential. The entire database is a single SQLite file:

Terminal window
# Create a backup
cp /app/data/espial.sqlite3 /app/data/backups/espial-$(date +%Y%m%d-%H%M%S).sqlite3
# Automated daily backup script
#!/bin/bash
BACKUP_DIR="/app/data/backups"
DB_FILE="/app/data/espial.sqlite3"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
cp "$DB_FILE" "$BACKUP_DIR/espial-$TIMESTAMP.sqlite3"
# Keep only last 30 days of backups
find "$BACKUP_DIR" -name "espial-*.sqlite3" -mtime +30 -delete
echo "Backup completed: espial-$TIMESTAMP.sqlite3"

Restore from Backup

Terminal window
# Stop Espial application first
# Replace current database with backup
cp /app/data/backups/espial-20240115-120000.sqlite3 /app/data/espial.sqlite3
# Restart Espial application

Optimize Database

For large databases, periodic optimization improves performance:

Terminal window
sqlite3 /app/data/espial.sqlite3 "VACUUM;"
sqlite3 /app/data/espial.sqlite3 "ANALYZE;"

Check Database Size

Terminal window
du -h /app/data/espial.sqlite3
sqlite3 /app/data/espial.sqlite3 "SELECT COUNT(*) as total_bookmarks FROM bookmarks;"
sqlite3 /app/data/espial.sqlite3 "SELECT COUNT(*) as total_users FROM users;"

Production Best Practices

Security Hardening

  1. Strong Passwords: Enforce strong passwords for all users
  2. HTTPS Only: Always use HTTPS (automatically provided by Klutch.sh)
  3. API Key Protection: Store API keys securely, rotate regularly
  4. Private by Default: Encourage users to mark sensitive bookmarks as private
  5. Regular Updates: Keep Espial updated to the latest stable version
  6. Access Logs: Enable detailed logging for security auditing

Performance Optimization

  1. Database Indexing: SQLite automatically indexes, but verify for large collections
  2. Regular VACUUM: Run VACUUM monthly on large databases
  3. Resource Allocation: Increase RAM for instances with 10,000+ bookmarks
  4. CDN Integration: Use CDN for static assets if serving many users
  5. Search Optimization: Encourage specific searches over broad queries
  6. Connection Pooling: Espial handles this internally

Monitoring and Observability

  1. Application Logs: Monitor logs for errors and slow queries
  2. Database Size: Track growth trends to plan storage expansion
  3. User Activity: Monitor active users and bookmark creation rates
  4. API Usage: Track API endpoint usage for rate limiting decisions
  5. Response Times: Monitor page load times and API latency
  6. Backup Verification: Regularly test backup restore procedures

Backup Strategy

  1. Daily Automated Backups: Schedule daily SQLite file backups
  2. Off-site Storage: Store backups outside the primary server
  3. Retention Policy: Keep 30 days of daily backups, 12 months of monthly backups
  4. Backup Testing: Quarterly restore testing to verify backup integrity
  5. Export Functionality: Regular exports in standard bookmark formats
  6. Disaster Recovery Plan: Document restore procedures

Scaling Considerations

  1. Vertical Scaling: Increase CPU/RAM as user count grows
  2. Storage Planning: Estimate 50-100MB per 10,000 bookmarks
  3. Read Replicas: For very large instances, consider read replicas (requires application changes)
  4. User Limits: Set reasonable limits per instance (recommend max 50 active users per instance)
  5. Multi-instance: Deploy separate instances for distinct user groups if needed

User Management Best Practices

  1. Onboarding Documentation: Provide users with bookmarklet setup guide
  2. Tag Guidelines: Establish tagging conventions for teams
  3. Privacy Education: Explain private vs. public bookmark implications
  4. Import Assistance: Help users migrate from other bookmark services
  5. Regular Cleanup: Encourage users to review and archive old bookmarks
  6. Feedback Loop: Collect user feedback for improvement opportunities

Troubleshooting

Cannot Access Espial

  • Verify your app is running in the Klutch.sh dashboard
  • Check that internal port is set to 3000
  • Ensure HTTP traffic type is selected (not TCP)
  • Review deployment logs for startup errors
  • Verify IP_FROM_HEADER=true environment variable is set

Cannot Log In

  • Verify user exists: Check using migration CLI
  • Confirm password is correct
  • Check database file exists at /app/data/espial.sqlite3
  • Review application logs for authentication errors
  • Ensure database isn’t corrupted (restore from backup if needed)

Bookmarklet Not Working

  • Verify you’re logged in to Espial
  • Check that bookmarklet code includes correct domain
  • Try dragging bookmarklet to bookmark bar again
  • Test with different browser to isolate browser-specific issues
  • Check browser console for JavaScript errors

Import Fails

  • Verify bookmark file format matches expected structure (Pinboard JSON or Firefox JSON)
  • Check file permissions in /app/data/ directory
  • Ensure sufficient disk space for import operation
  • Review logs for specific import errors
  • Try importing smaller batches if file is very large
  • Validate JSON syntax in bookmark file

Database Errors

  • Check database file isn’t locked by another process

  • Verify SQLite database file isn’t corrupted:

    Terminal window
    sqlite3 /app/data/espial.sqlite3 "PRAGMA integrity_check;"
  • Ensure sufficient disk space in persistent volume

  • Review file permissions on database file

  • Restore from most recent backup if corruption detected

Performance Issues

  • Check database size and optimize if over 1GB:

    Terminal window
    sqlite3 /app/data/espial.sqlite3 "VACUUM; ANALYZE;"
  • Increase compute resources (CPU/RAM) in Klutch.sh

  • Review logs for slow queries

  • Consider database rebuild for very large collections

  • Monitor concurrent user count

Search Not Working

  • Verify full-text search indexes exist in database
  • Rebuild search indexes if needed (requires database tools)
  • Check for SQLite version compatibility
  • Try restarting application to reload indexes
  • Review search queries for special characters causing issues

API Errors

  • Verify API key is correct and not expired
  • Check request headers include proper authentication
  • Ensure Content-Type is set to application/json for POST/PUT requests
  • Review API endpoint documentation for correct parameters
  • Check application logs for detailed error messages
  • Verify CORS settings if accessing from web browser

Additional Resources

Conclusion

Espial provides a powerful, privacy-focused bookmarking platform that’s perfect for self-hosting on Klutch.sh. With its efficient SQLite storage, multi-user support, and comprehensive API, Espial scales from personal bookmark management to team collaboration. The combination of Espial’s Haskell-powered performance and Klutch.sh’s infrastructure provides a reliable, secure platform for managing your bookmarks.

Whether you’re an individual seeking control over your bookmark data, a small team sharing curated resources, or a researcher organizing extensive reference collections, Espial delivers the features and flexibility needed for effective bookmark management. Deploy Espial on Klutch.sh today and take control of your bookmarks with a solution that respects your privacy and data ownership.