Rails project setup
Agent skills for working with Ruby on Rails 8+
npx -y skills add mickzijdel/rails-toolkit --skill rails-project-setupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when setting up a new Rails 8 project with modern stack (Solid Queue, Solid Cache, Solid Cable, Kamal deployment)
SKILL.md
11.5 KB, ~3.2k tokens by cl100k_base, as published. Nobody here has run it
Rails 8 Project Setup
1. Gemfile Essentials
The Solid Stack (solid_queue, solid_cache, solid_cable) plus propshaft, importmap-rails, kamal, and thruster — a complete Rails 8 setup with no Redis dependency.
# cooldown: gems must be public 4 days before resolving (supply-chain defense, Bundler 4.0.6+)
source "https://rubygems.org", cooldown: 4
gem "rails", "~> 8.1"
# Assets & front end
gem "importmap-rails" # JavaScript without Node bundling
gem "propshaft" # modern asset pipeline (replaces Sprockets)
gem "stimulus-rails"
gem "turbo-rails"
# Deployment and drivers
gem "bootsnap", require: false
gem "kamal", require: false # zero-downtime deployment
gem "puma", ">= 5.0"
gem "solid_cable", ">= 3.0" # database-backed ActionCable
gem "solid_cache", "~> 1.0" # database-backed cache store
gem "solid_queue", "~> 1.2" # database-backed job queue
gem "sqlite3", ">= 2.0"
gem "thruster", require: false # HTTP/2 proxy (replaces nginx for simple setups)
gem "trilogy", "~> 2.9" # MySQL adapter, faster than mysql2
# Operations
gem "mission_control-jobs" # web UI for Solid Queue
Bundler cooldown (supply-chain defense):
cooldown: 4on thesourceline means gems must have been public for 4 days before Bundler will resolve them, so freshly-hijacked releases can be vetted/yanked first (announcement, Bundler 4.0.6+).- Complement it machine-wide with
bundle config set --global cooldown 4; the explicit per-Gemfile line documents the policy and survives global-config changes. - Scope: own projects only — never add it to vendored gem Gemfiles,
.ruby-lsp/Gemfile, or appraisal gemfiles.
Vite + pnpm apps (alternative front-end stack):
- importmap-rails/no-Node is the default for new apps, but apps with heavier JavaScript needs may run Vite (+ Tailwind, Stimulus) alongside Propshaft, with Propshaft still serving page-specific assets from
app/assets. Don't migrate such an app to importmaps. - When Node packages are involved, use pnpm — not npm, yarn, or bun.
Optional add-on gems (beyond the defaults)
The stack above is everything a new app needs. The toolkit blesses a short list of add-ons beyond it — reach for each only when the need is real; the linked skill covers it in depth.
# Gemfile — add only what the app actually uses
gem "simple_form" # form-builder DSL (DRYs labels/wrappers); plain `form_with` is fine too
gem "friendly_id" # SEO slugs in URLs — see [[rails-models]]
gem "pundit" # per-resource authorization policies — see [[rails-security]]
group :development, :test do
gem "stackprof" # CPU/wall profiler — see [[rails-performance]] / [[rails-testing]]
gem "test-prof" # test-suite profiling (mainly inherited factory suites) — see [[rails-testing]]
end
group :test do
gem "shoulda-matchers" # one-line model declaration checks — see [[rails-testing]]
gem "shoulda-context" # `should` macro for Minitest
end
Already standard elsewhere in the toolkit (don't re-decide these): pagy (pagination — [[rails-api]]), prosopite (N+1 detection — [[rails-performance]]), simplecov + simplecov_json_formatter (coverage — [[rails-testing]]), bundler-audit + brakeman (security audit — [[rails-audit]]).
Deliberately not recommended (a default already covers the need): kaminari → use pagy; bullet → use prosopite; good_job → use solid_queue; devise → Rails 8 built-in auth ([[rails-security]]); factory_bot/faker → fixtures-first ([[rails-testing]]); dry-rb/Sorbet/reek → against the vanilla-Rails grain ([[rails-philosophy]]).
2. Solid Queue Configuration
config/queue.yml defines dispatchers and workers, with process counts derived from CPU count:
default: &default
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: [ "default", "solid_queue_recurring" ]
threads: 3
processes: <%= Integer(ENV.fetch("JOB_CONCURRENCY") { Concurrent.physical_processor_count }) %>
polling_interval: 0.1
development: *default
test: *default
production: *default
For queue naming (SLO tiers, worker-per-queue), see [[rails-jobs]] Pattern 5.
# config/puma.rb — run Solid Queue inside Puma, no separate worker process
unless ENV["SOLID_QUEUE_IN_PUMA"] == "false"
plugin :solid_queue
end
3. Solid Cache Configuration
# config/cache.yml
default_options: &default_options
store_options:
max_age: <%= 60.days.to_i %>
namespace: <%= Rails.env %> # isolate cache per environment
default_connection: &default_connection
database: cache
default: &default
<<: *default_connection
<<: *default_options
development: *default
test: *default_options
production: *default
4. Solid Cable Configuration
# config/cable.yml
cable: &cable
adapter: solid_cable
connects_to:
database:
writing: cable
reading: cable
polling_interval: 0.1.seconds
message_retention: 1.day
development: *cable
test:
adapter: test
production: *cable
5. Database Setup (Multiple Databases)
Separate databases for primary data, cache, queue, and cable prevent contention and allow independent scaling. Each gets its own migrations_paths.
# SQLite
default: &default
adapter: sqlite3
pool: 5
timeout: 5000
production:
primary:
<<: *default
database: storage/production.sqlite3
schema_dump: schema_sqlite.rb
cable:
<<: *default
database: storage/production_cable.sqlite3
migrations_paths: db/cable_migrate
cache:
<<: *default
database: storage/production_cache.sqlite3
migrations_paths: db/cache_migrate
queue:
<<: *default
database: storage/production_queue.sqlite3
migrations_paths: db/queue_migrate
# MySQL via trilogy — note the much larger pool in production
default: &default
adapter: trilogy
host: <%= ENV.fetch("MYSQL_HOST", "127.0.0.1") %>
port: <%= ENV.fetch("MYSQL_PORT", "3306") %>
username: <%= ENV.fetch("MYSQL_USER", "root") %>
password: <%= ENV["MYSQL_PASSWORD"] %>
pool: 50
timeout: 5000
production:
primary:
<<: *default
database: app_production
cable:
<<: *default
database: app_production_cable
migrations_paths: db/cable_migrate
queue:
<<: *default
database: app_production_queue
migrations_paths: db/queue_migrate
cache:
<<: *default
database: app_production_cache
migrations_paths: db/cache_migrate
6. Kamal Deployment
config/deploy.yml: servers, automatic SSL via the proxy, secrets from .kamal/secrets, and persistent volumes for SQLite/Active Storage.
service: app
image: app
servers:
web:
- app.example.com
ssh:
user: root
# Automatic SSL
proxy:
ssl: true
host: app.example.com
# Secrets come from .kamal/secrets; clear vars are plain
env:
secret:
- SECRET_KEY_BASE
- SMTP_USERNAME
- SMTP_PASSWORD
clear:
BASE_URL: https://app.example.com
SOLID_QUEUE_IN_PUMA: true # jobs run in the web container
registry:
server: localhost:5555
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password"
# Persistent storage for sqlite and Active Storage
volumes:
- "app_storage:/rails/storage"
# Bridge assets between versions during zero-downtime deploys
asset_path: /rails/public/assets
builder:
arch: amd64
7. Dockerfile Best Practices
Multi-stage build with jemalloc, bootsnap precompilation, and a non-root user:
# syntax=docker/dockerfile:1
# check=error=true
ARG RUBY_VERSION=3.4.7
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
WORKDIR /rails
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 libssl-dev && \
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development:test" \
LD_PRELOAD="/usr/local/lib/libjemalloc.so"
# Build stage
FROM base AS build
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
COPY Gemfile Gemfile.lock vendor ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile -j 1 --gemfile
COPY . .
RUN bundle exec bootsnap precompile -j 1 app/ lib/
# SECRET_KEY_BASE_DUMMY=1 allows asset precompilation without real secrets
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Final stage
FROM base
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash
USER 1000:1000
COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --chown=rails:rails --from=build /rails /rails
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
#!/bin/bash -e
# bin/docker-entrypoint — auto-migrate on deploy when starting the server
if [ "${1}" == "./bin/thrust" ] && [ "${2}" == "./bin/rails" ] && [ "${3}" == "server" ]; then
MIGRATE=1 ./bin/rails db:prepare
fi
exec "${@}"
8. Recurring Jobs Configuration
Scheduled tasks live in config/recurring.yml:
production: &production
cleanup_magic_links:
command: "MagicLink.cleanup"
schedule: every 4 hours
Full pattern (command vs class, Solid Queue maintenance entries, schedule syntax): [[rails-jobs]] Pattern 4.
9. Puma Configuration for Production
Workers per CPU, single thread per worker (optimal for SQLite's low I/O wait), copy-on-write warmup, and GC deferred to between requests:
# config/puma.rb
port ENV.fetch("PORT", 3000)
plugin :tmp_restart
unless ENV["SOLID_QUEUE_IN_PUMA"] == "false"
plugin :solid_queue
end
if !Rails.env.local?
# 1 worker per CPU, 1 thread per worker (optimal for SQLite)
workers Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count })
threads 1, 1
# Optimize for copy-on-write
before_fork do
Process.warmup
end
# Defer major GC until after request handling
before_worker_boot do
GC.config(rgengc_allow_full_mark: false)
end
out_of_band do
GC.start if GC.latest_gc_info(:need_major_by)
end
end
Quick Reference
New Project Checklist
- Add Solid Stack gems to Gemfile (with
cooldown: 4on the source line) - Create
config/queue.yml,config/cache.yml,config/cable.yml - Configure multiple databases in
config/database.yml - Add
plugin :solid_queuetoconfig/puma.rb - Create
config/deploy.ymlfor Kamal - Create production Dockerfile with jemalloc and bootsnap
- Set up
config/recurring.ymlfor scheduled tasks - Configure
bin/docker-entrypointfor auto-migrations
Environment Variables
# Required secrets (in .kamal/secrets)
SECRET_KEY_BASE=...
RAILS_MASTER_KEY=...
# Database (for MySQL)
MYSQL_HOST=127.0.0.1
MYSQL_PASSWORD=...
# Concurrency tuning
WEB_CONCURRENCY=4 # Puma workers
JOB_CONCURRENCY=4 # Solid Queue processes
SOLID_QUEUE_IN_PUMA=true # Run jobs in web process
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.