info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Ruby upgrade guidelines
We strive to run GitLab using the latest Ruby MRI releases to benefit from performance and
security updates and new Ruby APIs. When upgrading Ruby across GitLab, we should do
so in a way that:
- Is least disruptive to contributors.
- Optimizes for GitLab SaaS availability.
- Maintains Ruby version parity across all parts of GitLab.
Before making changes to Ruby versions, read through this document carefully and entirely to get a high-level
understanding of what changes may be necessary. It is likely that every Ruby upgrade is a little
different than the one before it, so assess the order and necessity of the documented
steps.
## Scope of a Ruby upgrade
The first thing to consider when upgrading Ruby is scope. In general, we consider
the following areas in which Ruby updates may have to occur:
- The main GitLab Rails repository.
- Any ancillary Ruby system repositories.
- Any third-party libraries used by systems in these repositories.
- Any GitLab libraries used by systems in these repositories.
We may not always have to touch all of these. For instance, a patch-level Ruby update is
unlikely to require updates in third-party gems.
### Patch, minor, and major upgrades
When assessing scope, the Ruby version level matters. For instance, it is harder and riskier
to upgrade GitLab from Ruby 2.x to 3.x than it is to upgrade from Ruby 2.7.2 to 2.7.4, as
patch releases are typically restricted to security or bug fixes.
Be aware of this when preparing an upgrade and plan accordingly.
To help you estimate the scope of future upgrades, see the efforts required for the following upgrades:
Before any upgrade, consider all audiences and targets, ordered by how immediately they are affected by Ruby upgrades:
1.**Developers.** We have many contributors to GitLab and related projects both inside and outside the company. Changing files such as `.ruby-version` affects everyone using tooling that interprets these files.
1.**GitLab CI/CD.** We heavily lean on CI/CD for code integration and testing. CI/CD jobs do not interpret files such as `.ruby-version`.
Instead, they use the Ruby installed in the Docker container they execute in, which is defined in `.gitlab-ci.yml`.
The container images used in these jobs are maintained in the [`gitlab-build-images`](https://gitlab.com/gitlab-org/gitlab-build-images) repository.
When we merge an update to an image, CI/CD jobs are affected as soon as the [image is built](https://gitlab.com/gitlab-org/gitlab-build-images/#pushing-a-rebuild-image).
1.**GitLab SaaS**. GitLab.com is deployed from customized Helm charts that use Docker images from [Cloud Native GitLab (CNG)](https://gitlab.com/gitlab-org/build/CNG).
Just like CI/CD, `.ruby-version` is meaningless in this environment. Instead, those Docker images must be patched to upgrade Ruby.
1.**Self-managed GitLab.** Customers installing GitLab via [Omnibus](https://gitlab.com/gitlab-org/omnibus-gitlab) use none of the above.
Instead, their Ruby version is defined by the [Ruby software bundle](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/config/software/ruby.rb) in Omnibus.
### Add new Ruby to CI/CD and development environments
To build and run Ruby gems and the GitLab Rails application with a new Ruby, you must first prepare CI/CD
and developer environments to include the new Ruby version.
At this stage, you *must not make it the default Ruby yet*, but make it optional instead. This allows
for a smoother transition by supporting both old and new Ruby versions for a period of time.
There are two places that require changes:
1.**[GitLab Build Images](https://gitlab.com/gitlab-org/gitlab-build-images).** These are Docker images
we use for runners and other Docker-based pre-production environments. The kind of change necessary
depends on the scope.
- For [patch level updates](https://gitlab.com/gitlab-org/gitlab-build-images/-/merge_requests/418), it should suffice to increment the patch level of `RUBY_VERSION`.
All projects building against the same minor release automatically download the new patch release.
- For [major and minor updates](https://gitlab.com/gitlab-org/gitlab-build-images/-/merge_requests/320), create a new set of Docker images that can be used side-by-side with existing images during the upgrade process. **Important:** Make sure to copy over all Ruby patch files
in the `/patches` directory to a new folder matching the Ruby version you upgrade to, or they aren't applied.
1.**[GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit).**
With the dependencies updated and the new gem versions released, you can update the main Rails
application with any necessary changes, similar to the gems and related systems.
On top of that, update the documentation to reflect the version change in the installation
and update instructions ([example](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68363)).
NOTE:
Be especially careful with timing this merge request, since as soon as it is merged, all GitLab contributors
will be affected by it and the changes will be deployed. You must ensure that this MR remains
open until everything else is ready, but it can be useful to get approval early to reduce lead time.
### Give developers time to upgrade (grace period)
With the new Ruby made available as an option, and all merge requests either ready or merged,
there should be a grace period (1 week at minimum) during which developers can
install the new Ruby on their machines. For GDK and `asdf` users this should happen automatically
via `gdk update`.
This pause is a good time to assess the risk of this upgrade for GitLab SaaS.
For Ruby upgrades that are high risk, such as major version upgrades, it is recommended to
coordinate the changes with the infrastructure team through a [change management request](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/).
GitLab patch release to self-managed customers. Consult our [release managers](https://about.gitlab.com/community/release-managers/)
for how to proceed.
## Ruby upgrade tooling
There are several tools that ease the upgrade process.
### Deprecation Toolkit
A common problem with Ruby upgrades is that deprecation warnings turn into errors. This means that every single
deprecation warning must be resolved before making the switch. To avoid new warnings from making it into the
main application branch, we use [`DeprecationToolkitEnv`](https://gitlab.com/gitlab-org/gitlab/blob/master/spec/deprecation_toolkit_env.rb).
This module observes deprecation warnings emitted from spec runs and turns them into test failures. This prevents
developers from checking in new code that would fail under a new Ruby.
Sometimes it cannot be avoided to introduce new warnings, for example when a Ruby gem we use emits these warnings
and we have no control over it. In these cases, add silences, like [this merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68865) did.
### Deprecation Logger
We also log Ruby and Rails deprecation warnings to a dedicated log file, `log/deprecation_json.log`
(see [GitLab Developers Guide to Logging](logging.md) for where to find GitLab log files),
which can provide clues when there is code that is not adequately covered by tests and hence would slip past `DeprecationToolkitEnv`.
During the upgrade process, consider the following recommendations:
- **Front-load as many changes as possible.** Especially for minor and major releases, it is likely that application
code will break or change. Any changes that are backward compatible should be merged into the main branch and
released independently ahead of the Ruby version upgrade. This ensures that we move in small increments and
get feedback from production environments early.
- **Create an experimental branch for larger updates.** We generally try to avoid long-running topic branches,
but for purposes of feedback and experimentation, it can be useful to have such a branch to get regular
feedback from CI/CD when running a newer Ruby. This can be helpful when first assessing what problems
we might run into, as [this MR](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50640) demonstrates.
These experimental branches are not intended to be merged; they can be closed once all required changes have been broken out
and merged back independently.
- **Give yourself enough time to fix problems ahead of a milestone release.** GitLab moves fast.
As a Ruby upgrade requires many MRs to be sent and reviewed, make sure all changes are merged at least a week
before the 22nd. This gives us extra time to act if something breaks. If in doubt, it is better to
postpone the upgrade to the following month, as we [prioritize availability over velocity](https://about.gitlab.com/handbook/engineering/#prioritizing-technical-decisions).