// framework · ruby

Ruby, the language.

Before Rails there was Ruby. Here's how to ship the runtime itself — workers, CLIs, and small services — onto real infrastructure.

Not every Ruby workload is a Rails monolith. Data pipelines, ETL scripts, chatops bots and background workers all deserve a first-class deployment story. This guide covers Ruby 3.3+ with YJIT, memory allocator picks, and container images that stay under 200 MB.
112 MB
min container size
1.7×
YJIT speedup
480 ms
cold start (Cloud Run)
01 / 04

Pick a base image

Use the slim official image and add only what you need. Alpine works but musl vs glibc bugs will find you eventually — start with Debian slim and only optimize if the size matters.
Dockerfile
FROM ruby:3.3-slim AS build RUN apt-get update -qq && apt-get install -y build-essential git WORKDIR /app COPY Gemfile Gemfile.lock ./ RUN bundle config set --local deployment 'true' \ && bundle config set --local without 'development test' \ && bundle install --jobs 4 FROM ruby:3.3-slim WORKDIR /app COPY --from=build /usr/local/bundle /usr/local/bundle COPY . . ENV RUBY_YJIT_ENABLE=1 MALLOC_ARENA_MAX=2 CMD ["ruby", "bin/worker"]
02 / 04

Turn on YJIT

Ruby 3.3's YJIT is stable and boring — the best kind of feature. Enable it with the env var above. Expect a ~1.5–2× throughput bump on real code with almost no memory penalty.
03 / 04

Tame memory

Set MALLOC_ARENA_MAX=2 to stop glibc from ballooning RSS on multi-threaded processes. On AWS Fargate or Cloud Run this alone often halves resident memory and keeps you well inside a 512 MB container.
04 / 04

Structured logs to stdout

Every cloud logger — CloudWatch, Cloud Logging, Log Analytics, Papertrail — assumes you write JSON to stdout. Use a lean logger and never touch the filesystem for logs.
ruby
require "json" require "logger" logger = Logger.new($stdout) logger.formatter = ->(sev, time, _prog, msg) { JSON.generate(t: time.iso8601, l: sev, m: msg) + "\n" } logger.info("worker.boot", pid: Process.pid)

Gotchas we learned the hard way

  • !Don't ship a Gemfile.lock built on macOS ARM to a Linux x86 container — bundler platform mismatches will haunt you. Run `bundle lock --add-platform x86_64-linux`.
  • !Signal handling: production containers get SIGTERM. Trap it and drain in-flight work, or your job queue will orphan messages.
  • !Time zones. Set `TZ=UTC` at the container level and rely on ActiveSupport / Time.iso8601. Debugging tz drift in cron is a special hell.

Official resources

Docs, repos, and training materials for this framework.

Related guides

Ready?
Ready to deploy a Ruby worker?

Jump into a cloud guide — every one includes a ruby-only worker recipe alongside the Rails walkthrough.