Prerequisites
- Docker Engine installed and running
- Basic familiarity with the command line
Installation
Install Docker
- Follow the official Docker installation guide
- Start the Docker service on your machine
- Verify the installation:
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:compose.yml file in your current directory with the default configuration.
Configuration
By default, thecompose.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):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:Configure environment variables
Open the.env file in your text editor and add:
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 theweb container.
- Create a tunnel in Cloudflare Zero Trust and copy the tunnel token.
- Add the token to your
.envfile:
- Edit
compose.ymland set:
- Add a
cloudflaredservice to yourcompose.yml:
- In the Cloudflare tunnel dashboard, add a public hostname such as
sure.example.comand point it at the internal Docker service:
- Start the tunnel connector:
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.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://.
See Passkeys and security keys for user setup steps.
Passwordless passkey login is enabled by default:
"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 examplehttps://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: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. ConfigureThe 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 yourprimary_key,deterministic_key, andkey_derivation_saltbefore saving provider credentials in production.
.env file before saving any provider credentials in production:
openssl rand -hex 32 for each one. Once all three are present, the warning disappears and credentials are stored encrypted.
Encryption is also required to generate a Wise SCA keypair. Without it, the Generate SCA keypair button returns a validation error. See Enabling SCA for details.
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.Brand logo variables
Sure uses Brandfetch to show each provider’s own icon on Bank Sync cards, drawers, and Accounts connection cards, as well as merchant and institution logos throughout the app. Set your Brandfetch client ID to enable these logos:BRAND_FETCH_CLIENT_ID is not set, Bank Sync cards and Accounts fall back to each provider’s initials on a colored background. You can also toggle high-resolution logos from Settings → Self-Hosting in the UI.
Application name variables
By default, Sure uses “Sure” as the product name and “FOSS” as the brand name throughout the UI, emails, and page titles. You can override both to white-label your instance:© <year>, <BRAND_NAME> <PRODUCT_NAME>, and in the default email sender name. Other UI copy can use the product name or brand name separately.
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. When a token is configured but T-Invest is not enabled for prices, the T-Invest settings block shows a notice: “Not enabled for prices above — T-Invest is used to fetch brand logos for all your securities whenever a token is configured, independent of the checkbox above.” Logo fetching happens regardless of whether T-Invest is selected as a price provider.Categorization provider variables
By default, Sure categorizes transactions using your configured LLM provider. You can optionally switch to Jev (by TypeSafe), a classification provider that answers typed questions about a transaction with calibrated probabilities. Because Jev only handles classification it is never used for chat, PDF processing, or merchant detection, and is configured independently ofLLM_PROVIDER.
Jev is a per-family opt-in behind the preview features gate. When the preview gate is open, each family can select their categorization provider from Settings > Self-Hosting > Categorization provider. The ENV override CATEGORIZATION_PROVIDER applies to every family on the instance.
JEV_ENDPOINT must use HTTPS. An HTTP endpoint (other than loopback addresses such as localhost or 127.0.0.1) is rejected because it would transmit the API key and transaction descriptions in cleartext. If a mis-typed or invalid endpoint is configured, Sure falls back to the LLM provider rather than stopping categorization entirely.Property valuation provider variables
Sure supports three automated valuation model (AVM) providers for property accounts: RentCast and Realie for US properties, and Chimnie for UK residential properties. Configure them through environment variables or in the UI under Settings > Self-Hosting. See property valuations for details on each provider.RentCast and Realie only support US addresses and return values in USD. Chimnie only supports UK residential properties and returns values in GBP. A property account must be denominated in the provider’s currency for automatic refreshes to run.
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.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.
Redis Sentinel (high availability)
By default Sure connects to Redis usingREDIS_URL. For high-availability deployments that use Redis Sentinel, set REDIS_SENTINEL_HOSTS instead. When that variable is present it takes precedence over REDIS_URL.
REDIS_SENTINEL_HOSTS is set but no valid hosts can be parsed from it, Sure falls back to REDIS_URL and logs a warning.
Analytics (PostHog)
Sure can send product analytics events to a PostHog instance. This is off by default and only activates whenPOSTHOG_KEY is set.
POSTHOG_HOST defaults to https://us.i.posthog.com when not set. Point it at your own PostHog instance if you self-host PostHog.
When analytics are enabled, Sure registers a sure_version property on every browser event. The value comes from the .sure-version file bundled in the Docker image and matches the release tag (for example, 0.7.5-alpha.10). This lets you filter PostHog events by the exact Sure version that produced them.
Sure also enables PostHog’s person profiles for all events, which allows PostHog to enrich events with GeoIP data based on the client IP address.
Two optional variables give you finer control over analytics behavior:
POSTHOG_DEVELOPMENT_ENABLED is only read in the development environment. Set it to true if you want analytics events to fire during local development. In production the key presence alone controls whether events are sent.
POSTHOG_FEEDBACK_ENABLED controls whether the in-app feedback widget appears. Set it to false to hide the widget on your instance.
When POSTHOG_FEEDBACK_ENABLED is true and POSTHOG_KEY is set, Sure emits one web_ui_served_daily event per user per calendar day. The event carries a daily pseudonym derived from the user ID and the current date — no account ID or persistent person profile is created. It also includes the sure_version, whether the user has preview features enabled, and the client IP for GeoIP enrichment. The pseudonym rotates every day, so the event counts daily active web users without linking activity across days. A shared Redis cache prevents duplicate events from parallel sessions or tab reloads; cache loss can allow a repeat event for the same user on the same day.
Running the application
Start the application
Start the app to verify everything is working:http://localhost:3000. You should see the Sure login screen.
Create your account
On first run, register a new account:- Click “Create your account” on the login page
- Enter your email
- Enter a password
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.
Run in the background
To run Sure in the background:- Stop the current process with
Ctrl+C - Start in detached mode:
http://localhost:3000.
Updating
The Docker image in yourcompose.yml file controls which version of Sure you’re running:
Recommended images
ghcr.io/we-promise/sure:latest- Latest alpha releaseghcr.io/we-promise/sure:stable- Latest stable release
Update to the latest version
Your app does not automatically update. To update:Change update channel
To switch between update channels, edit thecompose.yml file:
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.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.
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 hour0 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 withpg_dump.
[!NOTE] If you customized the PostgreSQL username, password, or database name in your.envorcompose.yml, replacesure_userandsure_productionin the commands below.
- Stop the application containers so they do not write to the database during the restore:
- Start or keep the database container running:
-
Locate the backup file in your backup directory, for example
/opt/sure-data/backups. - Restore the SQL backup into PostgreSQL:
- 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 incompose.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:
- Your environment uses self-signed certificates
- You need to trust a custom certificate authority
- Corporate proxies inject their own certificates
true)
- Set to
falseto disable SSL verification (not recommended for production) - Only disable verification in development or testing environments
true to enable verbose SSL logging at startup. Sure logs the active CA file, verification status, and the path of the combined CA bundle it writes to tmp/ssl_ca_bundle.pem. Use this to diagnose certificate trust issues in development or staging environments.
If you’re using a custom CA bundle, mount it into your containers in compose.yml:
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:- 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:- Development environments with self-signed certificates
- Testing email functionality locally
- Internal mail servers with custom certificates
- Always use valid SSL/TLS certificates in production
- Use the
SSL_CA_FILEoption instead of disabling verification - Ensure your SMTP server supports STARTTLS
Mailer SSL configuration
For SMTP connections, additional SSL options are available:true)
SMTP_OPENSSL_VERIFY_MODE: SSL verification mode for SMTP
none: Skip SSL verificationpeer: Verify the server certificate (default)
Security considerations
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 queue dashboard credentials
The Sidekiq queue dashboard is available at/sidekiq for signed-in super admins only. You can add a second HTTP basic-auth layer on top of that by setting both of these variables:
Sidekiq health banner
If Sure detects that Sidekiq is not processing jobs, a warning banner appears above the main content area on every page for super-admins. The banner indicates that data may be stale and links to the system health page. It is positioned so it does not overlap the page header or other admin bars that may be visible at the top of the screen. The banner title, body, and reason message are displayed in the super-admin’s configured interface language. 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.
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
Hosted usage tab
On the two project-operated installations (app.sure.am and demo.sure.am), the system health page shows a Hosted usage tab. The tab is not available on self-hosted instances and does not appear when APP_DOMAIN is set to any other value.
The tab loads a read-only snapshot of the database. It does not contact Stripe, run cleanup jobs, or modify any data. The snapshot covers:
- Summary — trial household and member counts for the last 30 days, trials expiring in the next 7 days, cleanup-eligible households, and local supporter status counts.
- Recent trial households — up to 100 subscriptions recorded in the last 30 days that retain a trial end date, including converted trials. The table shows retained sign-in session counts, not currently-online users or visit counts. Signing out deletes a session record.
- Cleanup eligibility — households that match the existing cleanup scope (a paused or trialing subscription ended more than 14 days ago, or no subscription and a household older than 59 days). Matching the scope does not confirm deletion.
- Local supporter status — current subscription rows with Stripe-shaped customer and subscription IDs, grouped by status. No Stripe request is made; these reflect the local database only.
AI status tab
The system health page has an AI tab at/admin/system_health?tab=ai. When you first open the tab, Sure shows a loading placeholder and then fetches the results in the background. This keeps the page responsive while up to six live probes run. The checks are bounded, non-destructive, and cover 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
toolspayload 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 whensupports_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
vectorextension 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.
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. Failure reason labels and failure code messages are displayed in the super-admin’s configured interface language.
You can tune the AI status checks with these environment variables:
AI_HEALTH_PROBE_TIMEOUT is also configurable from the Settings UI without a restart. Go to Settings → Self-Hosting, scroll to the Timeouts section under AI Provider, and enter a value in the Health Check Timeout in Seconds field. The environment variable takes precedence over the UI setting when set to a positive value. Raise this value if PDF text or vision probes time out with a cloud model like gpt-4.1 — the PDF probes do a full extraction round trip that can take longer than the 5-second default, even when real PDF imports succeed.
Verifying worker configuration
The checks above all run from theweb 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. All labels and status messages in the worker verification section are displayed in the super-admin’s configured interface language.
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_AFTERdisplays 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/devrunswebandworkeras separate OS processes. The development environment uses Sidekiq, so jobs do reach the worker process, butconfig/environments/development.rbuses a process-local memory store when development caching is enabled, or a null store when it is disabled, rather than a shared Redis cache. With development caching enabled, a worker check result is written to the worker’s own memory cache; with caching disabled, the result is not retained, so the Verify worker configuration button can appear to do nothing from the web process. This is expected; 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
webandworkerwithout a restart. Embedding configuration and anything set only through an environment variable is fixed when each container starts. Changing those requires restarting or recreating bothwebandworker. 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_TIMEOUTto 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.
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_TIMEOUTto 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.
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:
- 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.
Demo data refresh
Self-hosted super admins can opt into a daily demo family refresh from Settings > Self-Hosting > Demo data refresh. When enabled, Sure automatically regenerates the designated demo family’s data every day — removing all its existing transactions, accounts, and logins and replacing them with freshly generated sample data. All other families on the instance are untouched.Requirements
- A running
workerprocess with at least 2 GiB of RAM. The demo generator is memory-intensive and will fail on smaller workers. - A disposable demo family that was created with Sure’s built-in demo generator. Do not select a real user’s family.
Setup
- Load sample data to create a demo family: go to Settings and use the demo data generator. This creates a family owned by the configured demo email address.
- Go to Settings > Self-Hosting > Demo data refresh.
- Under Disposable demo family, select the demo family you just created. The dropdown only shows families that own the demo email address and have no super-admin members.
- Click Save demo family.
- Toggle Enable daily demo family refresh on.
- Generates a fresh set of demo data under the same demo email address.
- Retires and destroys the previous demo family.
- Updates the stored family ID to point at the newly created family.
Completion notification email
After each successful refresh, Sure queues a summary email to every super admin on the instance. The email includes:- Activity during this period — the number of unique login sessions recorded for the previous demo family and the number of new family accounts created during the same window.
- Reporting period (UTC) — the start and end timestamps of the 24-hour window the job reviewed.
- Previous demo family — the name and ID of the previous family, whose deletion is queued separately. The email does not confirm that deletion has finished.
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
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)
Loading sample data on a small host
If you want to load sample data on a 512 MB host:- 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.
- 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.
- Load the sample data, then optionally lower the worker back to the smaller plan with another fresh deploy.
Troubleshooting
Database connection errors
If you encounterActiveRecord::DatabaseConnectionError on first startup, Docker may have initialized the Postgres database with a different default role from a previous attempt.
Reset the database:
Turning on Active Record encryption after first boot
Self-hosted Sure can derive Active Record encryption keys fromSECRET_KEY_BASE automatically. If you prefer dedicated keys, you can add:
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:preparestill 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.
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:Slow CSV imports
If CSV imports are processing rows slower than expected, check your worker logs for errors: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).
- Set the MCP endpoint credentials in your
.env:
- Set the external assistant connection:
- Choose how to activate:
- Per-family (UI): Go to Settings > Self-Hosting > AI Assistant, select “External”
- Global (env): Set
ASSISTANT_TYPE=externalto force all families to use external
If you connect your own agent via MCP and want to remove the built-in chat UI entirely, set
ASSISTANT_TYPE=mcp_only (or select “None in app (connect your own agent via MCP)” in the UI). This disables the in-app chat, chat API endpoints, bills AI shortcuts, and AI-written insight text. The /mcp endpoint and background auto-categorization stay active.-
Select an agent. After you save a valid URL and token, Sure calls
/v1/modelson the endpoint and populates an agent dropdown in the UI. Select the agent you want to use and save. The external service manages the model behind each agent; Sure only handles routing. To skip the UI and pin an agent via environment variable, set:WhenEXTERNAL_ASSISTANT_MODELis set, the dropdown in the UI is disabled and the environment value is always used.
external-assistant profile. This profile starts OpenClaw without the local Ollama or Open WebUI services:
docs/hosting/ai.md file in the Sure repository for full configuration details including agent model selection, 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
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.
pipelock.example.yaml reference file in the Sure repository.
Local development bind
Forbin/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:
Procfile.dev starts three processes: the Rails web server, the Tailwind CSS watcher, and a Sidekiq worker. The worker process receives background jobs via Redis, as in production. Tests use the separate Active Job test adapter. Auto-categorization, syncs, and other background work all route through this worker when developing locally.
.devcontainer/docker-compose.yml already sets BINDING: "0.0.0.0", so Docker port forwarding works without a manual override when using the devcontainer.
GitHub Codespaces
When developing inside a GitHub Codespace, the app accepts login requests from the Codespaces preview URL without any extra configuration. The app detects the standard Codespaces environment variables (CODESPACES, CODESPACE_NAME, and GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN) that GitHub sets automatically and narrows the allowed CSRF origin to the forwarded preview domain. The origin check applies only when Rails.env.development? and CODESPACES=true are both set, so it has no effect in staging or production.
The Codespaces initializer also re-declares the cookie store with SameSite=None; Secure so the VS Code Preview iframe can read the session cookie. It uses the application-wide session cookie key (_sure_session, defined as Sure::SESSION_COOKIE_KEY in config/application.rb) rather than a hardcoded name, keeping the cookie name consistent whether the app boots with or without Codespaces.
If you cannot log in through a Codespaces preview URL, confirm that the three environment variables above are present in your Codespace environment and that the app is running with CODESPACES=true.
iOS push notifications
Push notifications are available in hosted mode only. WhenSELF_HOSTED=true or SELF_HOSTING_ENABLED=true is set, push registration and delivery are disabled.
Environment variables
Set the following variables for both theweb and worker services:
APNS_PRIVATE_KEY_BASE64 is a server-side secret. Do not embed it in the iOS app, logs, or version control.
Both web and worker must share the same REDIS_URL (or REDIS_SENTINEL_HOSTS for high-availability setups). A process-local memory cache cannot coordinate the push diagnostics between processes.
Sending a test notification
- Sign in to the Sure iOS app with the same account as the super-admin signed in to the web app, and enable push notifications in the app.
- Open System Health → Background jobs → Push notifications in the web app.
- Click Send test push notification. The test is sent only to the current account’s recently registered iOS devices.
- Click Refresh results to see each device’s outcome. Results remain for 24 hours. Accepted by APNs confirms Apple’s acceptance; verify presentation on a real device to confirm delivery. The results table is keyboard-navigable and can be scrolled with the keyboard when it overflows.
APNS_ENABLED=false), or another test was requested in the last 30 seconds.
Troubleshooting
Getting help
If you find bugs or have feature requests:- Read the contributing guide
- Ask in the Discord
- Open an issue