Skip to main content
This guide shows you how to set up, update, and maintain a self-hosted Sure application with Docker Compose.

Prerequisites

  • Docker Engine installed and running
  • Basic familiarity with the command line

Installation

Install Docker

  1. Follow the official Docker installation guide
  2. Start the Docker service on your machine
  3. Verify the installation:
If Docker is set up correctly, this command will succeed.

Create your application directory

Create a directory where your app will run:

Download the Docker Compose file

Download the sample compose file from the Sure repository:
This creates a compose.yml file in your current directory with the default configuration.

Configuration

By default, the compose.example.yml file runs without any configuration. For production deployments or if you’re running outside of a local network, follow these steps to add security.

Email configuration

To enable email notifications and password resets, configure SMTP settings in your .env file:

SSL/TLS options

For SMTP servers with custom SSL certificates or self-signed certificates: Skip TLS verification (not recommended for production):
Use custom CA certificate:
The SSL_CA_FILE option allows you to specify a custom CA certificate file for SSL verification when connecting to SMTP servers with self-signed or internal certificates.

Create an environment file

Create a .env file where Docker will read environment variables:

Generate a secret key

Generate a secret key using one of these methods: With OpenSSL:
Without OpenSSL:
Save the generated key for the next step.

Configure environment variables

Open the .env file in your text editor and add:
Replace the placeholder values with your generated secret key and a secure database password.

Optional: expose Sure with Cloudflare Tunnel

If you want to reach your self-hosted instance from the public internet without opening router ports, you can put a Cloudflare Tunnel in front of the web container.
  1. Create a tunnel in Cloudflare Zero Trust and copy the tunnel token.
  2. Add the token to your .env file:
  1. Edit compose.yml and set:
This tells Sure to generate HTTPS URLs correctly when Cloudflare terminates TLS before forwarding traffic to the container over HTTP.
  1. Add a cloudflared service to your compose.yml:
  1. In the Cloudflare tunnel dashboard, add a public hostname such as sure.example.com and point it at the internal Docker service:
  1. Start the tunnel connector:
Look for a successful tunnel registration message in the logs, then open https://sure.example.com.
Cloudflare should connect to http://web:3000, not https://web:3000. Sure speaks plain HTTP inside Docker and relies on RAILS_ASSUME_SSL to know the original request was HTTPS.
If you enable passkeys or security keys on a public hostname, also set WEBAUTHN_RP_ID and WEBAUTHN_ALLOWED_ORIGINS in .env to match that hostname.
If you only want Cloudflare Tunnel access, do not leave port 3000 broadly exposed to the internet. Keep the host firewall closed or bind the published port more narrowly.

Passkeys and security keys

If people will use passkeys, Touch ID, Windows Hello, or hardware security keys as MFA credentials, pin the WebAuthn relying party settings in .env:
WEBAUTHN_RP_ID should usually be the registrable domain, not a full URL. For example, use example.com for https://sure.example.com. WEBAUTHN_ALLOWED_ORIGINS must include the full origin where people access Sure, including https://.
Changing WEBAUTHN_RP_ID after people register credentials can make existing passkeys and security keys unavailable. Configure it before rollout and keep it stable across reverse proxy, domain, and hostname changes.
See Passkeys and security keys for user setup steps. Passwordless passkey login is enabled by default:
Set it to "false" if you only want registered passkeys and security keys to work as a second factor after email/password sign-in.

Reverse proxy sub-path deployments

Sure can be served under a path prefix by a reverse proxy — for example https://home.example.com/sure instead of the root. The web app works at that address with no extra configuration. When using the macOS desktop app with a sub-path deployment, enter the full mounted URL (including the path prefix) when adding the server. The app probes the health endpoint at the address you enter and walks up to the origin to find the correct mount point, so pasting a deep link such as https://home.example.com/sure/sessions/new also works.
The RAILS_RELATIVE_URL_ROOT environment variable tells Rails that it is mounted under a prefix. Set it to the path portion if your reverse proxy strips the prefix before forwarding, or configure the proxy to preserve the path and leave the variable unset.

Import size variables

