FROM coder-devbox-base-config:latest AS base-default USER root RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends libpq-dev libffi-dev zlib1g-dev libedit-dev libyaml-dev redis-server libicu-dev postgresql-client psmisc USER coder # RUN echo "[ Installing nvm ]" \ # curl -o- "https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh" | bash \ # echo "[ Installing Node.js $NODE_VERSION ]" \ # nvm install "$NODE_VERSION" \ # # Write alias file directly so persisted home volumes respect it on restart # mkdir -p "$NVM_DIR/alias" \ # echo "$NODE_VERSION" > "$NVM_DIR/alias/default" \ # nvm use "$NODE_VERSION" \ # echo "[ Node active: $(node --version) ]" RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv && \ cd ~/.rbenv && src/configure && make -C src && \ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build && \ export PATH="$HOME/.rbenv/bin:$PATH" && \ eval "$(rbenv init -)" && \ rbenv install -s 3.2.2 && \ rbenv global 3.2.2 && \ gem install bundler ruby-lsp --no-document USER root # rbenv's ruby lives entirely under /home/coder/.rbenv, exposed on PATH only # via ~/.bashrc — invisible to Kilo's own agent process, which is spawned # non-interactively and never sources .bashrc (needed for the ruby-lsp # built-in LSP, which auto-installs rubocop via `gem` if `ruby`/`gem` are # found on PATH). Copy the installed version out to image-owned territory and # add it to PATH via ENV, so it works regardless of the home volume's state. # bundler/ruby-lsp were installed BEFORE this copy deliberately: gem installs # done AFTER copying (via the copied `gem` binary) still land back under # /home/coder — Ruby's own RbConfig bakes in the interpreter's original # build-time prefix, which `cp -a` doesn't rewrite, so gem defaults to that # original path regardless of where the files physically now live. Confirmed # live: `gem install ruby-lsp` run here (after the copy) installed to # /home/coder/.rbenv/... same as before, not /usr/local/rbenv-ruby, leaving # `ruby-lsp` off PATH entirely. # # That same RbConfig-prefix behavior means EVERY workspace's own `bundle # install` (the normal, universal first step for any real Ruby project — # not something this template automates, see devenv/main.tf) writes its # gems' executables back under /home/coder/.rbenv/versions//bin, never # to /usr/local/rbenv-ruby/bin — confirmed live (real user report): `bin/dev`'s # `exec foreman start -f Procfile.dev` failed with "foreman: not found" even # after a clean `bundle install` showed `Bundle complete!` and `bundle # list`/`gem list` both showed `foreman (0.89.1)` installed; the real # executable was sitting under /home/coder/.rbenv/versions//bin, # invisible to $PATH (and thus to `bundle exec foreman` too — Bundler's own # exec lookup is $PATH-based). Same root cause as bare `rails` not being # found (also documented in devenv/README.md's Ruby section) — anything a # project's own Gemfile pulls in beyond the two gems baked into this image # was always going to hit this. # # Fixed at the root instead of papering over it with a second PATH entry: # once the real files are copied out, the *original* versioned rbenv # directory is deleted and replaced with a symlink back to the copy. Ruby's # baked-in RbConfig prefix still resolves to the same absolute path either # way, so every future `bundle install`/`gem install` (which only ever # knows about that original path, never /usr/local/rbenv-ruby directly) # transparently lands in the one real, already-on-PATH location instead — # no second copy, no drift, and no new place a future ruby-version bump has # to be kept in sync (the version string is read from rbenv's own # `~/.rbenv/version` file, set by `rbenv global` above, not re-hardcoded # here). RUN RBENV_RUBY_VERSION="$(cat /home/coder/.rbenv/version)" \ && mkdir -p /usr/local/rbenv-ruby \ && cp -a "/home/coder/.rbenv/versions/$RBENV_RUBY_VERSION/." /usr/local/rbenv-ruby/ \ && chown -R coder:coder /usr/local/rbenv-ruby \ && rm -rf "/home/coder/.rbenv/versions/$RBENV_RUBY_VERSION" \ && ln -s /usr/local/rbenv-ruby "/home/coder/.rbenv/versions/$RBENV_RUBY_VERSION" ENV PATH="/usr/local/rbenv-ruby/bin:${PATH}" USER coder RUN ruby --version && gem --version && ruby-lsp --version # Claude Code's plugin wrapper around ruby-lsp (installed above, before the # copy-out) — same pattern as php-lsp/intelephense. Codex gets the same # plugin from the same marketplace — see base-config's Dockerfile for why # this works. RUN "$HOME/.local/bin/claude" plugin install ruby-lsp@claude-plugins-official \ && codex plugin add ruby-lsp@claude-plugins-official RUN curl https://cli-assets.heroku.com/install.sh | sh