DOCS LLMs

Updating RailsFast

Updating any template is always going to be a bit difficult, because as you start building your app on top of it many files will diverge from the original template, and conflicts will naturally arise. The worst-case scenario is you need to go file-by-file reviewing the template updates and deciding what to incorporate into your app.

HOWEVER, RailsFast is designed so the amount of effort you need to do is minimized, and ideally you can keep pulling RailsFast improvements into your app without much work.

NOTE

This guide was battle-tested by upgrading a whole fleet of real production apps — including apps forked back on v0.x — with AI agents driving every merge, each one logging every snag. Everything below reflects what those upgrades actually hit.

Make your life easier for updating

As we covered in the Customizing Components section, the best practice if you plan to customize RailsFast components, is that you don't edit the RailsFast components in-place, instead, copy the component you want to change into your own folder and edit your copy.

Same idea for gems: the Gemfile ends with a "GEMS SPECIFIC TO THIS PROJECT" section — put your app's own gems there, not inside the template-owned regions, and your Gemfile stops conflicting on every future update.

Protect your customizations (merge guards)

At some point, you will inevitably edit RailsFast core files. If you did edit files that ship with RailsFast and that you can't copy easily into your own folder (for example, layouts or auth views), you can tell Git to always keep your version on merges by adding merge guards in .gitattributes.

Example:

# Keep my customized files when merging template updates
app/views/devise/** merge=ours

This way, if for example you update all your authentication screens to match your branding and design, when updating RailsFast your files will get priority, overriding the update, and you won't lose your work (you won't get updates on those files, though)

CAUTION

The .gitattributes line alone does nothing. ours is not a built-in Git merge driver — the guard only works when the driver is configured:

git config merge.ours.driver true

bin/setup configures this for you, but a fresh clone on a new machine (or a CI checkout) doesn't have it — and then Git silently falls back to a normal merge on your "protected" files, in the direction of losing your customizations. Verify before any merge:

git config --get merge.ours.driver   # must print: true

Three more truths about guards, each learned on a real upgrade:

  1. Guards are silent. A guarded file that kept your version prints an ordinary Auto-merging line — nothing tells you an upstream improvement was just discarded. That's why diffing guarded files is a standing step in After updating, not a v0.x special.
  2. Guards don't cover deletions. Merge drivers run only for content conflicts. If upstream deletes a file you've modified, you get a raw CONFLICT (modify/delete) that no guard resolves. Hard rule for that moment: on any conflict touching credentials.yml.enc, master.key, or any .key/encrypted file — keep YOUR version, always. The template cleaning up its own sample file is never a reason to delete your live credentials.
  3. Guard only what you must. If you guard huge paths like app/views/**, you will also silently skip upstream improvements (including fixes) for those files.

Before you update: figure out where you are

Three checks, one minute, and they prevent the worst classes of mistake:

# 1. Is this repo actually a fork of the template? (directory names lie)
git remote -v                          # is the railsfast remote there?
git fetch railsfast --prune --tags     # fetch FIRST — and bring the release tags
git merge-base HEAD railsfast/main     # a SHA = real shared ancestry; an error = STOP

# 2. Where did you *really* fork? (version constants on old apps can lie)
git tag --contains $(git merge-base HEAD railsfast/main) | head -3

# 3. Establish your baseline — run the gate BEFORE merging:
bin/ci

Fetch before you size up the job: measuring against a stale railsfast/main ref can overestimate the work several-fold. And the pre-merge bin/ci run is what lets you say "the merge broke this" versus "this was already broken" — if main is already red, fix that first, or you'll debug two problems tangled together.

How to update RailsFast

Work on a branch — never merge the template directly into main on a production app:

git switch -c chore/template-update

Then merge. Pinning a release is the reliable way — merging railsfast/main gets you whatever has landed since the last release, which can be more than you bargained for:

git tag --list 'v*' --sort=-v:refname | head -5   # what releases exist?
git merge v1.0.0-beta.4                            # pin a release (recommended)
# or: git merge railsfast/main                     # bleeding edge, eyes open

If you get conflicts, resolve them (Git will respect your merge guards — assuming the driver check above passed), and commit the merge. bin/railsfast update prints this whole flow tailored to your repo.

Generated files: regenerate, don't hand-merge

db/schema.rb and Gemfile.lock conflict on almost every non-trivial update, and the correct resolution is never a careful hand-merge:

  • Gemfile.lock: resolve the Gemfile conflict first, then bundle install — the lockfile regenerates itself.
  • db/schema.rb: take your side, then bin/rails db:migrate applies the template's new migrations and re-dumps the schema authoritatively. Free correctness check: if you did hand-union it first, a zero diff after db:migrate proves your whole resolution was right.

Port what the guards kept out

Guarded files keep your copy wholesale — which also means new template keys and improvements never arrive in them on their own. After merging, diff every guarded path against the release you merged, and port what's missing:

# the guard list lives in .gitattributes; for each merge=ours path:
diff <(git show v1.0.0-beta.4:config/railsfast/railsfast.yml) config/railsfast/railsfast.yml
diff <(git show v1.0.0-beta.4:config/application.rb) config/application.rb
diff <(git show v1.0.0-beta.4:.kamal/secrets) .kamal/secrets
# ...and so on down the .gitattributes list

This is mechanical, and it's the step that catches a new config key or an improved secrets expression that the guard silently discarded. Watch for nested keys — check paths, not just top-level names.

Using AI agents to resolve conflicts

Merge conflicts are inevitable when you've customized template files and the upstream template has improvements to those same files. The good news: AI coding agents like Claude Code excel at resolving these conflicts intelligently — the fleet of real-app upgrades that road-tested this guide was agent-driven end to end.

Give the agent the whole protocol, not just the merge:

Update this app to RailsFast <release tag>, on a branch, following
https://railsfast.com/docs/guides-updating/ exactly: verify
merge.ours.driver is configured, run a baseline bin/ci first, merge the
release tag, resolve conflicts (keep my customizations in app-owned
files; regenerate schema.rb and Gemfile.lock instead of hand-merging;
on anything touching credentials or .key files keep mine), diff every
merge-guarded file against the release and port missing keys, get
bin/ci green, run bin/railsfast doctor, then open a PR.

An agent given that prompt does the right thing. An agent told only to "merge and resolve conflicts, keep my styling" will skip exactly the steps this page says the merge cannot do by itself.

TIP

Enable Git's rerere ("reuse recorded resolution") before merging — it records how you resolve each conflict and replays it if you ever redo the merge: git config rerere.enabled true

After updating

In order — every step is here because skipping it bit a real upgrade:

bundle install                 # the update may have changed the Gemfile
bin/rails db:migrate           # new template migrations
bin/ci                         # THE gate: style, audits, Brakeman, all tests, seeds
bin/railsfast doctor           # production readiness — every finding carries its fix
bin/railsfast mark-updated     # record the update so `status` tracks drift correctly

Also diff config/bundler-audit.yml against the release: if the template ships an advisory ignore, check whether a patched gem has shipped since — inheriting a suppression when the patch exists means you're suppressing, not patched.

Then restart your dev server (bin/dev), click around, and land your update branch via PR like any other change.

What each release needs from you

  • v1.0.0-beta.4: nothing manual — stripe.context (Stripe Organizations support) is opt-in, the backup s3_endpoint.key is optional, no migrations. If your config/bundler-audit.yml carries a pay advisory ignore, bump pay to ≥ 11.6.2 and drop it.
  • v1.0.0-beta.3: the agent surface arrives (bin/railsfast doctor, the MCP server, the bundled Claude skill) — no manual steps.
  • From v0.x: the big jump — next section.

Upgrading to v1.0.0-beta (from v0.x)

The v1.0.0 betas are a big jump (Ruby 4, sessions & device security, the native server half, dark mode, organizations 0.5). A fleet of real apps has crossed it — the demo, railsfast.com itself, and several production apps.

Run bin/railsfast doctor first, not last. Old apps sometimes moved ahead independently (already on Ruby 4, already generated encryption keys) — doctor turns this checklist from "do all of this" into "verify, then act on what's actually missing":

  1. Active Record encryption keys are required in production. config/application.rb fails closed on boot without them (fresh apps get them from bin/setup; upgraded apps don't — and doctor reports it as ar-encryption-keys). Generate and add them BEFORE deploying, or your first deploy dies at the health check:
    bin/rails db:encryption:init
    # copy the printed block into: EDITOR="cursor --wait" bin/rails credentials:edit
  2. Diff every merge-guarded file against the release and port what's missing — the mechanical step above, which stays accurate as releases move (a hardcoded key list here would rot). One v0.x-specific find the diff will surface: delete the stale template_version: key from config/railsfast/railsfast.yml — the version's single source of truth moved to lib/railsfast/version.rb, and the guarded yml would keep the dead key forever.
  3. Ruby 4.0.5: rbenv install 4.0.5 / mise install, then bundle install. If you use a remote builder, update RUBY_VERSION in config/deploy.yml's builder args — and check your Dockerfile's ARG RUBY_VERSION if you've customized it.
  4. bin/rails db:migrate (sessions tables + organizations 0.5).

If you customized Stimulus-driven markup: the template reorganized all controllers under railsfast/ and app/ ownership folders, so old root-level identifiers (navbar, dropdown, slideover, …) are now railsfast--layout--navbar, railsfast--ui--dropdown, and so on — update any data-controller attributes in views you own.