By default, Sure NDJSON imports are limited to a fixed maximum file size. Self-hosted admins can raise this limit with:
This applies to both the GUI upload and the API upload paths. If you do not set this variable, the default limit is used.

Encryption keys for bank sync

When you configure a bank sync provider (such as Plaid, SimpleFIN, or Brex) on a self-hosted instance that has not set explicit Active Record encryption keys, Sure shows a warning before you save your credentials:
Encryption keys missing — This self-hosted install has not configured explicit Active Record encryption keys. Configure primary_key, deterministic_key, and key_derivation_salt before saving provider credentials in production.
The trust statement on the provider form also changes to remind you that credentials will not be stored encrypted until you configure those keys. If encryption is not configured at all when you visit Settings > Bank Sync, Sure blocks access and redirects you with the same “Encryption keys missing” message. To resolve the warning, add these three variables to your .env file before saving any provider credentials in production:
You can generate values with openssl rand -hex 32 for each one. Once all three are present, the warning disappears and credentials are stored encrypted.
Sure can derive these values from SECRET_KEY_BASE automatically, so the warning only appears when none of the three environment variables are explicitly set. If you already set SECRET_KEY_BASE, encryption is still active — you will only see the warning if you want to use dedicated keys instead. See Turning on Active Record encryption after first boot if you need to backfill existing data.

Market data provider variables

Sure supports multiple securities pricing providers. You can configure them through environment variables or in the UI under Settings > Self-Hosting. See market data providers for details on each provider.
Setting SECURITIES_PROVIDERS as an environment variable takes precedence over the UI setting. Leave it unset to manage providers from the UI only.
TINKOFF_INVEST_API_KEY serves two purposes: it enables T-Invest as a securities price provider when tinkoff_invest is included in SECURITIES_PROVIDERS, and it allows Sure to fetch brand logos for all securities (including MOEX-priced ones) via T-Invest’s CDN. The T-Invest settings block in the UI stays visible whenever a token is configured, even if T-Invest is not selected as a price provider.

On-chain wallet variables

On-chain wallet tracking uses public block explorers by default. You can override any endpoint with your own instance, and tune history and token limits.
To enable crypto pricing for on-chain wallets, include binance_public in SECURITIES_PROVIDERS. If your family currency is not USD, also set EXCHANGE_RATE_PROVIDER to a provider that needs no API key (e.g. frankfurter). See on-chain wallets for details.

Running the application

Start the application

Start the app to verify everything is working:
This pulls the official Docker image and starts the app. You’ll see logs in your terminal. Open your browser and navigate to http://localhost:3000. You should see the Sure login screen.

Create your account

On first run, register a new account:
  1. Click “Create your account” on the login page
  2. Enter your email
  3. Enter a password
The first user to register on a fresh instance is automatically assigned the super_admin role. If two registrations happen at the same time, Sure uses a database lock to ensure exactly one of them becomes super_admin and the other becomes admin.

Restrict future signups

After creating your initial admin account, you can control how other people join your self-hosted instance from Settings > Self-Hosting > Onboarding.
  • Open: Anyone can create an account from the registration page.
  • Invite-only: Signups stay enabled, but a valid invite code is required unless a default family is configured for invite-only onboarding.
  • Closed: The registration page is disabled for new signups.
If you do not want additional self-service registrations, switch the instance to Closed after the initial setup.

Run in the background

To run Sure in the background:
  1. Stop the current process with Ctrl+C
  2. Start in detached mode:
Verify it’s running:
Your app is now accessible at http://localhost:3000.

Updating

The Docker image in your compose.yml file controls which version of Sure you’re running:
  • ghcr.io/we-promise/sure:latest - Latest alpha release
  • ghcr.io/we-promise/sure:stable - Latest stable release
You can also pin to a specific version from the packages page.

Update to the latest version

Your app does not automatically update. To update:

Change update channel

To switch between update channels, edit the compose.yml file:
Then restart the app:

Backup service

