PmRails is a toolset for testing and developing Ruby on Rails applications without installing Rails or its dependencies on your local machine. It leverages Podman to create an isolated, containerized environment for your Rails projects.
- Clean Local Environment: No need to install Rails or dependencies locally.
- Quick Setup: Start developing immediately once Podman is installed.
- Consistent and Reproducible Environments: Isolated containers prevent dependency conflicts, making it ideal for team collaboration.
- Experiment Freely: Safely test different Rails versions or configurations.
PmRails provides the following commands:
-
pmrails-new: Creates a new Rails application as a wrapper aroundrails new.
Usage:pmrails-new RAILS_VERSION APP_PATH [OPTIONS] -
pmrails-new-plus: Performs the typical setup tasks for developing a new Rails application with PmRails in a single step.
Usage:pmrails-new-plus RAILS_VERSION APP_PATH [OPTIONS] -
pmrails-init: Generates a standard set of PmRails configuration files for an existing Rails application.
Usage:pmrails-init [OPTIONS] -
pmrails-run: Runs an arbitrary command inside a single Rails container with project-local runtime directories.
Usage:pmrails-run COMMAND [OPTIONS] -
pmrails-compose: Wrapspodman-composeto operate the project's Compose environment.
Usage:pmrails-compose [GLOBAL_OPTIONS] COMMAND [COMMAND_OPTIONS]
The following legacy commands are kept for backward compatibility and will be removed in a future release:
pmrails→ usepmrails-run bin/railsinstead.pmbundle→ usepmrails-run bundleinstead.pmrailsenvexec→ usepmrails-runinstead.
You must have Podman installed. Follow the Podman Installation Instructions for your operating system.
If you plan to use pmrails-compose (Mode 3), you also need podman-compose.
Important: You must install a recent version of
podman-composefrom PyPI. The versions provided by default OS package managers (such asapt) are often too old and may not work correctly with PmRails. We highly recommend usingpipxfor the installation.
Example installation on Ubuntu/Debian using pipx:
# Install pipx
sudo apt update
sudo apt install pipx
pipx ensurepath
# Reload your shell to apply PATH changes
exec $SHELL -l
# Install the latest podman-compose from PyPI
pipx install podman-composeOn other operating systems, install pipx by following the official pipx installation guide, and then run pipx install podman-compose. For more details, see the podman-compose repository.
Download PmRails to your preferred location. For example:
mkdir -p ~/.var
cd ~/.var
git clone https://github.com/wakairo/pmrails.gitAdd the bin directory to your system's PATH environment variable. For example, using bash:
echo 'export PATH="$HOME/.var/pmrails/bin:$PATH"' >> ~/.bashrc
exec $SHELL -lPmRails ships with a small aliases file that defines shorthand aliases for the most common invocations.
Sourcing it lets you replace long commands like:
pmrails-compose exec rails-app bin/rails consolewith shorter ones like:
pmrails-crails consoleTo install the aliases, append the file to your shell's startup script and reload the shell. For bash:
cat ~/.var/pmrails/aliases >> ~/.bashrc
exec $SHELL -lThis adds the following aliases:
# pmrails aliases
alias pmrails-rrails='pmrails-run bin/rails'
alias pmrails-crails='pmrails-compose exec rails-app bin/rails'
alias pmrails-cmpexe='pmrails-compose exec rails-app'PmRails has three primary modes:
- Create a new Rails application only — runs
rails newinside a container. - Create and develop with a single Rails container — installs gems into
.pmrails/var/bundle/, then usespmrails-runfor day-to-day Rails commands. - Create and develop with Compose — installs gems into
.pmrails/var/bundle/, then uses.pmrails/compose.yamlandpmrails-composeto operate a multi-container environment (Rails + database + Selenium, etc.).
These modes share the same building blocks and can be combined freely:
- A custom Rails container image (
.pmrails/Dockerfile) can be used with or without a multi-container setup. - A multi-container setup (
.pmrails/compose.yaml) can be used with or without a custom image. pmrails-initgenerates bothDockerfileandcompose.yaml, but you can keep only what you need.
Use this mode when you only want to generate the application files and intend to run the application in another environment.
pmrails-new behaves the same as rails new.
Navigate to a temporary directory. For example:
mkdir -p ~/tmp
cd ~/tmpCreate a new Rails application, specifying the Rails version and any rails new options you need. For example:
pmrails-new 8.1 new_app --database=postgresqlUse this mode when you plan to continue developing the application with PmRails using a single container that runs Rails directly.
Gems are installed into the local .pmrails/var/bundle/ directory, so the application can be developed without relying on the host Ruby environment.
Note: This section shows development with SQLite. However, you can use external databases (PostgreSQL, MySQL, etc.) by configuring your application — see Using an External Database below for examples.
Navigate to a temporary directory. For example:
mkdir -p ~/tmp
cd ~/tmpCreate a new Rails application with pmrails-new-plus:
pmrails-new-plus 8.1 sample_appWhen using this command, any rails new options can be specified after the application name.
pmrails-new-plus automatically performs the following tasks:
- Creates a new Rails application
- Installs gems into
.pmrails/var/bundle/ - Adds
/.pmrails/var/and/.pmrails/config.localto.gitignore
Navigate to the application directory:
cd sample_appUse pmrails-run to run Rails commands. For example, to start the server:
pmrails-run bin/rails server -b 0.0.0.0Then, open your web browser and navigate to http://localhost:3000/.
# Run Bundler to install gems
pmrails-run bundle install
# Run database migrations
pmrails-run bin/rails db:migrate
# Or, with the alias:
pmrails-rrails db:migrate
# Open the Rails console
pmrails-run bin/rails console
# Or, with the alias:
pmrails-rrails consoleTip: You can add a
.pmrails/Dockerfileto customize the Rails container image used in this mode. Compose is not required to take advantage of a custom image — see Custom Rails Container Image.
Use this mode when you want PmRails to spin up Rails together with a database, a Selenium browser, or other services as separate containers.
Gems are still installed into the local .pmrails/var/bundle/ directory, so the host Ruby environment stays untouched.
In this mode, think of the Compose environment as a long-lived workspace, not a one-shot container.
You usually bring it up once, run many exec commands while it is running, stop it when you want to pause, start it again when you return, and finally tear it down when you are done.
One important rule follows from that model: once you are working with Compose, run your day-to-day Rails commands through pmrails-compose exec rails-app ..., not pmrails-run.
pmrails-run starts an isolated container and cannot talk to the database, Selenium, or other services managed by Compose.
First, create a new Rails application. For example:
mkdir -p ~/tmp
cd ~/tmp
pmrails-new-plus 8.1 sample_app --database=postgresqlThen move into the new application directory and generate the PmRails setting files:
cd sample_app
pmrails-init --database=postgresqlWhen --database is given, pmrails-init writes a compose.yaml that includes a matching database service.
Supported values are sqlite3 (default), postgresql, mysql, trilogy, mariadb-mysql, and mariadb-trilogy.
pmrails-init creates .pmrails/config, .pmrails/Dockerfile, and .pmrails/compose.yaml.
These files can be used independently or together — see Configuration for details.
It also patches test/application_system_test_case.rb (when present) so that system tests can drive the Selenium container provided by Compose.
Tip:
pmrails-initis safe to run multiple times. If configuration files already exist, it preserves your custom edits and writes the newly generated versions to files with a.pmrails-initsuffix instead.
Tip: A
.pmrails/Dockerfileis optional in this mode as well. If you only have a.pmrails/compose.yaml, PmRails uses an upstreamrubyimage instead of building a project-specific image.
The usual workflow is:
- Bring the environment up with
pmrails-compose up -d. - Do your work with
pmrails-compose exec rails-app ...while it is running. - Pause it with
pmrails-compose stopwhen you want to come back later. - Resume it with
pmrails-compose start. - Remove it with
pmrails-compose downwhen you are done.
Start with:
pmrails-compose up -dUse up the first time you use the project, after changing .pmrails/compose.yaml, or whenever you are unsure what state the environment is in.
If the services do not exist yet, up creates them. If they already exist but are stopped, up starts them again.
Once the environment is running, do your work with exec:
pmrails-compose exec rails-app bundle install
pmrails-compose exec rails-app bin/rails db:migrate
pmrails-compose exec rails-app bin/rails console
pmrails-compose exec rails-app bin/rails serverIf you use the aliases, the same commands become:
pmrails-cmpexe bundle install
pmrails-crails db:migrate
pmrails-crails console
pmrails-crails serverIf you start the Rails server, open http://localhost:3000/ in your browser.
When you want to pause work without deleting the environment, run:
pmrails-compose stopWhen you return, resume that stopped environment with:
pmrails-compose startUse start when you simply want to continue a previously stopped environment as-is.
If you changed .pmrails/compose.yaml, use pmrails-compose up -d instead so Compose can reconcile the environment with the current configuration.
When you are truly finished and want to remove the Compose-managed containers and network, run:
pmrails-compose downNamed volumes are kept by default, so database data is preserved across normal down / up cycles.
To remove the volumes too and fully wipe the database data, run:
pmrails-compose down -vThe following diagram shows the basic lifecycle of a Compose environment:
Practical points:
upis the general-purpose "make it match the current configuration" command. It brings the environment to Running from either Base (Not created) or Stopped, and is also safe to run when the environment is already Running.startis narrower: it only resumes an already-created stopped environment and never recreates it from scratch. When the configuration has not changed, pairingstopwithstartis the fastest way to pause and resume work, sincestopleaves containers in place rather than deleting them.downtears the environment back down to Base (Not created) by removing the Compose-managed containers and network. Named volumes survive unless you also pass-v, so database data is preserved across a normal teardown.
PmRails is configured through a combination of:
- Configuration files —
configfiles at multiple scopes. - Environment variables set by the caller — override anything set by configuration files.
- A custom
Dockerfileat.pmrails/Dockerfile(optional) — controls the Rails container image. - A custom
compose.yamlat.pmrails/compose.yaml(optional) — describes the multi-container environment.
PmRails sources configuration files from four scopes. Files that do not exist or are unreadable are silently skipped, and later scopes override earlier ones:
| Scope | Path | Typical Use |
|---|---|---|
| System | /etc/pmrails/config (override path: PMRAILS_SYS_CONF) |
Defaults set by a system administrator. |
| User | ${XDG_CONFIG_HOME:-${HOME}/.config}/pmrails/config |
Defaults specific to your user account on this machine. |
| Project | ./.pmrails/config |
Project-shared settings (commit this file). |
| Project-local | ./.pmrails/config.local |
Per-developer overrides for this project (gitignored). |
Tip: To use a system config path other than
/etc/pmrails/config, set thePMRAILS_SYS_CONFenvironment variable (for example,export PMRAILS_SYS_CONF="/usr/local/etc/pmrails/config"). This is useful on hosts where/etc/is read-only or unavailable, such as some immutable distributions.
Note:
pmrails-new-plusadds both/.pmrails/var/and/.pmrails/config.localto the project's.gitignore, so project-local overrides are not committed.
Each file is a POSIX shell script that is sourced by the PmRails entry point. Most commonly, you set PMRAILS_* variables. For example:
# .pmrails/config
PMRAILS_PORTS="3000:3000 5000:5000"
PMRAILS_RUBY_VERSION_AT_NEW="3.4.8"Warning: Configuration files are sourced directly in the current shell. Only place trusted content, such as variable settings, in them.
The variables below can be set in a configuration file, exported in your shell, or passed inline before a pmrails-* command (for example: PMRAILS_PORTS=8080:3000 pmrails-run bin/rails server -b 0.0.0.0).
Selects the Ruby version (and therefore the upstream ruby:<version> container image) used by pmrails-run and pmrails-compose. When unset, PmRails reads the version from .ruby-version in the project root; see Ruby Version Resolution for the precise rules.
PMRAILS_RUBY_VERSION="3.3.7"Selects the Ruby version used to generate new Rails applications (pmrails-new and pmrails-new-plus). Defaults to latest. Use this to pin the generation environment to a specific Ruby release rather than relying on latest:
# Generate a new Rails 8.1 application using Ruby 3.4.8 instead of `latest`
PMRAILS_RUBY_VERSION_AT_NEW=3.4.8 pmrails-new-plus 8.1 sample_appConfigures the port mappings published by pmrails-run, and by the rails-app service in pmrails-compose. Each token is either HOST_PORT:CONTAINER_PORT or CONTAINER_PORT alone; multiple mappings are separated by spaces. When only a container port is given, the host port is auto-allocated by Podman; check the assigned port with podman port <container>.
Default:
PMRAILS_PORTS="3000:3000"Examples (used inline before a command):
# Publish container port 3000 on host port 3001
PMRAILS_PORTS="3001:3000" pmrails-run bin/rails server -b 0.0.0.0
# Run a one-shot command without publishing any ports (useful for analyzers
# and other CLI tools that do not need to expose listening ports to the host)
PMRAILS_PORTS= pmrails-run bin/brakeman
# Publish multiple ports / port ranges
PMRAILS_PORTS="3000:3000 1234-1236:1234-1236" pmrails-run bin/rails server -b 0.0.0.0Overrides the project name. PmRails uses it as the podman-compose project name (-p flag) and as part of the project-specific image repository name. When unset, PmRails derives it from the basename of the current directory (sanitized to alphanumerics and underscores, truncated to 16 characters).
PMRAILS_PROJECT_NAME="sample_app"Path to the project Dockerfile. Defaults to .pmrails/Dockerfile. When the file exists, pmrails-run and pmrails-compose build and use a project-specific image (pmrails-${PMRAILS_PROJECT_NAME}); when it does not, an upstream ruby image is used directly. See Custom Rails Container Image.
Path to the project Compose configuration file. Defaults to .pmrails/compose.yaml. pmrails-compose requires this file to exist and exits with an error otherwise.
If .pmrails/Dockerfile exists, pmrails-run (and the rails-app service in pmrails-compose) builds and uses a project-specific image named pmrails-${PMRAILS_PROJECT_NAME}:${PMRAILS_RUBY_VERSION} instead of the upstream ruby image. This lets you preinstall system packages, native build tools, or other dependencies that your Rails application needs.
pmrails-init generates a sensible Dockerfile that matches the database engine selected via --database. You can also write your own from scratch.
A custom Dockerfile is optional in both Mode 2 and Mode 3. In Mode 2 it lets you customize the single Rails container without Compose.
If .pmrails/compose.yaml exists, pmrails-compose layers it on top of an internal base file and an auto-generated overlay (which carries the PMRAILS_PORTS mapping for the rails-app service). The merge order, with later files overriding earlier ones, is:
- PmRails-internal base (
share/compose.base.yaml). - Auto-generated overlay.
- Your
.pmrails/compose.yaml.
pmrails-init generates a compose.yaml that includes a service for the chosen database (SQLite3 needs none) and a Selenium service for system tests. You can edit this file freely or replace it entirely.
Important: When modifying or replacing this file, you must use
rails-appas the service name for your Rails container. PmRails internal commands and auto-generated configurations explicitly rely on this exact service name to function correctly.
PmRails keeps all project-specific runtime files (gems, caches, configs, state)
inside a project-local directory named .pmrails/var/.
It sets environment variables to direct the containerized process to use these paths.
This design keeps your host user account clean and ensures the project is self-contained.
The following table shows how environment variables are mapped to project-local directories:
| Environment Variable (Container) | Project Path (Repo Root) | Purpose |
|---|---|---|
HOME |
.pmrails/var/home |
Process HOME — where tools write dotfiles |
XDG_CACHE_HOME |
.pmrails/var/cache |
Tool caches |
XDG_CONFIG_HOME |
.pmrails/var/config |
Per-user configuration files |
XDG_DATA_HOME |
.pmrails/var/share |
Optional data files used by some tools |
XDG_STATE_HOME |
.pmrails/var/state |
Optional state files used by some tools |
BUNDLE_PATH |
.pmrails/var/bundle |
Bundler gem installation path (project-local gems) |
- Cleanliness: Keeps the host user's
~/.gem,~/.bundle, and other personal files untouched. - Isolation: Makes project state local and easy to reset.
- Git: Do not commit
.pmrails/var/to source control.pmrails-new-plusadds it to.gitignoreautomatically. - Reset:
.pmrails/var/is safe to remove. If you encounter issues, runrm -rf .pmrails/varand thenpmrails-run bundle installorpmrails-compose exec rails-app bundle installto reset the environment. - Security: In multi-user environments, ensure
.pmrails/is readable only by your user (e.g.,chmod -R go-rwx .pmrails), as it may contain credentials or cached data.
PmRails determines which Ruby version to use based on the presence and content
of a .ruby-version file in the current directory.
The following commands read .ruby-version to determine the Ruby version:
pmrails-runpmrails-compose
(pmrails-new and pmrails-new-plus use PMRAILS_RUBY_VERSION_AT_NEW instead, since the project being generated does not yet have a .ruby-version.)
-
When
.ruby-versionexists: PmRails extracts a Ruby version from the first line of the file and uses the corresponding container image. -
When
.ruby-versiondoes not exist: PmRails defaults to usingruby:latest.
PmRails looks for a MAJOR.MINOR.PATCH pattern
on the first line of .ruby-version
and uses the first match found.
Accepted examples:
3.2.2ruby-4.0.1(the numeric4.0.1part is extracted)
If no MAJOR.MINOR.PATCH sequence is found on the first line,
the command exits with an error.
This design choice ensures unambiguous and reproducible container image selection.
The value read from .ruby-version is used directly as a container image tag:
ruby:<major.minor.patch>
For example:
.ruby-version:3.2.2->ruby:3.2.2
PmRails does not perform version normalization or compatibility checks.
Changing the Ruby version in .ruby-version effectively switches the container
image used by PmRails.
When doing so, previously generated local data (such as installed gems under
.pmrails/var/) may no longer be compatible. If you encounter issues after changing
the Ruby version, removing the .pmrails/var/ directory and reinstalling gems
is usually sufficient.
Tip: You can also override the resolved version explicitly by setting
PMRAILS_RUBY_VERSIONin a configuration file or as an environment variable; see Configuration Environment Variables.
PmRails can connect to a database running on the host or to a separately run database container.
One convenient method to have Rails (running inside a PmRails container) reach such a database is to use host.containers.internal.
Though the following example is for PostgreSQL, the same approach works for other databases: start a database container on the host with the -p option to publish its port, and set host: host.containers.internal in database.yml with the appropriate adapter and credentials.
Run PostgreSQL as a host-bound container:
podman run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=your_password postgres:latestPoint your Rails application to the host database by using host.containers.internal:
default: &default
adapter: postgresql
encoding: unicode
max_connections: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: sample_app_development
username: postgres
password: your_password
host: host.containers.internal
test:
<<: *default
database: sample_app_test
username: postgres
password: your_password
host: host.containers.internalAfter editing the config, run your usual PmRails commands (for example, pmrails-run bin/rails db:create and pmrails-run bin/rails server -b 0.0.0.0). The Rails process inside the PmRails container should then connect to the PostgreSQL server running in another container on the host.
Useful commands for lifecycle management of the host PostgreSQL container:
# Stop the postgres container
podman stop postgres
# Start (resume) the postgres container
podman start postgres
# Remove the postgres container (before removing, stop the container)
podman rm postgresNote: If you prefer running the database as part of the same Compose stack as your Rails application, see Mode 3: Create and Develop with Compose.
PmRails is designed as a lightweight, predictable wrapper around Podman. To maintain simplicity and transparency, it makes several assumptions and trade-offs.
On systems with SELinux enabled, mounted host directories may not be writable from inside the container.
- PmRails does not automatically apply
:zor:Zmount options. - If access is denied, users are expected to adjust SELinux contexts manually (for example, using
chcon). - This behavior is intentional to avoid implicitly weakening SELinux security policies.
See the contributing guide.