How to self-host Maybe without Docker on your own VPS
Run Maybe v0.6.0, the personal finance app, on your own Ubuntu server with ox: Rails, Sidekiq, Postgres and Redis, from the config that ran.
Self-hosting Maybe takes an Ubuntu server, a fork of the Maybe repo with one ox.toml added, and 14 variables, three of which the config marks as required. In our run the first deploy took about 5 minutes, and ox set up Postgres, Redis and a Sidekiq worker for it.
How we tested On Oct 17, 2026, ox deployed Maybe v0.6.0 from commit 224f2135 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory. The run then redeployed it, backed it up and rebooted the server. Then a separate test agent, one that did not write the config, signed in, finished the setup, added a cash account with a balance, and saw it on the dashboard after a reload, and checked the app's services and its backup.
Maybe is a personal finance app. A household adds its accounts (cash, investments, property, vehicles, loans, credit cards, crypto), records transactions, sets budgets and follows its net worth. The README says the company revived the product as open source so you can run it yourself for free, under the AGPLv3. It is a Rails app: data in PostgreSQL, background jobs in Sidekiq, their queues in Redis.
ox deployed Maybe v0.6.0 (commit 224f2135) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 5 minutes and a redeploy took 2 minutes.
- First deploy: 5 minutes, from adding the project until the app passed the run's first checks.
- Redeploy of the same commit: 2 minutes, until the app passed the checks again.
- Server: DigitalOcean, 8 vCPUs and 16 GB of memory (
s-8vcpu-16gb), with this app alone on it. Every one of the 85 apps we deployed ran on this one size, so it was not picked for this app. - Memory in use after the reboot: 1480 MB of 15986 MB, for the whole server: Ubuntu, the app and its staging copy, its services, Caddy and the ox agent. On a server with no apps, ox's tests on real servers hold the agent to 40 MB or less and Caddy to 50 MB or less.
- Services ox runs for it:
postgres,redis. - Background processes:
sidekiq, each its own systemd unit beside the web process. - Source: github.com/maybe-finance/maybe at v0.6.0, commit
224f2135.