The Docker Compose configuration includes an optional backup service that automatically backs up your PostgreSQL database and uploads it to any cloud storage provider supported by rclone — including S3, Cloudflare R2, Google Drive, SFTP, and 70+ others.
The backup service only creates PostgreSQL backups. It does not back up local files stored in Sure’s storage directory. If your deployment uses local file storage, back up that directory separately. If you use external object storage such as S3 or R2, make sure that storage is protected with its own backup and retention policy.

What to back up

For a complete recovery plan, make sure you know which of these apply to your deployment:
  • PostgreSQL database: accounts, transactions, settings, users, and metadata
  • Local file storage: uploaded files stored on disk by the app
  • External object storage: uploaded files stored in S3, R2, or another object store
  • Environment and deployment config: your .env, compose.yml, secrets, and any reverse proxy or DNS setup needed to bring the app back online

Enabling backups

The backup service uses Docker Compose profiles and is disabled by default. To enable it:

Configure backup settings

Add the following variables to your .env file to configure the backup destination and schedule. BACKUP_DESTINATION is required; all other variables are optional.
Then configure your rclone storage provider. The example below uses an S3-compatible provider (such as Cloudflare R2):
For providers other than S3 (Google Drive, SFTP, Backblaze B2, etc.), replace the RCLONE_CONFIG_S3_* variables with the equivalent variables for your provider. See the rclone documentation for the full list of supported providers and their configuration variables. The compose.yml maps the S3 variables by default. If you use a different provider, add the corresponding variables to both your .env file and the backup service’s environment block in compose.yml.

Backup schedule options

BACKUP_SCHEDULE accepts standard cron syntax. Some examples:
  • 0 2 * * * — every day at 2 AM (default)
  • 0 * * * * — every hour
  • 0 2 * * 0 — once per week (Sunday at 2 AM)
  • 0 2 1 * * — once per month (1st of the month at 2 AM)

Restore from a PostgreSQL backup

Use this process when you have a SQL dump created by the backup service or with pg_dump.
[!NOTE] If you customized the PostgreSQL username, password, or database name in your .env or compose.yml, replace sure_user and sure_production in the commands below.
  1. Stop the application containers so they do not write to the database during the restore:
  1. Start or keep the database container running:
  1. Locate the backup file in your backup directory, for example /opt/sure-data/backups.
  2. Restore the SQL backup into PostgreSQL:
  1. If your deployment uses local uploaded files, restore them before restarting the app. If you use external object storage, verify that provider’s backup or versioning recovery has completed.

Restore local uploaded files

If your Sure instance stores uploaded files on the local filesystem, restoring the database alone is not enough. You must also restore the app’s storage directory from the matching file backup. The exact host path depends on how you mapped volumes in compose.yml. Restore the same directory that Sure uses for local storage before starting the app containers. If you are using external object storage instead of local disk, restore those files using that provider’s backup or versioning workflow instead. When the database and any uploaded files are restored, restart the app:

Verify the restore

After restoring, check the following:
  • You can sign in successfully
  • Your accounts and transactions appear as expected
  • Uploaded files open correctly, if you use uploads
  • The web and worker containers start cleanly without repeated errors

Verifying backups

Check that backups are running correctly:

SSL certificate configuration

For self-hosted environments using self-signed certificates or custom certificate authorities, Sure provides SSL configuration options.

Environment variables

Add these variables to your .env file:
SSL_CA_FILE: Path to a custom CA certificate bundle (PEM format). Use this when:
  • Your environment uses self-signed certificates
  • You need to trust a custom certificate authority
  • Corporate proxies inject their own certificates
SSL_VERIFY: Controls SSL certificate verification (default: true)
  • Set to false to disable SSL verification (not recommended for production)
  • Only disable verification in development or testing environments
If you’re using a custom CA bundle, mount it into your containers in compose.yml:
Restart the application after updating your configuration:

Optional: SSL/TLS Configuration

Sure supports additional SSL/TLS configuration options for secure email delivery and API connections.

Custom CA Certificate

If you’re using a custom Certificate Authority (CA) or self-signed certificates, you can specify a CA file:
This is useful when:
  • Running Sure in a corporate environment with internal CAs
  • Using self-signed certificates for development
  • Connecting to services with custom certificate chains

Skip TLS Verification for Email

