RubyEverywhere
Ship desktop and mobile apps from the Ruby web apps you already know how to build.
rubyeverywhere.com · by Andrea Fomera
RubyEverywhere takes a Rails (or Sinatra, or Hanami) app and gives it a native life on the platforms your users are already on:
- Desktop — a Tauri-based shell. In local mode your whole app is pressed into a single-file binary with Tebako and runs on-device, no Ruby install required; in remote mode the shell is a thin native wrapper around your deployed app.
- iOS — a Hotwire Native shell with a real tab bar, nav-bar buttons, menus, action sheets, notifications, haptics, biometrics, deep links, and native sign-in.
- Android — a feature-for-feature Hotwire Native Android port of the same
shell, stamped from the same
config/everywhere.yml.
One config file, one CLI, one JavaScript API. The web app stays the source of truth — most of what you'd normally rebuild natively (tabs, routes, menus, badges) deploys with your Rails app rather than through an app-store review.
Install
gem install ruby_everywhere
This gives you two equivalent executables — every and rbe — plus the
packaging toolchain (the rbe-tebako fork) as a dependency.
Or add it to your app's Gemfile, which also wires up the Rails engine (bridge asset serving, importmap pin, view helpers, the live mobile config endpoint):
gem "ruby_everywhere"
Requires Ruby >= 3.2.
The gem is free, and it always will be. Everything here — the CLI, the desktop shell, both mobile shells, the bridge — is MIT licensed and yours to use, forever, with no account and no key. Local builds run on a Mac today (pressing, code signing, Xcode and the Android SDK all want a macOS host). If you don't own one, or you'd rather not keep a build toolchain healthy, the RubyEverywhere Platform is a paid service coming soon that runs the exact same builds for you — see Hosted builds below. It's a convenience, never a gate: nothing in this gem is held back for it.
Quick start
cd path/to/your/app
every install # remote mode: a native shell around your deployed app
every install --local # local mode: compile and ship the app itself
every doctor # check the toolchain before your first build
every dev # dev server + native shells on demand, live reload
every dev --ios # ...straight into the iOS Simulator
every dev --android # ...straight into the Android emulator
every build # package the macOS .app
every build --ios # build the iOS shell
every build --android # build the APK/AAB
every install is idempotent — it adds config/everywhere.yml, a boot stub,
and the framework-specific config tweaks it needs, and skips anything already
in place. For Rails it does the surgery for you; for Sinatra and Hanami it
vendors the bridge and prints the remaining wiring rather than guessing at your
boot files.
every dev never packages anything: it runs your ordinary dev server and points
the native shells at it, so the edit-refresh loop is untouched Rails.
Configuration
Everything per-app lives in config/everywhere.yml. The native shell templates
are frozen — the CLI stamps a copy per app and only ever writes the identity,
config, icon, and color files, so a shell upgrade never collides with your app.
app:
name: My Really Awesome App
bundle_id: com.example.myapp # set it once; renaming moves users' data
version: 1.2.0
entry_path: /dashboard # where the app opens
icon: icon.png
appearance:
tint_color: "#CC342D"
background_color: # one hex string, or split by system theme
light: "#FAF9F7"
dark: "#1C1B1A"
remote:
url: https://app.example.com # remote mode: the deployed app to wrap
tabs: # the native mobile tab bar
- name: Builds
path: /builds
icons:
ios: hammer # SF Symbol
android: build # Material icon
permissions: # only declared permissions can ever prompt
notifications: true
camera: "Scan QR codes to pair devices." # the sentence iOS shows
rules: # Hotwire Native path configuration
- patterns: ["/preferences$"]
properties:
context: modal
build:
ruby: "4.0.6"
targets: [macos-arm64, ios-arm64, android-arm64]
updates: # self-hosted, signed auto-updates
url: https://updates.example.com
public_key: RWQ…
auto: check # off | check | download | install
Also supported: auth: (native OAuth sign-in with a callback scheme),
deep_linking: (universal links / App Links — the gem serves the
apple-app-site-association and assetlinks.json files for you), and
native: for "supernative" extensions — Swift you keep in your app repo under
native/ios/ that every build compiles into the shell, including SwiftUI
screens, bridge components, a custom splash, and pinned SPM packages.
Tabs and path rules ship two ways from that one source: baked into the app
bundle for offline/first launch, and served live from
/everywhere/{ios,android}_v1.json, so changing them deploys with your web app.
Everywhere.filter_tabs lets you vary the tab bar per request — it shares the
app's session, so signed-out users can get a different bar without a release.
The JavaScript bridge
The gem ships @rubyeverywhere/bridge — one API for the browser, the desktop
shell, and the mobile shells. Rails apps get it served and importmap-pinned
automatically; Sinatra and Hanami get it vendored to public/bridge.js.
import Everywhere from "@rubyeverywhere/bridge"
Everywhere.platform // "desktop" | "mobile" | "browser"
Everywhere.native // true inside a RubyEverywhere app
Everywhere.notify({ title, body })
Everywhere.menu({ title, items })
Everywhere.haptics.impact("medium")
Everywhere.badge.setTab("/inbox", 3)
Everywhere.permissions.request("camera")
Everywhere.storage.set("theme", "dark")
Everywhere.updates.check()
Everything degrades gracefully — the same page works in a plain browser tab and
gains native powers inside the app. See
bridge/README.md for the full surface.
Most native chrome needs no JavaScript at all. Tag ordinary markup and the
bridge lifts it into real native controls; tapping the native control just
.click()s the element it mirrors, so behavior stays defined once in the DOM.
Rails ships helpers that emit exactly that markup:
<%= everywhere_nav_button "New", new_note_path, icons: { ios: "plus", android: "add" } %>
<%= everywhere_fab new_note_path %>
<%= everywhere_nav_menu "More" do %>
<%= everywhere_menu_item "Delete", note_path(@note), method: :delete, style: "destructive" %>
<% end %>
Plus native_app?, native_platform, everywhere_badge,
everywhere_biometric_lock, and friends.
Commands
| Command | What it does |
|---|---|
every install |
Prepare an app (boot stub, config/everywhere.yml, config tweaks) |
every doctor |
Check the toolchain — Tebako, Rust, brew deps, Xcode, Android SDK/JDK |
every dev |
Dev server with native shells on demand (--desktop --ios --android --browser) |
every logs |
Stream shell logs from the Simulator or Android device |
every build |
Package natively — desktop, --ios, or --android |
every icon |
Generate .icns / .ico / Linux icons from a source PNG |
every release |
Sign, notarize, staple, and emit a release.json receipt |
every updates keygen |
Generate a minisign-compatible update-signing keypair |
every publish |
Publish a release to your update bucket |
every clean |
Remove the shell build caches |
every shell-dir |
Print the path of the bundled shell template |
every platform … |
Hosted builds: login, logout, auth status, build |
Run every with no arguments for the full list, or every <command> --help for
a command's flags.
Auto-updates
Desktop releases can update themselves from a bucket you own, with no service in the middle:
every updates keygen # once, per app
every release # build, sign, notarize, staple
every publish --notes-file NOTES.md # sign the manifest, upload, flip latest.json
The public key ships in the app; the secret key never leaves your machine. Apps
can check, install, and switch channels at runtime via Everywhere.updates.
Hosted builds
Don't have a Mac, or want iOS and Android built without babysitting Xcode, the Android SDK, and a signing setup? The RubyEverywhere Platform — a paid service, coming soon — runs the same builds on managed machines and hands back the artifacts. The CLI already speaks to it:
every platform login
every platform build --target ios-arm64,android-arm64
Same gem, same config/everywhere.yml, same output — you're paying for the
build machines and the toolchain upkeep, not for features. The gem stays free
and complete on its own.
Project layout
lib/everywhere/ the CLI, config, builders, and Rails engine
exe/every, exe/rbe the executables
bridge/ @rubyeverywhere/bridge — the JS half
support/shell/ the Tauri desktop shell template
support/mobile/ios/ the Hotwire Native iOS shell template
support/mobile/android/ the Hotwire Native Android shell template
The shell templates have their own READMEs documenting the freeze rules — what the CLI stamps per app and what it must never touch: iOS · Android.
Contributing
Bug reports and pull requests are welcome. Run the test suite with rake test.
License
Released under the MIT License. Copyright © 2026 Andrea Fomera.