A Rails application template for building Rails monoliths with AI agents — Claude Code, Cursor, Codex and friends — rather than alongside them.
rails new gives you a great skeleton. This adds the parts an agent needs to be productive on day one: an opinionated architecture with written-down conventions, an authorization layer that fails loudly when you forget it, API docs generated from the test suite, a real test pyramid (RSpec + Playwright), and agent harness configuration that is checked into the repo instead of living in someone's home directory.
rails new myapp \
--database=postgresql \
--css=tailwind \
--skip-test \
--skip-action-cable \
-m https://raw.githubusercontent.com/rubyroidlabs/rails-template/main/template.rbThen:
cd myapp
docker compose up -d # Postgres + MailHog
bin/setup # gems, database, dev serverRequires Ruby >= 4.0 and Rails >= 8.0 — the template checks both and stops early if they aren't met. It does not pin either any further, so you always get the newest Rails you have installed.
PostgreSQL and Tailwind are required (config/database.yml, docker-compose.yml, the CI workflow and the UI conventions are all written against them); the template refuses to run without those two flags. --skip-test and --skip-action-cable are recommended rather than required — if a Minitest test/ tree slips through, the template removes it so the app has exactly one test suite.
To run it from a local checkout instead of GitHub:
git clone https://github.com/rubyroidlabs/rails-template.git
rails new myapp --database=postgresql --css=tailwind --skip-test --skip-action-cable \
-m rails-template/template.rbRAILS_TEMPLATE_REPO and RAILS_TEMPLATE_BRANCH override where the template fetches its files from when run by URL — useful for testing a branch.
Vanilla rails new on Rails 8 — Propshaft, import maps, Hotwire (Turbo + Stimulus), Solid Queue, Solid Cache, Kamal, Thruster, Brakeman, bundler-audit and RuboCop Omakase all stay exactly as Rails ships them. Everything below is layered on top.
| Pundit | Authorization. ApplicationController includes Pundit::Authorization and rescues NotAuthorizedError; ApplicationPolicy denies by default. |
| Interactor | Business logic in app/interactors/, one interactor per business action, organizers for multi-step flows. |
| Blueprinter + Oj | JSON serialization. One blueprint per resource in app/blueprints/, never inline hashes in controllers. |
| Heroicon | Icons, paired with Tailwind. No second icon set or CSS framework. |
| RSpec | Replaces Minitest. rails_helper already wires up FactoryBot syntax methods, pundit-matchers and spec/support/**. |
| FactoryBot + Faker | Test data. No fixtures. |
| rswag | Request specs double as the OpenAPI source. bundle exec rake rswag:specs:swaggerize regenerates swagger/v1/swagger.yaml; Swagger UI is mounted at /api-docs. |
| Playwright | End-to-end browser tests in e2e/, with a TypeScript config that boots its own Rails server on port 3100. |
| ESLint | Flat config for the Stimulus controllers in app/javascript. Bundler-free, matching the import-map setup. |
A health-check request spec, rswag spec and Playwright spec ship as working examples of each layer.
docker-compose.yml runs PostgreSQL 18 and MailHog. Development mail is delivered to MailHog over SMTP and readable at http://localhost:8025, so nothing escapes to a real inbox. config/database.yml reads DATABASE_{HOST,PORT,USERNAME,PASSWORD} so the same file works locally and in CI.
.github/workflows/ci.yml runs five jobs: Ruby security scans (Brakeman + bundler-audit), an import-map audit, lint (RuboCop with a warm cache + ESLint), RSpec, and Playwright with its report uploaded on failure. bin/ci runs the equivalent pipeline locally from config/ci.rb.
This is the part rails new doesn't give you.
AGENTS.md— the engineering contract: architecture rules, the authorization invariant, serialization rules, testing rules, and the definition of done. Agents read it; so should humans.bin/setup-agents— installs the agent tooling with each tool's own installer, then leaves the harness config it generates (.claude/,.codex/,.cursor/,.agents/skills/) checked into the repo so every contributor and every agent gets the same setup.rails newruns it for you; re-run it to upgrade. Nothing is vendored into this template by hand, so the skills you get are the ones their authors are shipping today.- mattpocock/skills — installed as a Claude Code plugin, and copied under
.agents/skills/(pinned inskills-lock.json) for Codex and Cursor. Gives you/implement,/code-review,/research,/grill-with-docsand the rest as first-class workflows instead of ad-hoc prompting. - graphify — a knowledge-graph skill wired into all three harnesses, with hooks that push agents to query the graph before grepping. Run
/graphifyonce in the new repo to build it;graphify-out/is gitignored. docs/agents/— how agents should use the domain docs (CONTEXT.md+docs/adr/, created lazily).
Nothing secret is in this repo. Every generated app gets its own config/master.key and config/credentials.yml.enc straight from rails new; the key is gitignored and never leaves your machine.
template.rb the application template — the only file rails new downloads
template/ everything copied into the generated app
AGENTS.md engineering conventions for the new app
bin/setup-agents installs the skills + graphify tooling in the new app
spec/ e2e/ swagger/ test suites and generated API docs
*.tt ERB templates interpolating the app name
AGENTS.md instructions for an agent bootstrapping a project with this template
When the template is run by URL, template.rb shallow-clones this repository to a temp directory so it can copy template/, then cleans up after itself.
Try a change end to end before pushing it:
rails new /tmp/scratch/demo_app --database=postgresql --css=tailwind \
--skip-test --skip-action-cable -m ./template.rbFiles under template/ are copied verbatim unless they end in .tt, which are ERB templates rendered with the generator's context — app_name is the underscored application name. Runtime ERB inside a .tt file (for example in config/database.yml.tt) must be escaped as <%%= ... %> so it survives to the generated app.
MIT — see LICENSE.