For development or testing environments, you can disable TLS verification for the email mailer:
Only disable TLS verification in development or trusted environments. In production, always use proper TLS verification to ensure secure email delivery.
When to use this:
  • Development environments with self-signed certificates
  • Testing email functionality locally
  • Internal mail servers with custom certificates
Production recommendations:
  • Always use valid SSL/TLS certificates in production
  • Use the SSL_CA_FILE option instead of disabling verification
  • Ensure your SMTP server supports STARTTLS

Mailer SSL configuration

For SMTP connections, additional SSL options are available:
SMTP_ENABLE_STARTTLS_AUTO: Enable STARTTLS for SMTP connections (default: true) SMTP_OPENSSL_VERIFY_MODE: SSL verification mode for SMTP
  • none: Skip SSL verification
  • peer: Verify the server certificate (default)

Security considerations

Disabling SSL verification (SSL_VERIFY=false or SMTP_OPENSSL_VERIFY_MODE=none) exposes your application to man-in-the-middle attacks. Only use these settings in trusted networks or development environments.
For production deployments:
  • Use properly signed certificates from a trusted CA
  • Keep SSL_VERIFY=true
  • Use SMTP_OPENSSL_VERIFY_MODE=peer
  • If you must use self-signed certificates, provide a CA bundle via SSL_CA_FILE

Monitoring system health

When the Sidekiq worker container isn’t running, background jobs silently never execute. Balance calculations, net-worth updates, and account syncs stall, and the UI may show zeros or “No balance data available” without explaining why.

Sidekiq health banner

If Sure detects that Sidekiq is not processing jobs, a warning banner appears at the top of every page for super-admins. The banner indicates that data may be stale and links to the system health page. The check is conservative by design:
  • A worker process is considered stale if its heartbeat is older than 2 minutes, which tolerates brief deploy restarts and Redis blips.
  • A queue is considered backed up if its latency exceeds 5 minutes.
The health snapshot is cached for 60 seconds to avoid extra Redis round-trips on every page load. You can override the thresholds and cache TTL with environment variables:

System health page

Super-admins can view live Sidekiq state at Settings → Advanced → System health. The page shows:
  • Worker process count and last heartbeat time
  • Maximum queue latency
  • Job counters (processed, failed, enqueued, retries)
  • Per-queue depth breakdown
The system health page always bypasses the cache and fetches fresh state, so you can confirm a worker restart took effect immediately.

AI status tab

The system health page has an AI tab at /admin/system_health?tab=ai. Opening it runs bounded, non-destructive live checks against the effective AI configuration:
  • LLM probe — Verifies the configured model is available. For standard OpenAI, it queries the models list. For OpenAI-compatible endpoints (Ollama, OpenRouter, etc.), it sends a short chat completion request instead, since many compatible providers do not implement the models list endpoint.
  • Function calling (tools) probe — Asks the configured model for one trivial tool call using the same tools payload the assistant sends for its own function calls. A pass means tool calls work end-to-end. Two distinct failure outcomes are reported separately: if the tools request is rejected as a client error but the same request without tools succeeds, the probe marks tool support as unsupported (common with models on providers like OpenRouter that answer a bare 404); if the endpoint accepted the tools parameter but the model replied with text instead of calling the tool, the probe marks it as tools accepted, but the model called none. The probe uses the same API route the assistant takes — Responses API for hosted OpenAI when supports_responses_endpoint? is true, Chat Completions otherwise — so it always reflects the path live chat follows.
  • PDF text extraction probe — Sends a synthetic PDF to the model and checks that the response correctly identifies the institution name embedded in the document. This probe only runs when the effective provider is OpenAI and the model reports PDF text extraction support. The synthetic document contains no customer data.
  • PDF vision processing probe — Same synthetic PDF check using the vision processing path. Runs for any provider that reports vision-based PDF support, including Anthropic. The synthetic document contains no customer data. If the probe fails with “The pdftoppm renderer (poppler-utils) is not available in this container”, your image predates the fix that bundled poppler-utils; pull the latest image and restart.
  • Vector store probe — For OpenAI vector stores, confirms a list request succeeds without creating or modifying a store. For pgvector, checks that the vector extension is enabled and the chunks table exists.
  • Embedding probe — For pgvector, sends a short test embedding request and verifies the returned vector matches EMBEDDING_DIMENSIONS. The test vector is not stored.
