JRuby on Miren
JRuby runs Ruby on the JVM. Miren auto-detects standard (MRI) Ruby from a Gemfile —
see Ruby on Miren — but to run on JRuby specifically you use a
Dockerfile.miren. Your Ruby code and gems run unchanged; they execute on the JVM.
Ask your AI coding agent to "set up this app on JRuby on Miren" after installing the
Miren agent skills. It adds the Dockerfile.miren, wires up Bundler and
the server, and deploys — using this page as its reference.
Does this source build need a Dockerfile?
Yes — to run on JRuby specifically. (Miren's auto-detection would pick MRI Ruby.) Add a
Dockerfile.miren built on the jruby image. See
Using Dockerfile.miren.
Miren auto-detects and builds common stacks (Python, Node, Bun, Go, Ruby, Rust) without a Dockerfile. This language isn't one of them yet — if you'd like first-class support, request it.
The app
A normal Sinatra app with a Gemfile — nothing JRuby-specific:
# Gemfile
source 'https://rubygems.org'
gem 'sinatra'
gem 'puma'
gem 'rackup'
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
# Sinatra 4 rejects unknown Host headers; an empty list permits any host.
set :host_authorization, permitted_hosts: []
get '/' do
content_type 'text/plain'
"Hello from JRuby on Miren!\n"
end
end
Sinatra 4 enables Rack::Protection::HostAuthorization, which only allows requests whose
Host header is on an allowlist (localhost by default). Behind Miren's router your app is
reached at its route hostname, so without configuration it returns Host not permitted.
Set host_authorization to an empty permitted_hosts list (allow any), or list your
actual hostnames. This applies to any Sinatra 4 app, MRI included.
# config.ru
require './app'
run App
Miren injects PORT and routes to it; the Dockerfile's Puma CMD below binds
0.0.0.0 on that port.
The Dockerfile
Create Dockerfile.miren in your project root. Install gems with Bundler at build time:
FROM jruby:9.4
WORKDIR /app
COPY Gemfile ./
RUN bundle install
COPY . /app
EXPOSE 8080
CMD ["sh", "-c", "exec bundle exec puma -b tcp://0.0.0.0:$PORT"]
.dockerignore
.git
Deploy
The Dockerfile's CMD starts the app. Miren uses it as the web service's startup default,
so you don't need a Procfile or service command.
JRuby's JVM startup plus Bundler and Puma can take longer than the default 15-second
port-wait, so raise port_timeout. Keeping one instance always warm (fixed scaling) also
avoids paying that cold-start on every scale-from-zero. Set both in .miren/app.toml:
name = "jruby-bench"
[services.web]
# JVM Ruby boots slowly; give it more than the 15s default to bind.
port_timeout = "120s"
[services.web.concurrency]
mode = "fixed"
num_instances = 1
miren deploy
By default Miren waits 15 seconds for a service to bind its port, then reports
nothing is listening after the port timeout. JRuby's JVM startup plus Bundler and Puma
routinely exceeds that. Raise port_timeout (e.g. "120s") so the health check waits
long enough, and use fixed scaling so a warm instance stays up.
Environment variables
Set variables with miren env set — -e for plain values, -s for secrets (masked in
output and logs). Read them with ENV['KEY']:
miren env set -e RACK_ENV=production
miren env set -s DATABASE_URL
See App Configuration — Environment Variables.
Agent quick reference
- Detection: MRI Ruby is auto-detected from a
Gemfile; useDockerfile.mirento pin JRuby - Base image:
jruby:9.4;bundle installat build time - Startup: inherited from the Dockerfile
CMD; noProcfileor service command needed - Slow boot: raise
port_timeout(e.g."120s") past the 15s default, and pinnum_instances = 1to stay warm - Port: Puma
-b tcp://0.0.0.0:$PORT - Env vars:
miren env set -e/-s; read withENV['KEY']
Next steps
- Ruby on Miren — auto-detected MRI Ruby
- TruffleRuby on Miren — Ruby on GraalVM
- Application Scaling — fixed vs. autoscaling