For AI Agents
RailsFast is built to be driven by AI agents end to end, not as a marketing line, but as a set of testable guarantees. An agent has taken a fresh clone to a branded, deployed production app with the human only touching secrets. This page is the contract that makes that repeatable.
Why it works so well: convention over configuration is the best agent
interface ever shipped. Every Rails app looks the same, so your agent has
effectively already seen your codebase. No build step, no node_modules
maze, boring omakase technology: fewer places for an agent to get lost.
Point your agent at these
- Docs, LLM-shaped: railsfast.com/docs/llms.txt: the entire documentation as one plaintext file.
- In-repo context: every clone ships
AGENTS.md/CLAUDE.mdplus.cursor/rules/describing the stack and quality bar. Claude Code additionally auto-discovers the bundledrailsfastskill (.claude/skills/railsfast/): the workflow verbs and the gotchas that aren't guessable from the file tree. - MCP: the project serves its own state over the Model Context Protocol. One line in any MCP-capable host:
{ "mcpServers": { "railsfast": { "command": "bin/railsfast", "args": ["mcp"] } } }
Tools: doctor (production readiness), status (template sync state), project_info (app identity + docs pointers). Read-only, stdlib-only, zero config.
The guarantees
- Non-interactive everything:
bin/railsfast init --yes --name "My App"takes a fresh clone to a configured, committed, database-ready project with zero prompts. The name is kept exactly as typed. - One deterministic gate:
bin/ciruns RuboCop, the translations health check (i18n-tasks health), dependency audits, Brakeman, the full test suite, system tests, and seed verification. It keeps going past failures and reports everything at the end: one command, one exit code, no judgment calls. - Machine-readable state:
.railsfast/config.jsonrecords what the project is and which template commit it's based on;bin/railsfast statusand the MCPstatustool read it back. - Self-diagnosing setup:
bin/railsfast doctor(or--json) lists everything still unconfigured (placeholder credentials per service, deploy config, backups, branding), and every finding carries its own fix. Exit0means production-ready; branch on it. - Deploys are reproducible:
bin/kamal deploybuilds from git HEAD via a clean clone. If it's not committed, it doesn't ship, so an agent can reason about exactly what's live. - Verifiable health: after a deploy,
https://yourdomain.com/healthcheck(allgood) is the agent's post-deploy assertion.
Day zero: from one sentence to a live site
The entire quickstart is agent-executable. The human's job is one
sentence ("I want to build MyProject on myproject.com") plus answering a
few questions and creating exactly two API tokens. Everything else, the agent
does: it even installs its own tools (brew install hcloud) if they're missing.
What the agent should ask before touching anything
An agent driving the quickstart should qualify the run first (one batch of questions, not twenty interruptions), and always recommend a good default:
- Name + domain: if not already given in the starting sentence. Deploying to a subdomain of a domain the human already has on Cloudflare is the fastest path; a fresh domain is a human purchase (agents can't buy domains).
- New server, or reuse one? New costs real money: quote the actual price (
hcloud server-type describe ...) and get an explicit yes beforehcloud server create. Reuse means: which IP, and skip server creation. - Which location? Physically close to the app's users. Check live stock first: cheap models come and go per location (
hcloud server-type list). - SSH key: generate fresh, or reuse? Recommend a fresh per-project
ed25519key (ssh-keygen -t ed25519 -f ~/.ssh/myproject_hetzner -N ""): revocable independently, no passphrase prompts to block automation. Reusing an existing key is fine if the human prefers it. - Remote builder? On Apple Silicon the answer is almost always yes (why): that's a second, small server (same cloud-config), so include it in the spend quote.
- Where does the private GitHub repo live?
gh repo createunder which account/org.
The two human steps
Both are "create an API token while logged in", genuinely human (password + 2FA), one minute each:
- Hetzner: Console → your project → Security → API tokens → Generate (Read & Write).
- Cloudflare: profile → API tokens → Create Token → "Edit zone DNS" template → scope it to the one zone, and add Zone → Zone Settings → Read (plus Edit only if the agent may need to set the SSL mode).
Where a step truly needs a human, the agent should say exactly what to click and what scopes to grant (like above), and nothing more.
The secret handoff pattern
Tokens should never be pasted into a chat: transcripts get logged, summarized, and sometimes shared. The pattern that keeps secrets out of band: the human copies the token to the clipboard, then runs:
mkdir -p ~/.config/railsfast && pbpaste > ~/.config/railsfast/hcloud_token && chmod 600 ~/.config/railsfast/hcloud_token
(macOS pbpaste; Linux: xclip -o or wl-paste.) Same for
cloudflare_token. The agent then reads it inline where needed
(HCLOUD_TOKEN="$(cat ~/.config/railsfast/hcloud_token)" hcloud ...) and never
echoes it. The token exists in exactly two places: the provider dashboard and a
chmod 600 file.
The provisioning sequence
The quickstart, as an agent runs it (every step links to its agent-lane block there):
clone template → gh repo create + push → bin/railsfast init --yes --name "My Project"
→ ssh-keygen → hcloud server create (cloud-config, deploy + builder) → firewall
→ Cloudflare A record + SSL-mode check → poll /etc/railsfast-setup.version readiness
→ fill deploy.yml (IPs, host, key) → commit → kamal accessory boot all
→ kamal registry setup → kamal deploy → assert /healthcheck
Server initialization (~5 min) overlaps with DNS and deploy.yml work, so
wall-clock from sentence to live site is dominated by the first Docker build.
Day one: configuring every service
After the site is live, bin/railsfast doctor --json is the agent's
worklist: every remaining finding, machine-readable, each carrying its own
fix. The configuration page carries a
terminal/agent lane for each service. The orchestration layer:
Ask first, in one batch (like day zero, again with recommended defaults):
which AWS profile and SES region (aws sts get-caller-identity tells you
what's already authenticated); the mailbox addresses; which services to skip
for now (Stripe needs KYC on fresh accounts, a fine skip for a first pass);
and the one extra token (Cloudflare account-scoped, R2 Edit + Turnstile
Edit, distinct from day zero's zone-scoped DNS token).
Then verify everything empirically: configured ≠working. The agent's post-configuration assertions, all validated lanes:
- Email: identity status via
aws sesv2 get-email-identity, then a real message from production (the smoke test); the human confirms SPF/DKIM/DMARC allPASSin the received headers. - Storage: an Active Storage round trip from production
(
upload → exist? → deleteviabin/kamal app exec --reuse 'bin/rails runner ...'). - Turnstile: the widget script and sitekey render on
/users/sign_up. - Backups: trigger one manually and list the bucket (verify-your-backups lane).
- The gate:
bin/railsfast doctorexits clean apart from deliberate skips, and/healthcheckstays green after the redeploy.
Day two: something to sell
Credentials make payments possible. An actual sellable product needs three more things, in order:
- Plans in code: define them in
config/initializers/pricing_plans.rb(pricing, limits, features, marketing bullets): pure Ruby, fully agent-editable. - Products and Prices in Stripe: one Product per plan plus its recurring
Price(s), fully agent-executable under the
permission list (sandbox first with the
CLI, live with the org key; commands in that guide). Wire the returned
price_…IDs intoconfig/railsfast/railsfast.ymlunderstripe.plans, and switch each plan from the hardcodedpriceto thestripe_priceline the initializer already stubs out. - The human gates: finishing Stripe account activation (identity
verification: only a human can attest to that), and the branding pass
(logo, OG image, favicon:
doctor's "optional" list).
A good agent flow treats this as its own conversation: propose plan names, prices and limits from what it knows about the product, get the human's yes, then create everything and wire it up.
Day three: operations on (mostly) autopilot
Once the app is live, configured, and selling, the operational loop is:
allgood (/healthcheck, the sensor) → UptimeRobot (the trigger: it
has an API, so monitor creation is agent work) → Telegram (the channel:
the template's admin notifications, one credential block) → you or your
agent (the responder). Wire those once and incidents find you; nothing
polls a dashboard.
The standing cadence an agent can run on your behalf:
- Weekly fleet sweep:
bin/railsfast doctor --json+ a/healthcheckprobe + a skim ofbin/kamal logsacross every app; report anything actionable, touch nothing without a human yes. - Restore drill, once per app (and after any backup-pipeline change): the runbook against a scratch database. A backup that's never been restored is hope, not disaster recovery.
- Template updates: check
bin/railsfast statusfor drift; when a release ships, run the updating protocol on a branch and hand the human a green-CI PR. - Server maintenance: security patches auto-apply, but kernel updates
need a reboot. When
sshgreets you with*** System restart required ***, schedule one (containers come back on their own). Re-run the verify script quarterly. - Know the escape hatch before you need it:
bin/kamal rollbackexists and Kamal retains the last releases for exactly this. Practice it once.
What stays human on day three: acknowledging a real incident, anything account-destructive, and spend changes. Everything else above is safe, reversible agent work.
The complete handoff manifest
Everything a human hands over, in total, for a fully hands-off clone-to-configured-production run, each one a "create while logged in" step delivered via the secret handoff pattern above:
| # | Handoff | Where | Scope |
|---|---|---|---|
| 1 | Hetzner API token | Console → project → Security → API tokens | Read & Write |
| 2 | Cloudflare zone token | profile → API tokens | Zone: DNS Edit, Zone Read, Zone Settings Read (+ Edit only if the SSL mode may need changing) |
| 3 | Cloudflare account token | profile → API tokens | Account: Workers R2 Storage Edit + Turnstile Edit |
| 4 | AWS CLI auth | aws configure (usually already done) |
a profile able to create IAM users/policies; the agent then mints per-project scoped users itself |
| 5 | GitHub CLI auth | gh auth login (usually already done) |
repo scope |
| 6 | Stripe login | stripe login (browser, once per account) |
yields all dev credentials |
| 7 | Stripe restricted key | Dashboard → API keys → restricted key | the exact permission list: the only per-project production key a human ever mints |
That's the whole surface. Items 4–5 are usually already true on a dev machine; on a fresh project under an existing setup, the marginal human cost is three tokens, one login, and one restricted key: a few minutes, total. Deliberately not on the list: anything account-destructive (deleting repos, closing accounts). Keep those human.
The loop your agent should run
small change → bin/ci → commit → bin/kamal deploy → check /healthcheck
That's the whole workflow. The develop guide has the human version of the same story.
Gate the deploy in the same chain
bin/ci stops a deploy only when its exit status stops the command that deploys. Run the two in one chain:
bin/ci && bin/kamal deploy
In a script, put bin/ci || exit 1 on the line before bin/kamal deploy. Never run bin/ci in a subshell or a pipe whose exit status the chain drops. For example, bin/ci | tail -20 && bin/kamal deploy deploys a red suite, because a pipe returns the status of its last command, tail. An app on the fleet once deployed a red suite this way.
With ECR as your container registry, also log in first. The ECR login lasts 12 hours, so a deploy after a pause fails with 403 Forbidden or "unexpected status from HEAD request", although your credentials are fine:
bin/ci && bin/kamal registry login && bin/kamal deploy
With the default local registry, leave out bin/kamal registry login: Kamal refuses it there.
Console steps in these docs (Hetzner, Cloudflare, AWS) increasingly carry CLI equivalents in collapsible blocks: those are the agent lane. Where a step truly needs a human (buying a domain, an OTP), the docs say so explicitly.
Several agents at once: one worktree each
Several agents can build one app at the same time, each on its own branch in its own git worktree. An invoicing app on the fleet was built this way, and four rules came out of that build:
-
One worktree per branch, next to the main checkout, with the main checkout's directory name and a suffix:
git worktree add ../myapp-wp3 -b wp3-imports main -
Copy the key files into each new worktree before you run anything in it. They are gitignored, so a new worktree does not have them. Without
config/master.key,bin/setupstops, and Kamal's pre-connect check stops a deploy. Withoutconfig/db.key(the production database password),bin/setupwrites a new random one, and a deploy from that worktree then sends the wrong password.cp config/master.key config/db.key ../myapp-wp3/config/ -
Each worktree uses its own databases. For a worktree whose directory is the main checkout's directory name, a hyphen and a suffix,
config/database.ymladds_<suffix>to the development and test database names:myapp-wp3usesmyapp_wp3_developmentandmyapp_wp3_test. One agent's migrations and test runs then never touch another agent's data. The main checkout, CI and production keep the plain names. Runbin/rails db:prepareonce in a new worktree to create its databases.An app created before this rule does not get it from a template update, because
config/database.ymlis merge-guarded. Copy the ERB block abovedevelopment:from the template's file (git show railsfast/main:config/database.yml), and add<%= worktree_suffix %>after your app's name in the four development and test database names. -
Check the guarded files after each merge into
main. The merge guards also run when a worktree's branch merges into your app'smain, and they can drop the branch's change to a guarded file without a word. Run the check in Guards also run on your own merges after every merge. Each agent says in its report when its branch changes a guarded file.