When the pgvector setup is failing, the tab shows a specific alert explaining the cause instead of a generic failure message. The possible alerts and what to do for each are: The tab also shows the active provider, model, endpoint (credentials are always redacted), function calling status, LLM request timeout, health-check probe timeout, PDF text extraction and vision processing status, and pgvector extension and table status. The model settings field links super-admins to this tab and notes that the assistant requires a model with function-calling support. The LLM request timeout and health-check probe timeout are displayed as separate rows because they bound different operations: the LLM request timeout covers normal chat and PDF import calls, while the health-check probe timeout covers only the live checks run on this page. When OPENAI_URI_BASE points to a custom endpoint, the page identifies the provider where possible — Ollama, OpenRouter, Together AI, Kilo, Cloudflare Workers AI or AI Gateway — and labels unrecognized services as Custom endpoint. Results are cached for 60 seconds by default. Click Run checks again to bypass the cache and get fresh results. Failures are written to the system debug log and to Rails.logger; credentials are never included. You can tune the AI status checks with these environment variables:

Verifying worker configuration

The checks above all run from the web process. Most AI work — assistant responses, PDF processing, embeddings, auto-categorization, and merchant detection — actually executes in a Sidekiq worker process, which can differ from web in network access, DNS, proxy rules, or the credentials it loaded. This is most common in Kubernetes deployments using workload-specific environment overrides or a Secret updated without a pod restart. A passing web check does not prove a worker can reach the same provider. Click Verify worker configuration on the AI status tab to queue an asynchronous check (WorkerAiHealthCheckJob). It runs the same live probes from inside whichever worker process dequeues it, and the tab lists the result: that worker’s process identity, when it checked in, whether its effective configuration matches what web resolved, and probe outcomes. A few things this does and does not prove:
  • One check verifies one worker. Sidekiq does not broadcast a job to every process, so a passing result confirms the named process is healthy, not your whole fleet. With multiple worker replicas, queue the check again to sample another. Only the 5 most recently checked-in distinct processes are kept. A 6th check-in can evict an older entry before its own 15-minute retention window expires. A result older than WorkerAiHealth::STALE_AFTER displays as Stale rather than pass or fail.
  • Worker checks never reuse a web-cached result, or vice versa. The web page’s probes are cached briefly so repeat page loads do not re-hit providers. The worker job bypasses that shared cache so its result always reflects a live call from its own network context, and never leaves an entry the web process could read back as its own.
  • In local development, web and worker results will not show up together. bin/dev runs web and worker as separate OS processes, and config/environments/development.rb uses a process-local cache. A worker check queued locally writes to the worker’s own in-memory cache, so the Verify worker configuration button can appear to do nothing. This is expected behavior; production’s shared Redis store is unaffected.
  • Which settings need a restart depends on how they are set. Provider, model, and API-key settings changed in Settings → Self-Hosting are stored in the database and take effect automatically on both web and worker without a restart. Embedding configuration and anything set only through an environment variable is fixed when each container starts. Changing those requires restarting or recreating both web and worker. The AI status tab labels this distinction next to the worker results.

OpenAI request timeout

The LLM request timeout controls how long each OpenAI-compatible HTTP request is allowed to take before it is cancelled. It applies to auto-categorization, merchant detection, chat completions, PDF processing, and vector-store calls. The default is 60 seconds, which works well for hosted models. Raise it when using slow local models (for example, Ollama on modest hardware). You can configure the timeout in two ways:
  • Environment variable — set OPENAI_REQUEST_TIMEOUT to a number of seconds. This takes precedence over the UI setting.
  • Settings UI — go to Settings > Self-Hosting and enter a value in the Request Timeout in Seconds field under the OpenAI section.
The minimum accepted value is 1 second. If both the environment variable and the UI setting are present, the environment variable wins.

AI response timeout

