diff --git a/db/post_migrate/20240325131114_move_self_managed_cr_to_instance.rb b/db/post_migrate/20240325131114_move_self_managed_cr_to_instance.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d41d6af4f7d3352b057bf750fb6bd6e81d23b9eb
--- /dev/null
+++ b/db/post_migrate/20240325131114_move_self_managed_cr_to_instance.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class MoveSelfManagedCrToInstance < Gitlab::Database::Migration[2.2]
+  milestone '17.0'
+
+  disable_ddl_transaction!
+
+  restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+  def up
+    # the migration needs to run only on self-managed
+    return if Gitlab.com?
+
+    sql = <<~SQL
+      UPDATE member_roles mr SET name = CONCAT(mr.name, ' (', g.name, ' - ', g.id, ')'), namespace_id = NULL
+        FROM namespaces g WHERE mr.namespace_id IS NOT NULL AND
+        g.id = mr.namespace_id
+    SQL
+
+    execute(sql)
+  end
+
+  def down; end
+end
diff --git a/db/schema_migrations/20240325131114 b/db/schema_migrations/20240325131114
new file mode 100644
index 0000000000000000000000000000000000000000..f241e126ea71fc491508b349293a509db6e50da0
--- /dev/null
+++ b/db/schema_migrations/20240325131114
@@ -0,0 +1 @@
+1a934bf4e7dbc8355b0c38a4e6231b2021115c19b3e078f07de56ada378604e1
\ No newline at end of file
diff --git a/spec/migrations/20240325131114_move_self_managed_cr_to_instance_spec.rb b/spec/migrations/20240325131114_move_self_managed_cr_to_instance_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1a6e9c7bddc69702836dc125a881344a2cbe6245
--- /dev/null
+++ b/spec/migrations/20240325131114_move_self_managed_cr_to_instance_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe MoveSelfManagedCrToInstance, feature_category: :permissions do
+  let(:migration) { described_class.new }
+
+  let(:namespaces) { table(:namespaces) }
+  let(:member_roles) { table(:member_roles) }
+
+  let(:group_1) { namespaces.create!(name: 'Group 1', path: 'group1') }
+  let(:group_dupl) { namespaces.create!(name: 'Group 1', path: 'group_duplicated') }
+  let(:group_2) { namespaces.create!(name: 'Other group', path: 'other_group') }
+
+  let!(:group_1_role_1) { member_roles.create!(name: 'foo', namespace_id: group_1.id, base_access_level: 10) }
+  let!(:group_1_role_2) { member_roles.create!(name: 'other role', namespace_id: group_1.id, base_access_level: 10) }
+  let!(:group_dupl_role_1) { member_roles.create!(name: 'foo', namespace_id: group_dupl.id, base_access_level: 10) }
+  let!(:group_2_role) { member_roles.create!(name: 'foo', namespace_id: group_2.id, base_access_level: 10) }
+  let!(:instance_role) { member_roles.create!(name: 'foo', namespace_id: nil, base_access_level: 10) }
+
+  describe '#up' do
+    context 'when on self managed' do
+      it 'sets namespace_id to nil and updates name', :aggregate_failures do
+        migration.up
+
+        expect(group_1_role_1.reload.name).to eq("foo (Group 1 - #{group_1.id})")
+        expect(group_1_role_1.namespace_id).to be_nil
+        expect(group_1_role_2.reload.name).to eq("other role (Group 1 - #{group_1.id})")
+        expect(group_1_role_2.namespace_id).to be_nil
+        expect(group_dupl_role_1.reload.name).to eq("foo (Group 1 - #{group_dupl.id})")
+        expect(group_dupl_role_1.namespace_id).to be_nil
+        expect(group_2_role.reload.name).to eq("foo (Other group - #{group_2.id})")
+        expect(group_2_role.namespace_id).to be_nil
+        expect(instance_role.reload.name).to eq('foo') # no update for instance-level role
+        expect(instance_role.namespace_id).to be_nil
+      end
+    end
+
+    context 'when on SaaS', :saas do
+      it 'does not update the custom roles', :aggregate_failures do
+        migration.up
+
+        expect(group_1_role_1.reload.name).to eq('foo')
+        expect(group_1_role_1.namespace_id).to eq(group_1.id)
+        expect(group_1_role_2.reload.name).to eq('other role')
+        expect(group_1_role_2.namespace_id).to eq(group_1.id)
+        expect(group_2_role.reload.name).to eq('foo')
+        expect(group_2_role.namespace_id).to eq(group_2.id)
+        expect(instance_role.reload.name).to eq('foo')
+        expect(instance_role.namespace_id).to be_nil
+      end
+    end
+  end
+end