How Do I Set Up a Ruby and Rails Dev Environment in a Cloud Sandbox?
Updated October 27, 2026 .md
Short answer: A new sandbox has no Ruby. Install rbenv and ruby-build, then rbenv install 4.0.9, which compiled in 4.4 minutes on small. Bundle a real Rails app, add libvips, run its tests, and save a named snapshot so the next sandbox has Ruby and every gem ready in seconds.
We ran every command on this page on prod on 2026-10-27 in eu-central, on small sandboxes (2 vCPU, 4 GB). The project is basecamp/once-campfire, the open-source Campfire chat app, at commit ca9b963: a Rails app with a Gemfile.lock of about 120 gems, 17 of them with native extensions, and 649 Minitest tests. For a fresh app we used rails new with Rails 8.1.4 (released 2026-09-24).
Results#
| Step | Time on small |
|---|---|
apt-get install ruby-full (Ubuntu's Ruby 3.2.3) |
4.4 s |
| Build dependencies from the ruby-build wiki, with apt | 5.3 s |
rbenv install 4.0.9 (compiled from source) |
264.5 s |
bundle install, campfire, empty gem directory |
135.2 s |
bundle install, nothing to do |
0.45 s |
bin/rails test, 649 tests on 2 workers (2 runs) |
31.8, 27.3 s |
gem install rails -v 8.1.4 |
2.7 s |
rails new blog |
28.3 s |
sbx new --from the snapshot, to ready |
3.9 s |
From the snapshot: bundle install |
0.51 s |
From the snapshot: bin/rails test |
35.7 s |
From the snapshot: rails new blog2 |
13.3 s |
The lesson: in Ruby the slow part is compiling, twice. Ruby itself builds from source, and gems like nio4r, puma, bcrypt and json compile C extensions during bundle install. Together that was about 6.7 minutes before the first test ran. A snapshot skips all of it.
1. What the image has, and why apt's Ruby is a trap#
sbx new --name rubydev-1 --type small
sbx exec sb_3voqtmqv2c bash -lc 'ruby --version; apt-cache policy ruby-full | head -3; rustc --version'bash: line 1: ruby: command not found
ruby-full:
Installed: (none)
Candidate: 1:3.2~ubuntu1
rustc 1.99.1 (58cf8f9c3 2026-10-13)Ruby is not installed. That was the image on 2026-10-27; it changes over time. The full inventory is in What's pre-installed in a sandbox. build-essential, libssl-dev and Rust are already there.
Ubuntu 24.04's ruby-full installs in 4 seconds, but it is Ruby 3.2.3, and its gem directory belongs to root:
sbx exec sb_3voqtmqv2c bash -lc 'gem install bundler'ERROR: While executing gem ... (Gem::FilePermissionError)
You don't have write permissions for the /var/lib/gems/3.2.0 directory.You could sudo gem install, but then gems live outside /home/user and mix with apt's files. Rails 8.1.4 needs Ruby 3.2 or newer (its rubygems.org page on 2026-10-27), so 3.2.3 still runs it, but it is several releases behind the current one. Use a version manager instead.
2. Install the current Ruby with rbenv and ruby-build#
The current stable release on ruby-lang.org/en/downloads on 2026-10-27 was Ruby 4.0.9, released 2026-10-23. We followed the rbenv README (git checkout) and installed the build packages listed for Ubuntu in the ruby-build wiki:
sbx exec sb_3voqtmqv2c --timeout 300 bash -lc '
sudo apt-get install -y -qq build-essential autoconf libssl-dev libyaml-dev zlib1g-dev libffi-dev libgmp-dev rustc
git clone -q https://github.com/rbenv/rbenv.git ~/.rbenv
git clone -q https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
~/.rbenv/bin/rbenv init'writing ~/.bashrc: now configured for rbenv.That line is a catch in a sandbox. Ubuntu's ~/.bashrc returns early for non-interactive shells, so sbx exec … bash -lc never reaches the rbenv line, and rbenv stays "not found". Put the same line in ~/.profile, which login shells read:
sbx exec sb_3voqtmqv2c bash -lc 'echo '\''eval "$(~/.rbenv/bin/rbenv init - --no-rehash bash)"'\'' >> ~/.profile'
sbx exec sb_3voqtmqv2c bash -lc 'rbenv --version; rbenv install --list | head -3'rbenv 1.3.2-25-g07e9b1e
3.3.13
3.4.12
4.0.9ruby-build compiles from source, which runs past the 30-second default of sbx exec. Start it with --detach and read the log:
sbx exec sb_3voqtmqv2c --detach "bash -l b1.sh > b1.log 2>&1" # b1.sh runs: time rbenv install 4.0.9; rbenv global 4.0.9
sbx exec sb_3voqtmqv2c "cat b1.log"==> Downloading ruby-4.0.9.tar.gz...
==> Installing ruby-4.0.9...
-> ./configure "--prefix=$HOME/.rbenv/versions/4.0.9" --enable-shared --with-ext=openssl,psych,+
-> make -j 2
-> make install
==> Installed ruby-4.0.9 to /home/user/.rbenv/versions/4.0.9
264.516 s
ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [x86_64-linux]
true
4.0.20
4.0.20The two true lines say YJIT is built in (RubyVM::YJIT.respond_to?(:enable)) and turns on with ruby --yjit; ruby-build compiled it because rustc was present. Ruby 4.0.9 ships RubyGems 4.0.20 and Bundler 4.0.20. The install is 158 MB under ~/.rbenv, on the sandbox disk.
3. Bundle a real Rails app and run its tests#
campfire's .ruby-version says 4.0.9, so rbenv picks it up in the repo:
sbx exec sb_3voqtmqv2c --timeout 120 bash -lc 'git clone -q https://github.com/basecamp/once-campfire campfire && cd campfire && git checkout -q ca9b963961ab'
sbx exec sb_3voqtmqv2c --detach "bash -l b2.sh > b2.log 2>&1" # time bundle install --jobs 2, then the checks below== bundle install, empty gem dir
135.167 s
exit 0
117
17
bigdecimal 4.1.3 json 2.21.2 prism 1.9.0 date 3.5.1 stringio 3.2.0 psych 5.5.0 erb 6.0.7 io-console 0.9.4 nio4r 2.7.5 bcrypt 3.1.22 ffi 1.17.2 openssl 4.0.2 puma 8.0.2 rbs 4.2.0 websocket-driver 0.8.2 debug 1.11.0 lexxy 0.9.24
603M /home/user/.rbenv/versions/4.0.9/lib/ruby/gems/4.0.0
== bundle install, nothing to do
0.453 s117 gems installed and 17 of them compiled native extensions; that compile is likely most of the 135 seconds, though we didn't time it separately. campfire pulls Rails, Propshaft and Turbo from GitHub main, so Bundler also cloned those repos. The gems land in the rbenv Ruby's own gem directory, 603 MB.
The first bin/rails db:prepare failed:
bin/rails aborted!
LoadError: libvips or the ruby-vips gem is not available (LoadError)
/home/user/campfire/config/initializers/vips.rb:5:in '<top (required)>'Gems install fine without system libraries that are only loaded at runtime. Active Storage's image processing needs libvips, which the image doesn't have. One apt package fixed it, and then the database and the test suite worked:
sbx exec sb_3voqtmqv2c --timeout 120 bash -lc 'sudo apt-get install -y -qq --no-install-recommends libvips42'
sbx exec sb_3voqtmqv2c --detach "bash -l b3.sh > b3.log 2>&1" # db:prepare, then bin/rails test twice== apt libvips
3.414 s
== db:prepare
3.091 s
exit 0
== rails test, run 1
Running 649 tests in parallel using 2 processes
Finished in 28.764620s, 22.5624 runs/s, 136.3133 assertions/s.
649 runs, 3921 assertions, 0 failures, 0 errors, 2 skips
31.791 s
== rails test, run 2
27.333 scampfire's test_helper.rb calls parallelize(workers: :number_of_processors), so on small the tests ran in 2 processes. The installed package was libvips42t64 8.15.1. Because apt installs to /usr, which is also on the sandbox disk, the snapshot in step 5 keeps it.
4. rails new, and a dev server on a private URL#
sbx exec sb_3voqtmqv2c --timeout 300 bash -lc 'gem install rails -v 8.1.4 --no-document && rails new blog --skip-git && cd blog && bin/rails generate scaffold Post title:string body:text && bin/rails db:migrate && bin/rails test'Rails 8.1.4
…
run bundle install --quiet
run bundle lock --add-platform=x86_64-linux
…
7 runs, 11 assertions, 0 failures, 0 errors, 0 skipsgem install rails took 2.7 seconds and rails new 28.3 seconds, including four quiet bundle install runs. Some of its gems were already installed from campfire, so on a bare Ruby expect it to take longer.
Start Puma on 127.0.0.1 in the background and host the port with --localhost. The sandbox's public IPv6 firewall (ufw) is closed except SSH by default, and binding to 127.0.0.1 keeps the port private whatever the firewall does:
sbx exec sb_3voqtmqv2c --cwd blog --detach "bin/rails server -b 127.0.0.1 -p 3000 > ~/rails.log 2>&1"
sbx host sb_3voqtmqv2c 3000 --localhost --title "Rails dev"{"ok":true,"type":"port.hosted",…,"url":"https://brisk-bright-ibis-3000.box.sandboxer.com?_token=<token>","access":"private","isProtected":true,"localhost":true}Without the token the URL answered 401. The token link answered 302 and set a cookie. Then Rails itself refused the request with 403:
<h1>Blocked hosts: brisk-bright-ibis-3000.box.sandboxer.com, brisk-bright-ibis-3000.box.sandboxer.com</h1>In development, Rails only answers localhost and IP addresses unless you add hosts to config.hosts. A leading dot allows a domain and all its subdomains, so one line in config/environments/development.rb covers every sandbox and port:
Rails.application.configure do
# …
config.hosts << ".box.sandboxer.com"
endEnvironment files are read at boot, so the request still got 403 until we ran bin/rails restart. After the restart, /posts answered 200 in about 0.2 seconds (0.47 s for the first request). campfire's own development config sets config.hosts = [], which allows any host, so it doesn't hit this.
A --detach process does not come back if the sandbox boots from disk. To keep a dev server running across that, use a systemd unit as in Run a Next.js dev server with a preview URL. For VS Code or Cursor over SSH, see SSH into a cloud sandbox from VS Code or Cursor; we did not test an editor for this page.
5. Save it as a named snapshot and start from it#
Stop the server first: a snapshot keeps files, not processes.
sbx exec sb_3voqtmqv2c bash -lc 'kill $(cat blog/tmp/pids/server.pid)'
sbx snapshot sb_3voqtmqv2c rubydev-cache
sbx new --name rubydev-2 --type small --from rubydev-cacheThe save took 2.8 seconds and the new sandbox was ready in 3.9 seconds. The snapshot holds the whole disk: rbenv and Ruby 4.0.9, the ~/.profile line, every gem, libvips, and both apps. In the new sandbox:
ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +PRISM [x86_64-linux]
/home/user/.rbenv/shims/ruby
== bundle install, as saved
0.508 s
== rails test, as saved
649 runs, 3921 assertions, 0 failures, 0 errors, 2 skips
35.722 s
== rails new, from snapshot
13.253 sThat is 0.5 seconds to a ready bundle instead of 4.4 minutes of compiling Ruby plus 2.3 minutes of bundle install. The tests take the same time either way; they are CPU-bound. To name the snapshot after a hash of Gemfile.lock and .ruby-version and rebuild it when either changes, follow Cache dependencies in a named snapshot.
The first sandbox also sat idle long enough to stop on its own. sbx resume brought it back in 2.2 seconds with rbenv, the gems and both apps in place.
FAQ#
Is Ruby installed in a Sandboxer sandbox?#
Not on 2026-10-27. apt-get install ruby-full gives Ubuntu's Ruby 3.2.3 in about 4 seconds, but its gem directory needs sudo. For a current Ruby, install rbenv and ruby-build and compile it; 4.0.9 took 4.4 minutes on small.
Why does rbenv say "command not found" in sbx exec?#
rbenv init writes to ~/.bashrc, and Ubuntu's ~/.bashrc stops early in non-interactive shells. Add eval "$(~/.rbenv/bin/rbenv init - --no-rehash bash)" to ~/.profile and run commands with bash -lc.
Why does my Rails app show "Blocked hosts" on the hosted URL?#
Rails host authorization only allows localhost in development. Add config.hosts << ".box.sandboxer.com" to config/environments/development.rb and restart the server.
Where do gems go, and do they survive sbx stop?#
With rbenv, in ~/.rbenv/versions/<version>/lib/ruby/gems, under /home/user on the sandbox disk. sbx stop keeps the disk, and a named snapshot or a fork copies it.
Should I use small or default for Ruby?#
small worked for everything here, including 649 parallel tests. More vCPUs speed up the Ruby compile (make -j) and parallel tests; with a snapshot you rarely compile again.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.