The response timeout is a whole-turn watchdog for chat. It bounds how long Sure waits for a complete assistant turn — including all tool calls and their results — before giving up. The default is 90 seconds. Size this as a sum of the per-call request timeout across the maximum number of tool-call iterations, plus tool execution and queue wait time: (1 + ASSISTANT_MAX_TOOL_CALL_ITERATIONS) × OPENAI_REQUEST_TIMEOUT. The 90-second default is sized for typical cloud model latency. For local models where each request is slow, either raise this value or lower ASSISTANT_MAX_TOOL_CALL_ITERATIONS. You can configure the timeout in two ways:
  • Environment variable — set AI_RESPONSE_TIMEOUT to a number of seconds. This takes precedence over the UI setting.
  • Settings UI — go to Settings > Self-Hosting and enter a value in the Response Timeout in Seconds field under the OpenAI section.
The minimum accepted value is 30 seconds. If both the environment variable and the UI setting are present, the environment variable wins.

Extra HTTP headers for OpenAI-compatible requests

OPENAI_EXTRA_HEADERS lets you attach custom HTTP headers to every request Sure sends to the OpenAI-compatible endpoint. This is useful for AI gateways that require routing or authentication headers — for example, sending a session identifier to a proxy that tracks usage per conversation. Set the variable to a JSON object of header names and values:
How it works:
  • The value must be a valid JSON object. If it is missing, not an object, or not valid JSON, Sure logs an error and falls back to no extra headers. Chat continues normally.
  • String keys and values are used as-is. Blank values are dropped.
  • If a header value contains the literal {session_id}, Sure substitutes the current chat’s UUID at request time. This identifies requests per conversation rather than per installation. The substitution only happens for chat requests — batch flows such as auto-categorization, merchant detection, and PDF processing never receive session headers.
  • This variable is environment-only. There is no Settings UI equivalent.
The Sidekiq health banner, system health page, and AI status tab are only available in self-hosted mode. Managed deployments do not show these features.

Running Sure on small (512 MB) hosts

Sure runs comfortably on hosts or containers limited to 512 MB of RAM, which makes it a good fit for the smallest tiers on platforms like Render, Fly.io, or a cheap VPS.

What fits in 512 MB

Measured on a production deploy of the official image with the tuning below:
  • Boot, first-run onboarding, and everyday use (dashboard, transactions, budgets, reports)
  • Small bank syncs and CSV imports
  • Scheduled cron jobs such as exchange-rate refreshes
Steady-state memory sits around 352 MB, leaving comfortable headroom under a 512 MB limit.

What does not fit in 512 MB

  • The demo-data generator (“Load sample/demo data”): this is the one operation that deterministically exceeds 512 MB. On a 512 MB container it climbs to the limit and gets OOM-killed mid-generation. Because the generation runs in the worker, the symptom is a sample-data load that never completes, sometimes with all rows silently rolled back.
  • Very large first-time imports or historical syncs (tens of thousands of rows) can also exceed the limit. Import your history in smaller batches, or temporarily raise the memory limit for the initial import and lower it afterwards.
  • AI features: the assistant needs extra headroom. If you enable AI, run at least 1 GB.

Tuning already in the image

The official image ships with the memory tuning that makes 512 MB viable, so no extra configuration is needed:
  • jemalloc preloaded to reduce memory fragmentation
  • YJIT (Ruby’s JIT) enabled
  • Puma constrained to 1 worker × 3 threads (WEB_CONCURRENCY=1, RAILS_MAX_THREADS=3)
If you run your own process supervisor instead of the official image, set those same values.

Loading sample data on a small host

If you want to load sample data on a 512 MB host:
  1. Raise the worker’s memory limit. The generator runs in the worker process, not the web process. On Render, bump the worker service’s plan; on Docker Compose, raise the worker container’s memory limit.
  2. Apply the change with a fresh deploy or restart of the worker. On Render, plan changes only take effect on the next deploy and do not apply on a plain restart.
  3. Load the sample data, then optionally lower the worker back to the smaller plan with another fresh deploy.

Troubleshooting

Database connection errors

If you encounter ActiveRecord::DatabaseConnectionError on first startup, Docker may have initialized the Postgres database with a different default role from a previous attempt.
The following commands will delete all existing data in your Sure database. Only proceed if you’re comfortable losing this data.
Reset the database:
The last command verifies the issue is fixed.

