ruby/rubygems · error · ProductionError

can't be updated because file system is read-only

Error message

can't be updated because file system is read-only

What it means

While writing the resolved lockfile, Definition#write_lock opens the file inside SharedHelpers.filesystem_access; an Errno::EROFS from the operating system is normalized to ReadOnlyFileSystemError and re-raised as ProductionError with a lockfile-changes summary ending in file system is read-only (lib/bundler/definition.rb:447). It means resolution changed the lockfile but the storage cannot accept writes: a read-only container filesystem, an immutable production image, or (via the same filesystem_access wrapper) missing write permission on the directory. An up-to-date lockfile triggers no write, so this fires only when a change was actually needed.

Source

Thrown at lib/bundler/definition.rb:447

              "Your lockfile needs to be updated, but it can't be because frozen mode is set.\n\n" \
              "Run `bundle install` elsewhere and add the updated #{SharedHelpers.relative_lockfile_path} to version control."
        raise ProductionError, msg
      end

      # Convert to \r\n if the existing lock has them, i.e., Windows with
      # `git config core.autocrlf=true`. Detect from the bytes on disk because
      # reading in text mode strips carriage returns on Windows, which would
      # otherwise defeat this check and rewrite a `\r\n` lockfile with `\n`.
      if File.exist?(file) && SharedHelpers.filesystem_access(file, :read) {|p| File.binread(p).include?("\r\n") }
        contents.gsub!(/\n/, "\r\n")
      end

      begin
        SharedHelpers.filesystem_access(file) do |p|
          File.open(p, "wb") {|f| f.puts(contents) }
        end
      rescue ReadOnlyFileSystemError
        raise ProductionError, lockfile_changes_summary("file system is read-only")
      end
    end

    def locked_ruby_version
      return unless ruby_version
      if @unlocking_ruby || !@locked_ruby_version
        Bundler::RubyVersion.system
      else
        @locked_ruby_version
      end
    end

    def locked_ruby_version_object
      return unless @locked_ruby_version
      @locked_ruby_version_object ||= begin
        unless version = RubyVersion.from_string(@locked_ruby_version)
          raise LockfileError, "The Ruby version #{@locked_ruby_version} from " \
            "#{@lockfile} could not be parsed. " \

View on GitHub (pinned to 86cbb817a3)

Solutions

  1. Move locking to build time: run `bundle install` during the image build and ship the current Gemfile.lock so runtime needs no write
  2. If the runtime must lock, give it a writable layer: mount a writable volume on the app dir or drop --read-only
  3. Fix directory permissions when the filesystem is writable but the user lacks write access
  4. Check writability before bundler runs: `ruby -e "puts File.writable?(Dir.pwd)"`

Example fix

# before
$ docker run --read-only myapp bundle install
# => can't be updated because file system is read-only

# after (lock at build time)
# Dockerfile: COPY Gemfile Gemfile.lock ./  then  RUN bundle install
$ docker run --read-only myapp bundle exec puma
Defensive patterns

Strategy: validation

Validate before calling

lockfile = Bundler.default_lockfile
abort "directory not writable" unless File.writable?(File.dirname(lockfile))
abort "lockfile unwritable" if lockfile.exist? && !File.writable?(lockfile)

Try / catch

begin
  Bundler.definition.lock(Bundler.default_lockfile)
rescue Bundler::ProductionError => e
  warn e.message if e.message.include?("read-only")
  # ship a pre-generated lockfile instead of locking here
end

Prevention

When it happens

Trigger: `bundle install` in a container started with docker --read-only or a Kubernetes readOnlyRootFilesystem pod while Gemfile.lock is stale; runtime re-locking in distroless images; bundle commands on NFS or overlay mounts mounted read-only; an unwritable app directory even on a writable filesystem.

Common situations: Security-hardened deployments run app volumes read-only; an image build changed the Gemfile but shipped a stale lockfile so the entrypoint re-resolves at boot; sidecars running bundle at startup inside read-only containers.

Related errors


AI-assisted analysis of ruby/rubygems@86cbb817a3 (2026-08-23). Data as JSON: /api/errors/c78c9e4de4e29f4e. Report an issue: GitHub.