# How Self‑Hosted vs Managed App Mode Configuration Works in Maybe Finance

> Understand self-hosted vs managed app mode in Maybe Finance. Learn how environment variables control rate limits, database migrations, and features.

- Repository: [Maybe/maybe](https://github.com/maybe-finance/maybe)
- Tags: internals
- Published: 2026-03-07

---

**Maybe Finance uses environment variables in [`config/application.rb`](https://github.com/maybe-finance/maybe/blob/main/config/application.rb) to set `Rails.application.config.app_mode` to either `"self_hosted"` or `"managed"`, which then controls rate limits, database migrations, and feature availability throughout the application.**

The self‑hosted vs managed app mode configuration is a fundamental architectural decision in Maybe that determines whether the application runs as a multi‑tenant SaaS platform (managed) or as a single‑tenant instance deployed by individual users (self‑hosted). This configuration is evaluated once during Rails initialization and stored as a global application setting.

## How App Mode Is Determined During Initialization

### Environment Variable Detection in config/application.rb

The mode selection logic resides in [`config/application.rb`](https://github.com/maybe-finance/maybe/blob/main/config/application.rb), where the application inspects two specific environment variables during the boot process:

```ruby

# config/application.rb

config.app_mode = (
  ENV["SELF_HOSTED"] == "true" ||
  ENV["SELF_HOSTING_ENABLED"] == "true"
) ? "self_hosted" : "managed"

```

If either `SELF_HOSTED` or `SELF_HOSTING_ENABLED` equals the string `"true"`, the application configures itself for self‑hosted operation. Otherwise, it defaults to managed mode. This evaluation occurs before most initializers run, ensuring downstream components can reference the mode immediately.

### ActiveSupport::StringInquirer for Predicate Methods

To enable readable conditional checks throughout the codebase, Maybe wraps the mode string in an `ActiveSupport::StringInquirer` via the `.inquiry` method (applied automatically by Rails string configuration values). This creates boolean predicate methods:

```ruby
Rails.application.config.app_mode.self_hosted?   #=> true in self‑hosted deployments
Rails.application.config.app_mode.managed?       #=> true in managed deployments

```

These predicates are used extensively in initializers, models, and controllers to branch logic without hard‑coding string comparisons.

## Operational Differences Between Self‑Hosted and Managed Modes

### Rate Limiting Configuration

The [`config/initializers/rack_attack.rb`](https://github.com/maybe-finance/maybe/blob/main/config/initializers/rack_attack.rb) file uses the app mode to adjust request throttling thresholds. Self‑hosted instances receive significantly higher limits because they typically run behind private firewalls with fewer users:

```ruby

# config/initializers/rack_attack.rb

self_hosted = Rails.application.config.app_mode.self_hosted?

throttle("api/requests", limit: self_hosted ? 10_000 : 100, period: 1.hour) { |req| req.ip }
throttle("api/ip",      limit: self_hosted ? 20_000 : 200, period: 1.hour) { |req| req.ip }

```

Managed mode uses conservative limits (100 requests per hour for API endpoints) to prevent abuse in multi‑tenant environments, while self‑hosted mode allows 10,000–20,000 requests per hour to accommodate data imports and internal network usage.

### Database Migration Behavior

Certain database migrations are conditional based on the deployment mode. For example, subscription‑related tables required for billing in the managed SaaS are skipped during self‑hosted deployments:

```ruby

# db/migrate/20250502164951_create_subscriptions.rb

if Rails.application.config.app_mode.managed?
  # create subscription-related tables and columns

end

```

This keeps the self‑hosted database schema lean, excluding multi‑tenancy infrastructure that single‑tenant instances do not require.

### Feature-Specific Logic

The codebase contains numerous feature toggles that reference the app mode:

- **Email Confirmation Bypass**: In [`app/models/user.rb`](https://github.com/maybe-finance/maybe/blob/main/app/models/user.rb), self‑hosted instances can skip email confirmation requirements based on additional settings, streamlining local deployments.
- **External API Key Handling**: The `lib/tasks/securities.rake` task selects between a user‑provided Synth API key (self‑hosted) or a centralized service key (managed):

```ruby

# lib/tasks/securities.rake

api_key = if Rails.application.config.app_mode.self_hosted?
            Setting.synth_api_key
          else
            ENV["SYNTH_API_KEY"]
          end

```

- **Encryption Configuration**: [`config/initializers/active_record_encryption.rb`](https://github.com/maybe-finance/maybe/blob/main/config/initializers/active_record_encryption.rb) adjusts key derivation strategies based on whether the instance is self‑hosted or managed.

## Configuring Self‑Hosted Mode in Production

To deploy Maybe in self‑hosted mode, set one of the required environment variables before the Rails server starts. In a Docker‑Compose deployment, add the variable to the service definition:

```yaml

# docker-compose.yml

services:
  web:
    image: ghcr.io/maybe-finance/maybe:latest
    environment:
      - SELF_HOSTED=true
      # or: SELF_HOSTING_ENABLED=true

    ports:
      - "3000:3000"

```

After restarting the application, verify the configuration in a Rails console:

```bash
bin/rails console

```

```ruby
Rails.application.config.app_mode.self_hosted?  # => true

Rails.application.config.app_mode.managed?      # => false

```

## Summary

- **Initialization**: [`config/application.rb`](https://github.com/maybe-finance/maybe/blob/main/config/application.rb) sets `config.app_mode` to `"self_hosted"` if `SELF_HOSTED` or `SELF_HOSTING_ENABLED` equals `"true"`, otherwise defaulting to `"managed"`.
- **Query Interface**: The mode is stored as an `ActiveSupport::StringInquirer`, enabling `.self_hosted?` and `.managed?` predicate methods throughout the codebase.
- **Behavioral Differences**: Self‑hosted mode increases Rack Attack rate limits (10,000+ requests/hour), skips subscription‑related database migrations, and alters feature logic for email confirmation and external API keys.
- **Configuration**: Set `SELF_HOSTED=true` in your environment (e.g., Docker‑Compose) and restart the application to activate self‑hosted mode.

## Frequently Asked Questions

### What environment variables trigger self-hosted mode in Maybe?

Maybe checks for two environment variables during startup: `SELF_HOSTED` or `SELF_HOSTING_ENABLED`. If either variable is set to the exact string `"true"`, the application configures itself for self-hosted operation. Any other value (or absence of these variables) results in managed mode.

### How do I verify which mode my Maybe instance is running?

Start a Rails console by running `bin/rails console` in your application directory. Then query the configuration object: `Rails.application.config.app_mode.self_hosted?` returns `true` for self-hosted deployments, while `Rails.application.config.app_mode.managed?` returns `true` for the SaaS version. You can also inspect the raw value with `Rails.application.config.app_mode` to see the string `"self_hosted"` or `"managed"`.

### Does self-hosted mode disable all subscription features?

Self-hosted mode excludes database tables and application logic specifically related to multi-tenant billing and subscription management, such as the subscriptions table migration found in [`db/migrate/20250502164951_create_subscriptions.rb`](https://github.com/maybe-finance/maybe/blob/main/db/migrate/20250502164951_create_subscriptions.rb). However, the core financial tracking features remain fully functional. Self-hosted users typically manage their own infrastructure costs rather than paying per-user SaaS fees, so the subscription logic is bypassed rather than disabled.

### Can I switch from managed to self-hosted mode without data loss?

Switching modes requires changing the `SELF_HOSTED` or `SELF_HOSTING_ENABLED` environment variable and restarting the application. While the mode change itself does not delete data, the database schema may differ between modes—managed mode includes subscription-related tables that self-hosted mode skips. If you are migrating from managed to self-hosted, ensure your database migrations have run completely in managed mode before switching, or manually verify that your schema matches the self-hosted expectations. Always back up your database before changing operational modes.