What you need
An Ubuntu LTS server. The run used the size in the facts above and measured its memory there.
A GitHub account to fork
maybe-finance/maybe. Signing in to ox with GitHub opens your ox account.An email address for the first admin, and long random values for its password, the Sidekiq page password and the secret key.
Not tested: real email (the run's mail settings pointed at a test mail catcher), exchange rates and stock prices from Synth, the AI assistant (it asks for an OPENAI_ACCESS_TOKEN), bank connections through Plaid, imports, a custom domain and a restore.
The steps
In the ox dashboard press Add a server, and run the install command it shows, as root, on your VPS. It installs the agent and Caddy only.
Fork maybe-finance/maybe and start your branch at the tag v0.6.0, the release the run deployed. Add the ox.toml shown below at the repo root, then commit and push. The run added no other file.
On your laptop, write maybe.env with one line for each of the 14 keys in the variables list. Maybe's .env.example makes SECRET_KEY_BASE with openssl rand -hex 64. Pick your own admin email and make both passwords long and random. Maybe's sign-up form wants eight characters with mixed case, a number and a symbol. The admin script below skips that rule, so the strength is yours to set. With the ox CLI signed in, run:
ox new <you>/maybe
ox review maybe --from-file maybe.env --wait \
--skip RAILS_MASTER_KEY --skip SYNTH_API_KEY \
--skip SMTP_USERNAME --skip SMTP_PASSWORDThe project takes the repo's name. The review saves your values and starts the first deploy, and --wait follows the log to its end. --skip marks a key as not needed, so the app never receives it. ox asks about these four because three are in .env.example and the repo holds an encrypted credentials file with no key. Maybe booted on SECRET_KEY_BASE alone, and the run's mail catcher needed no login. SYNTH_API_KEY is in the FAQ. In the browser, the review screen's Not needed button does the same, as the quickstart shows.
When the deploy is green, open the project's address and sign in with MAYBE_ADMIN_EMAIL and MAYBE_ADMIN_PASSWORD. The first sign-in opens a short setup: your name, a household name and country, then preferences and goals. The test agent finished it, then added a Cash account by hand, named Wallet, with a balance of 250. After a reload the Accounts list still held it and the dashboard showed a net worth of $250.00. The run used the preview address only. A domain of your own was not part of it, and the domains page covers how ox adds one.
The ox.toml, line by line
# Maybe v0.6.0 (Rails 7.2, PostgreSQL, Redis), as its operator would write
# it: ox detects Ruby, Puma, the asset build, db:prepare, and both services.
# No domain of its own: its compose file (compose.example.yml) turns off
# RAILS_FORCE_SSL and RAILS_ASSUME_SSL, so it works at its own address over
# https or http, behind ox's proxy.
# The administrator is the only way in: an operator names the login and
# generates the password (Sidekiq's job pages are behind their own password).
required_variables = ["MAYBE_ADMIN_EMAIL", "MAYBE_ADMIN_PASSWORD", "SIDEKIQ_WEB_PASSWORD"]
[build]
# db:prepare, then the first administrator, made the way the sign-up form
# makes it (RegistrationsController#create: a new family, role admin), once.
# The runner prints which of the two it did; any error stops the deploy.
migrate = """\
RAILS_ENV=production bundle exec rails db:prepare && \
RAILS_ENV=production bundle exec rails runner '\
email = ENV.fetch("MAYBE_ADMIN_EMAIL"); \
if User.exists?(email: email.strip.downcase) then puts "Maybe: the administrator exists"; \
else User.create!(email: email, password: ENV.fetch("MAYBE_ADMIN_PASSWORD"), role: :admin, family: Family.new); \
puts "Maybe: made the administrator"; end'"""
[services]
# Maybe stores its data in PostgreSQL, and Sidekiq's queues in Redis.
postgres = {}
redis = {}
[workers]
# Sidekiq runs Maybe's jobs (account syncs, imports, the cron schedule).
sidekiq = "RAILS_ENV=production bundle exec sidekiq"
[storage]
# Active Storage's Disk service keeps uploads under storage/ (ox's plan
# hints it). Rails' tmp/ is scratch: ox makes it writable on its own.
keep = ["storage"]The file is short because ox reads the rest from the repo: Ruby 3.4.4 from .ruby-version, the gems, the asset build, Rails' own server, and /up as the health route ox asks before it switches releases.
required_variablesnames three keys that Maybe's example files do not list, and ox asks for each before the first deploy. The first two are the way in, described undermigrate. The third guards/sidekiq, the job dashboard. Maybe protects it with the usermaybeand the password fromSIDEKIQ_WEB_PASSWORD, and with the variable unset the password ismaybetoo.migraterunsrails db:prepare, which creates the tables on a first deploy and applies new migrations later. Then arails runnerscript makes the first admin as Maybe's sign-up does without an invitation: a new household and the admin role. It printsMaybe: made the administrator, orMaybe: the administrator existswhen the email is already there, so a redeploy changes nothing. An error stops the deploy. Sign-up stays open and each sign-up makes its own household, so the script makes yours before the site is live.[services]asks for Postgres and Redis. In self-hosted mode Maybe pings Redis before every page and sends the visitor to/redis-configuration-errorwhen it cannot connect.sidekiqunder[workers]is the command Maybe's compose file gives its worker. On start it loadsconfig/schedule.yml: an hourly cleanup of stale syncs and two weekday jobs, for market data and security checks. Account syncs run here too.keep = ["storage"]carries thestoragefolder across releases. Maybe's Active Storage writes uploads there unlessACTIVE_STORAGE_SERVICEnames Amazon S3 or Cloudflare R2.
SELF_HOSTED
RAILS_FORCE_SSL
RAILS_ASSUME_SSL
SECRET_KEY_BASE
DB_HOST
POSTGRES_PASSWORD
APP_DOMAIN
SMTP_ADDRESS
SMTP_PORT
SMTP_TLS_ENABLED
EMAIL_SENDER
MAYBE_ADMIN_EMAIL
MAYBE_ADMIN_PASSWORD
SIDEKIQ_WEB_PASSWORDSELF_HOSTED is true, which turns on the Self hosting page in Settings and turns off the subscription pages. RAILS_FORCE_SSL and RAILS_ASSUME_SSL are false, as in Maybe's own compose file. Unset, Rails treats both as true: it redirects http to https and marks cookies secure. Sign-in worked at the run's preview address behind ox's proxy.
SECRET_KEY_BASE is the root secret Rails uses for its session cookies. In self-hosted mode Maybe also derives its database encryption keys from it unless you set the three ACTIVE_RECORD_ENCRYPTION keys. DB_HOST and POSTGRES_PASSWORD are two of the settings database.yml reads, and the run set them to the references ${postgres.host} and ${postgres.password}. APP_DOMAIN is ${PUBLIC_HOST}, the host in the links of Maybe's email. The variables page explains references.
SMTP_ADDRESS and SMTP_PORT point at a mail server, a test mail catcher in the run, so no real mail went out. SMTP_TLS_ENABLED was false, since Maybe turns TLS on only for the exact value true. EMAIL_SENDER is the From address, one at example.com in the run. MAYBE_ADMIN_EMAIL and MAYBE_ADMIN_PASSWORD are the first admin's login, and SIDEKIQ_WEB_PASSWORD opens /sidekiq.
Postgres, Redis and the worker
ox gives the project its own database and user on the server's Postgres, and a Redis that runs only for this project. The run also deployed a staging copy beside production, and the memory figure counts both. The test agent read the app, worker and Redis units twice, over a minute apart, and found them running with no restarts. Right after Wallet was added, the worker's journal showed its sync job performed.
Updates and backups
For a new release, merge its tag into your fork and push, or run ox deploy maybe --wait. ox builds beside the running release, dumps Postgres, runs migrate and switches when /up answers. The run redeployed the same commit, so a move to a newer Maybe was not tested.
Backups need no setup. The run's backup folder held two Postgres dumps, one from Back up now and one from before the migrate, and a small archive of storage, where nobody had uploaded a file. ox backups maybe lists them. A restore was not tested. The backups page covers copies in your own S3 bucket, and the self-hosting page lists other apps ox has run.
Frequently asked questions
Do I need Docker to self-host Maybe?
No. Maybe's own guide uses Docker Compose, with a web container, a Sidekiq worker, Postgres and Redis. With ox the same four parts run as plain services on your server. It builds Maybe from your fork and starts it as system units, so there is no image to pull, and an update is a push or one deploy command.
How do I make the first Maybe account?
Set MAYBE_ADMIN_EMAIL and MAYBE_ADMIN_PASSWORD before the first deploy. The migrate step makes that user as the admin of a new household, and you sign in at /sessions/new. Sign-up stays open, and each sign-up makes its own household. Settings has an invite-code switch on the Self hosting page, which was not tested.
How much memory does self-hosted Maybe use?
On the run's server, the whole machine held the memory the facts show after a reboot. That counts Maybe, its staging copy, Sidekiq, Postgres, two Redis instances, Caddy and the ox agent. The build runs on the server too. ox works on any server from 1 GB up and tells you when memory is why an app stopped.
Do I need a Synth API key to use Maybe?
No. Manual accounts and balances work without one, and the run set none. Synth supplies exchange rates and stock prices, and its key can go in Settings under Self hosting later. Leave SYNTH_API_KEY unset, not empty: Maybe reads the variable first, so an empty value hides the key you save in Settings.
New to running apps on your own server? How ox self-hosts your apps shows what ox sets up on your server.
