← Back to Blog

How To: Ruby on Rails, git worktrees and Claude Code

February 15, 2026 Ruby on Rails git worktrees Claude Code AI

Introduction

If there is one thing which makes coding with AI go from being a helper to being an entire development process, it’s the use of independent workable copies of your code. And what I mean by that is: being able to work on multiple features or ideas at once, independently, without breaking anything — because your environments are so isolated that it’s a breeze to use AI for your agentic development process.

This is a How To guide where I show you how I use git worktrees and isolated Ruby on Rails environments to enable independent coding using Claude Code.

The required tooling

Before we start, let me explain what tools are required to set this up:

I assume you’re familiar with Rails development and how to install Claude Code. The new piece here is git worktrees. Think of them like this: instead of cloning the repo twice, you create a second working directory that points at the same repository. Both directories see the same branches, commits and history — but each one has its own checked-out branch and its own working files. That’s the foundation for everything below.

The changes

The first change is your mindset. Don’t think of this as simply “feature branches” — think of each worktree as an independent developer working on your project. You’re the orchestrator.

The most basic way of using worktrees is git worktree add ../my-feature feature-branch. But your codebase isn’t ready for that kind of independence yet. Each worktree needs its own database, its own port, and its own Redis namespace. Let’s fix that.

a) ENV-driven database.yml

Strip out all hardcoded database names and make everything environment-driven:

default: &default
  adapter: postgresql
  encoding: unicode
  database: 
  username: 
  password: 
  host: 
  port: 
  pool: 5

development:
  <<: *default

test:
  <<: *default

Now each worktree can point at its own database just by setting DATABASE_NAME.

b) Shared Postgres & Redis

All worktrees share the same Postgres and Redis instances — they just use different database names and Redis DB numbers. If you’re running them locally, you’re already set. If you prefer Docker, here’s a simple docker-compose.yml:

services:
  db:
    image: postgres:17-alpine
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "${DOCKER_POSTGRES_PORT:-5431}:5432"

  redis:
    image: redis:7-alpine
    command: redis-server
    ports:
      - "${DOCKER_REDIS_PORT:-6380}:6379"

volumes:
  db_data:

The non-default ports (5431/6380) avoid collisions with any locally installed Postgres or Redis. Adjust to match your setup — the important thing is that every worktree talks to the same services but uses a different database name and Redis DB number.

c) Auto-derive environment per worktree

This is the core trick. A bin/worktree-env script reads the directory name and derives all the environment variables from it. Let’s say your app is called myapp:

#!/usr/bin/env bash
WORKTREE_NAME=$(basename "$PWD")
WORKTREE_SUFFIX=$(echo "$WORKTREE_NAME" | sed 's/^myapp-*//' | sed 's/-/_/g')

if [ -z "$WORKTREE_SUFFIX" ] || [ "$WORKTREE_SUFFIX" = "myapp" ]; then
  # Main worktree
  DATABASE_NAME="${DATABASE_NAME:-myapp_development}"
  PORT="${PORT:-3000}"
  REDIS_DB="${REDIS_DB:-1}"
else
  # Feature worktree — derive everything from the directory name
  DATABASE_NAME="${DATABASE_NAME:-myapp_${WORKTREE_SUFFIX}_development}"
  PORT=$(( 3001 + $(echo "$WORKTREE_SUFFIX" | cksum | cut -d' ' -f1) % 99 ))
  REDIS_DB=$(( $(echo "$WORKTREE_SUFFIX" | cksum | cut -d' ' -f1) % 15 + 1 ))
fi

echo "export DATABASE_NAME='$DATABASE_NAME'"
echo "export PORT='$PORT'"
echo "export REDIS_URL='redis://localhost:${REDIS_PORT:-6379}/$REDIS_DB'"

Directory myapp-feature-auth → database myapp_feature_auth_development, port 3042 (or whatever the hash lands on), Redis DB 7. Fully deterministic, no manual config.

d) Procfile.dev

The Procfile just references ${PORT} so each worktree starts on its own port:

web: bin/rails server -p ${PORT:-3000}
css: bin/rails dartsass:watch
sidekiq: bundle exec sidekiq -C config/sidekiq.yml

The flow state

Once you understand the concept, it’s time to automate. I added three scripts to bin/ that handle the full lifecycle:

bin/wt-new — create a new worktree

bin/wt-new feature-auth              # branch off current branch
bin/wt-new feature-auth develop      # branch off develop

What it does:

You end up with a ready-to-run Rails environment in seconds.

bin/wt-list — see what’s running

══════════════════════════════════════════════════════════════════
                          Git Worktrees
══════════════════════════════════════════════════════════════════

  ● myapp                          :3000  develop
    myapp-feat-new-folder-design   :3002  feat/new-folder-design

  ● = Running

Shows every worktree, its port, its branch, and whether it’s currently running.

bin/wt-rm - clean up when you’re done

Now here’s where it gets fun. Open a separate terminal in each worktree directory and run claude in each one. Each Claude Code session sees only that worktree’s files and branch. You can have one session implementing a feature, another fixing a bug, and a third refactoring tests - all at the same time, all completely isolated. You review and merge when each one is done.

That’s the flow state: you stop being the person who writes every line and start being the person who directs the work and reviews the output.

Conclusion

And that’s it. That’s the magic. It’s amazing what you can achieve with this setup. You are much faster and can work on multiple features at once. Or review code while a big feature is churning away in another worktree. It’s proper development orchestration.

Just make sure to have proper safeguards in place to not produce code which doesn’t do what you want it to do. But I will talk about that in another article in the future.

Cheers, Michael