Running Ruby 4 with Ruby::BOX inside Docker (Alpine)

Ruby 4 with Ruby::BOX
Ruby 4 with Ruby::BOX

December 24, 2025

Ruby 4 introduces one of the most important runtime features in the history of the language: Ruby::BOX. It allows Ruby to execute multiple isolated class worlds inside the same process, finally making it possible to load conflicting libraries, plugins, and user code safely.

In this guide we will build a lightweight Alpine-based Docker image running Ruby 4.0.0-preview3 and demonstrate how Ruby::BOX enables true code isolation.


What is Ruby::BOX?

Article content

Ruby::BOX creates independent class universes inside a single Ruby VM.

Each Box has its own:

  • classes
  • modules
  • constants
  • $LOAD_PATH
  • require state

But they all share:

  • memory
  • threads
  • garbage collector
  • the same Ruby process

This means two Boxes can load two different implementations of the same class name without collisions.

That was impossible in Ruby before 4.0.


Advertise on RubyStackNews

RubyStackNews is a niche publication read by Ruby and Rails developers worldwide. Our audience includes senior engineers, tech leads, and decision-makers from the US, Europe, and Asia.

Sponsorship Options

📝 Article Sponsorship
Your brand featured inside a technical article (clearly marked as sponsored).
📌 Inline Sponsored Block
Highlighted sponsor section embedded within an article.
📎 Sidebar Sponsor
Logo + link displayed site-wide in the sidebar.
  • Highly targeted Ruby / Rails audience
  • Organic traffic from search and developer communities
  • No ad networks — direct sponsorships only

Interested in sponsoring RubyStackNews?

Contact via WhatsApp

Why this matters

Historically, Ruby has one global class space:

class User; end

Every gem, framework, and application defines into the same world. If two libraries define User, one overwrites the other.

With Ruby::BOX, each piece of code can live in its own isolated universe.

This unlocks:

  • plugin systems
  • multi-tenant Ruby runtimes
  • marketplaces of Ruby extensions
  • safe execution of third-party code
  • loading multiple versions of the same library

Ruby 4 in Docker (Alpine)

Article content

We use Alpine to keep the image small and compile Ruby 4 from source.

FROM alpine:3.20

RUN apk add --no-cache \
  build-base \
  linux-headers \
  bison \
  openssl-dev \
  yaml-dev \
  readline-dev \
  zlib-dev \
  ncurses-dev \
  libffi-dev \
  gdbm-dev \
  db-dev \
  wget \
  ca-certificates \
  tar \
  autoconf

WORKDIR /usr/src

RUN wget https://cache.ruby-lang.org/pub/ruby/4.0/ruby-4.0.0-preview3.tar.gz \
 && tar -xzf ruby-4.0.0-preview3.tar.gz

WORKDIR /usr/src/ruby-4.0.0-preview3

RUN ./configure \
      --disable-install-doc \
      --enable-shared \
 && make -j$(nproc) \
 && make install

WORKDIR /app

Enabling Ruby::BOX

Ruby::BOX is activated through an environment variable.

In Docker Compose:

version: "3.9"

services:
  ruby4:
    build: .
    container_name: ruby4_preview
    volumes:
      - .:/app
    environment:
      RUBY_BOX: 1

This tells Ruby 4 to run with Box support enabled.


Two libraries, same name, different behavior

We will create two libraries that define the same class but behave differently.

lib/v1/logger.rb

class Logger
  def format(msg)
    "[V1] #{msg.upcase}"
  end
end

lib/v2/logger.rb

class Logger
  def format(msg)
    "[V2] #{msg.upcase}"
  end
end

Both define Logger, but their behavior is different.


Running them inside two Boxes

b1 = Ruby::Box.new
b1.eval <<~R
  $LOAD_PATH.unshift "/app/lib/v1"
  require "logger"

  class Foo
    def hello
      l = Logger.new
      l.format("Hello Ruby")
    end
  end
R

b2 = Ruby::Box.new
b2.eval <<~R
  $LOAD_PATH.unshift "/app/lib/v2"
  require "logger"

  class Foo
    def hello
      l = Logger.new
      l.format("Hello Ruby")
    end
  end
R

puts b1::Foo.new.hello
puts b2::Foo.new.hello

Run it:

docker compose run --rm ruby4 ruby main.rb

Output:

[V1] HELLO RUBY
[V2] HELLO RUBY

Even though both define Logger and Foo, they live in completely separate worlds.


What just happened?

Inside a single Ruby process:

Ruby VM
 ├── Box 1 → Logger (v1), Foo
 ├── Box 2 → Logger (v2), Foo
 └── Global

The classes do not collide. They do not overwrite each other. They do not even know the other exists.

This is a true class loader inside Ruby.


Why this changes Ruby forever

With Ruby::BOX, Ruby becomes a platform, not just a language.

You can now build:

  • plugin marketplaces
  • Rails engines running side-by-side
  • SaaS multi-tenant runtimes
  • safe execution of user-supplied Ruby
  • isolated extensions for frameworks

All inside one Ruby process.

Ruby has crossed the boundary into modular runtime architecture.

🚀 Ruby 4 Docker Sandbox — Ruby::Box

This repository contains a lightweight Alpine + Docker environment for testing Ruby 4.0 and the new Ruby::Box runtime feature. It demonstrates how multiple isolated class worlds can run inside a single Ruby process.

Ruby::Box enables plugin systems, safe user code execution, and side-by-side loading of conflicting libraries — something Ruby has never been able to do before.

Download the Ruby 4 Docker Sandbox

Includes Dockerfile, docker-compose.yml, and a working Ruby::Box example.

Article content

Leave a comment