Turning on Active Record encryption after first boot

Self-hosted Sure can derive Active Record encryption keys from SECRET_KEY_BASE automatically. If you prefer dedicated keys, you can add:
Keep the same SECRET_KEY_BASE for an existing instance unless you are restoring from a backup that was created with the new value. Sure uses it to derive encryption keys when the dedicated ACTIVE_RECORD_ENCRYPTION_* variables are not set. If SECRET_KEY_BASE changes, deterministic encrypted fields such as user email addresses may no longer be searchable, and email/password login can fail with “invalid username or password” even when the password is correct. If you add those keys after your instance already has data, the normal app startup path is not enough to rewrite older plaintext values:
  • db:prepare still runs automatically on startup for schema setup and migrations.
  • New writes use encryption after the app boots with the keys.
  • Existing rows are not automatically re-encrypted.
Run the backfill task once after the app is up:
The task is idempotent, so it is safe to rerun if needed. If login started failing immediately after changing encryption settings, restore the previous SECRET_KEY_BASE or previous ACTIVE_RECORD_ENCRYPTION_* values first. After the app can read the existing data again, run the backfill task if you are intentionally moving to dedicated keys.

Sure NDJSON import file size limit

By default, Sure NDJSON imports are limited to a fixed maximum file size. Self-hosted admins can raise this limit with an environment variable:
This setting applies to both the GUI upload and the API upload paths. Set the value to the maximum file size in megabytes that you want to allow.

Slow CSV imports

If CSV imports are processing rows slower than expected, check your worker logs for errors:
Look for connection timeouts or Redis communication failures. The sure-worker container requires Redis to process CSV imports.

AI features, external assistant, and Pipelock

Sure ships a separate compose file for AI-related features: compose.example.ai.yml. It adds:
  • Pipelock (always on): AI agent security proxy for outbound tunnel controls and inbound MCP scanning
  • Ollama + Open WebUI (optional --profile local-ai): local LLM inference

Using the AI compose file

Setting up the external AI assistant

The external assistant delegates chat to a remote AI agent instead of calling LLMs directly. The agent calls back to Sure’s /mcp endpoint for financial data (accounts, transactions, balance sheet).
  1. Set the MCP endpoint credentials in your .env:
  1. Set the external assistant connection:
  1. Choose how to activate:
    • Per-family (UI): Go to Settings > Self-Hosting > AI Assistant, select “External”
    • Global (env): Set ASSISTANT_TYPE=external to force all families to use external
To use the bundled OpenClaw service instead of a separately hosted agent, start the external-assistant profile. This profile starts OpenClaw without the local Ollama or Open WebUI services:
See the docs/hosting/ai.md file in the Sure repository for full configuration details including agent ID, session keys, and email allowlisting.

Pipelock security proxy

Pipelock sits between Sure and external services and provides:
  • MCP request and response scanning for DLP, prompt injection, and tool poisoning
  • HTTPS tunnel controls for destination, SSRF, rate, budget, CONNECT headers, and optional signed receipts
When using compose.example.ai.yml, Pipelock is always running. External AI agents should connect to port 8889 (Pipelock’s MCP reverse proxy) instead of directly to Sure’s /mcp on port 3000.
The example compose file does not enable TLS interception, so Pipelock controls the tunnel destination but cannot read encrypted request or response bodies. Docker Compose also does not prevent a client from bypassing the proxy.
For full Pipelock configuration including signed receipts, see Self-hosting with Helm for Kubernetes options and the pipelock.example.yaml reference file in the Sure repository.

Local development bind

For bin/dev on your own machine, the server defaults to localhost (127.0.0.1 and [::1]), reachable only from the same machine. If you need external access (phone on the same Wi-Fi, devcontainer port forwarding, LAN testing), set the BINDING environment variable:
The bundled devcontainer at .devcontainer/docker-compose.yml already sets BINDING: "0.0.0.0", so Docker port forwarding works without a manual override when using the devcontainer.

Getting help

If you find bugs or have feature requests: