Using Conductor.build with Ruby on Rails

February 03, 2026

Hello!

When I first saw Conductor.build almost five months ago, it was early, there were significant issues for using it with a Ruby on Rails app, but with time and how AI tooling is moving quickly, things seem to be much more stable these days.

I picked it up again a few weeks ago and have been making it an active part of my AI workflow and wanted to share some helpful scripts / setup to maybe help you along the way. In one project I added my scripts to, it was ~100 lines of changes with most being bin scripts being added for Conductor.

What the heck does Conductor.build even do?

In a fairly common type of workflow, developers tend to use branches to checkout semi-isolated sections of the code bases, make their changes and then create pull requests to get them into the main branch. This is great for many situations, but has some fun edge cases with things like database migrations and so forth. Working on DB changes in one branch, but need to make changes in a different branch? Congratulations, now your schema shows the columns from the other branches.

There's another method I hadn't heard about until ~8 months ago, git worktrees, but to be transparent, I've found them a royal pain to use manually. So when Conductor popped on the radar for me, I realized it basically automated managing Worktrees for me.

What wasn't clear was that it was essentially checking out the source code to its own folder, and not bringing anything that lived in my .gitignore along with it (aka the environment variables, encrypted credentials keys and so forth), so some things weren't obvious until I ran into those. Then I'd have to find my files I needed and copy them into the conductor worktree. Royal pain. Anyway, Conductor realized this and added supports for scripts!

Conductor provides scripts + some Environment Variables

Conductor provides a few helpful variables that can be used to make things isolated:

  • CONDUCTOR_ROOT_PATH -> the path of the main checkout (aka your main branch, what everything spins up off of)

  • CONDUCTOR_PORT -> port assigned to your app's workspace, IE (55050, 55060...) This is actually the first of 10 ports assigned to the workspace.

  • CONDUCTOR_WORKSPACE_NAME -> Workspace name, likely a city name from Conductor's process to spin up trees.

  • CONDUCTOR_WORKSPACE_PATH -> the path of the workspace directory.

There are three scripts Conductor supports at the moment:

  • Setup

  • Run

  • Archive

Betcha can't name what those scripts do from just the name. I'm mostly joking of course, they do exactly what they are named for. 10/10 naming job.

Setup

For most Ruby on Rails apps the basic setup script is going to look really similar across the board.

  • Symlink the .bundle for private gem server credentials

  • Run dependency installs (bundle install, yarn install, etc)

  • Copy the files you need that .gitignore ignored. Things like a .env file, config/master.key, config/credentials/development.key or whatever else you need.

    • If you use Disk ActiveStorage locally, copy those files over too so there's no broken images/files.

  • Copy the DB over for an isolated DB copy (if you want!)

  • Run any migrations or whatever else you want to do here.

Run

For the Run script, you really just want to ensure your app knows the PORT conductor gives each Workspace and ensure your app code responds accordingly. Then I've been executing my bin/dev script after passing along the PORT var from conductor.

Also ensure your things like your mailers that send out use the correct port / domain for each workspace.

Archive

Teardown the database, any local ActiveStorage files you might have made on that workspace, and so forth. If you needed isolated API keys that you automatically spun up, you can archive them here if you needed a space to do so.

Gotchas?

I've spotted a few issues on my more advanced projects, that use custom routing for things like subdomains and so forth, but I think with some crafty-ness you could solve them (I mean just set Claude to the task after sharing the context about your application with 'em).

For example it's not uncommon for some apps to want;

  • admin.domain -> admin center

  • app.domain -> main app

  • domain -> marketing

I've been solving this myself by running Caddy outside of Conductor and having my scripts spin up a domain for them and add to the running Caddyfile.

Show me the code

conductor.json (committed at the root of your repo, so your teammates can use the same scripts)

{
  "scripts": {
    "setup": "bin/conductor-setup",
    "run": "bin/conductor-server",
    "archive": "bin/conductor-archive"
  }
}

bin/conductor-archive

#!/usr/bin/env zsh
set -eo pipefail

echo "==> Archiving workspace: ${CONDUCTOR_WORKSPACE_NAME}"

# Nothing to do — Conductor removes the worktree automatically. Do any extra teardown you need to do.
# For non-SQLite databases:
bin/rails db:drop

echo "==> Archive complete"

bin/conductor-server

#!/usr/bin/env zsh
set -eo pipefail

REPO_ROOT="${0:a:h:h}"

# Port priority: CONDUCTOR_PORT > PORT > 3000
export PORT="${CONDUCTOR_PORT:-${PORT:-3000}}"

cd "$REPO_ROOT"
exec bin/dev

and for the big one;

bin/conductor-setup

#!/usr/bin/env zsh
set -eo pipefail

echo "==> Conductor setup for workspace: ${CONDUCTOR_WORKSPACE_NAME}"

REPO_ROOT="${0:a:h:h}"

# Symlink .env files from root repo if they exist
if [[ -n "$CONDUCTOR_ROOT_PATH" ]]; then
  for env_file in ".env" ".env.development.local"; do
    src="$CONDUCTOR_ROOT_PATH/$env_file"
    dest="$REPO_ROOT/$env_file"
    if [[ -f "$src" && ! -e "$dest" ]]; then
      echo "==> Symlinking $env_file from root repo"
      ln -sf "$src" "$dest"
    fi
  done

  # Copy master.key if not present
  if [[ -f "$CONDUCTOR_ROOT_PATH/config/master.key" && ! -f "$REPO_ROOT/config/master.key" ]]; then
    echo "==> Copying master.key from root repo"
    cp "$CONDUCTOR_ROOT_PATH/config/master.key" "$REPO_ROOT/config/master.key"
  fi

  # Copy per-environment credential keys (config/credentials/*.key)
  if [[ -d "$CONDUCTOR_ROOT_PATH/config/credentials" ]]; then
    for key_file in "$CONDUCTOR_ROOT_PATH"/config/credentials/*.key(N); do
      dest="$REPO_ROOT/config/credentials/$(basename "$key_file")"
      if [[ ! -f "$dest" ]]; then
        mkdir -p "$REPO_ROOT/config/credentials"
        echo "==> Copying $(basename "$key_file") from root repo"
        cp "$key_file" "$dest"
      fi
    done
  fi

  # Copy SQLite databases and Active Storage files from root repo
  if [[ -d "$CONDUCTOR_ROOT_PATH/storage" ]]; then
    mkdir -p "$REPO_ROOT/storage"
    for db_file in "$CONDUCTOR_ROOT_PATH"/storage/*.sqlite3(N); do
      dest="$REPO_ROOT/storage/$(basename "$db_file")"
      if [[ ! -f "$dest" ]]; then
        echo "==> Copying $(basename "$db_file") from root repo (safe backup)"
        sqlite3 "$db_file" ".backup '$dest'" # NOTE: this app used SQLite. Customize to your database type (ie Postgres)
      fi
    done

    # Copy Active Storage blob files (if any exist)
    blobs_copied=false
    for blob_dir in "$CONDUCTOR_ROOT_PATH"/storage/??/??(N); do
      if [[ -d "$blob_dir" ]]; then
        rel_path="${blob_dir#$CONDUCTOR_ROOT_PATH/storage/}"
        mkdir -p "$REPO_ROOT/storage/$rel_path"
        if [[ -n "$(ls -A "$blob_dir" 2>/dev/null)" ]]; then
          cp -rn "$blob_dir"/* "$REPO_ROOT/storage/$rel_path/"
          blobs_copied=true
        fi
      fi
    done
    if $blobs_copied; then
      echo "==> Copied Active Storage blobs from root repo"
    fi
  fi
fi

# Install dependencies
echo "==> Installing Ruby dependencies"
cd "$REPO_ROOT"
bundle install

# Run migrations in case the copied DB is behind
echo "==> Running migrations"
bin/rails db:prepare

echo "==> Setup complete"

A few callouts:

  • This app used SQLite, but my Postgres app setup was me basically running rails db:prepare to run setup + seeds vs copying the SQLite DB manually.

  • I didn't need bundle credentials so I didn't copy them in on this app. John Nunemaker went into more detail on his blog + conductor's docs: https://docs.conductor.build/quickstart/rails

App wise I needed two changes for a fresh-ish Rails app.

config/environments/development.rb:

  config.action_mailer.default_url_options = { host: "localhost", port: ENV.fetch("PORT", 3000).to_i }

Just needed to add the ENV.fetch for the port.

For Postgres (and I suspect MySQL apps):

config/database.yml:

  database: gentle_brief_development<%= "_#{ENV['CONDUCTOR_WORKSPACE_NAME'].gsub(/[^a-zA-Z0-9_-]/, '_')}" if ENV['CONDUCTOR_WORKSPACE_NAME'] %>

# then for the test DB do the same thing
  database: gentle_brief_test<%= "_#{ENV['CONDUCTOR_WORKSPACE_NAME'].gsub(/[^a-zA-Z0-9_-]/, '_')}" if ENV['CONDUCTOR_WORKSPACE_NAME'] %>

That was like all I needed to do. You'll need to customize the setup script for your app and if you have the more complicated routing setup, but for fresh-rails apps this tends to work really freaking well.

Other sources for helpful info:

Happy coding!

  • Andrea

Enjoyed this post? Follow along on Bluesky or GitHub.