diff --git a/db/docs/batched_background_migrations/backfill_ci_resources_project_id.yml b/db/docs/batched_background_migrations/backfill_ci_resources_project_id.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5743ac97fc6f4175f98ee3ea853d751e2bb8607c
--- /dev/null
+++ b/db/docs/batched_background_migrations/backfill_ci_resources_project_id.yml
@@ -0,0 +1,9 @@
+---
+migration_job_name: BackfillCiResourcesProjectId
+description: Backfills sharding key `ci_resources.project_id` from `ci_resource_groups`.
+feature_category: continuous_integration
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167645
+milestone: '17.5'
+queued_migration_version: 20240930154303
+finalize_after: '2024-10-22'
+finalized_by: # version of the migration that finalized this BBM
diff --git a/db/docs/ci_resources.yml b/db/docs/ci_resources.yml
index f557aa50d2b577765e28462064d2c4a9bd3b244e..5a8aa27ec2e5498a8ee1e512078204b8f940a7e4 100644
--- a/db/docs/ci_resources.yml
+++ b/db/docs/ci_resources.yml
@@ -17,3 +17,4 @@ desired_sharding_key:
         table: ci_resource_groups
         sharding_key: project_id
         belongs_to: resource_group
+desired_sharding_key_migration_job_name: BackfillCiResourcesProjectId
diff --git a/db/migrate/20240930154300_add_project_id_to_ci_resources.rb b/db/migrate/20240930154300_add_project_id_to_ci_resources.rb
new file mode 100644
index 0000000000000000000000000000000000000000..393f6f9f5c3a05de2e8d7efa913433351dabb6ea
--- /dev/null
+++ b/db/migrate/20240930154300_add_project_id_to_ci_resources.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddProjectIdToCiResources < Gitlab::Database::Migration[2.2]
+  milestone '17.5'
+
+  def change
+    add_column :ci_resources, :project_id, :bigint
+  end
+end
diff --git a/db/post_migrate/20240930154301_index_ci_resources_on_project_id.rb b/db/post_migrate/20240930154301_index_ci_resources_on_project_id.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3aef341b025ba5f95ad2f2fab2544e29c237d405
--- /dev/null
+++ b/db/post_migrate/20240930154301_index_ci_resources_on_project_id.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class IndexCiResourcesOnProjectId < Gitlab::Database::Migration[2.2]
+  milestone '17.5'
+  disable_ddl_transaction!
+
+  INDEX_NAME = 'index_ci_resources_on_project_id'
+
+  def up
+    add_concurrent_index :ci_resources, :project_id, name: INDEX_NAME
+  end
+
+  def down
+    remove_concurrent_index_by_name :ci_resources, INDEX_NAME
+  end
+end
diff --git a/db/post_migrate/20240930154302_add_ci_resources_project_id_trigger.rb b/db/post_migrate/20240930154302_add_ci_resources_project_id_trigger.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5bebbce2292347b4cd16e6678c44ee9481a30693
--- /dev/null
+++ b/db/post_migrate/20240930154302_add_ci_resources_project_id_trigger.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddCiResourcesProjectIdTrigger < Gitlab::Database::Migration[2.2]
+  milestone '17.5'
+
+  def up
+    install_sharding_key_assignment_trigger(
+      table: :ci_resources,
+      sharding_key: :project_id,
+      parent_table: :ci_resource_groups,
+      parent_sharding_key: :project_id,
+      foreign_key: :resource_group_id
+    )
+  end
+
+  def down
+    remove_sharding_key_assignment_trigger(
+      table: :ci_resources,
+      sharding_key: :project_id,
+      parent_table: :ci_resource_groups,
+      parent_sharding_key: :project_id,
+      foreign_key: :resource_group_id
+    )
+  end
+end
diff --git a/db/post_migrate/20240930154303_queue_backfill_ci_resources_project_id.rb b/db/post_migrate/20240930154303_queue_backfill_ci_resources_project_id.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7d09c028a77a9dac6f22671acde783125d28821e
--- /dev/null
+++ b/db/post_migrate/20240930154303_queue_backfill_ci_resources_project_id.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class QueueBackfillCiResourcesProjectId < Gitlab::Database::Migration[2.2]
+  milestone '17.5'
+  restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+  MIGRATION = "BackfillCiResourcesProjectId"
+  DELAY_INTERVAL = 2.minutes
+  BATCH_SIZE = 1000
+  SUB_BATCH_SIZE = 100
+
+  def up
+    queue_batched_background_migration(
+      MIGRATION,
+      :ci_resources,
+      :id,
+      :project_id,
+      :ci_resource_groups,
+      :project_id,
+      :resource_group_id,
+      job_interval: DELAY_INTERVAL,
+      batch_size: BATCH_SIZE,
+      sub_batch_size: SUB_BATCH_SIZE
+    )
+  end
+
+  def down
+    delete_batched_background_migration(
+      MIGRATION,
+      :ci_resources,
+      :id,
+      [
+        :project_id,
+        :ci_resource_groups,
+        :project_id,
+        :resource_group_id
+      ]
+    )
+  end
+end
diff --git a/db/schema_migrations/20240930154300 b/db/schema_migrations/20240930154300
new file mode 100644
index 0000000000000000000000000000000000000000..97e825455880e45269d828ca0593b109ccc492ba
--- /dev/null
+++ b/db/schema_migrations/20240930154300
@@ -0,0 +1 @@
+514b46352f4e88800a8bc7fbec6ffb04330000b8acc2959d1d3278f9c03cd73d
\ No newline at end of file
diff --git a/db/schema_migrations/20240930154301 b/db/schema_migrations/20240930154301
new file mode 100644
index 0000000000000000000000000000000000000000..96dc8d9e559f6e28f4fa21ba5638b40d94727a03
--- /dev/null
+++ b/db/schema_migrations/20240930154301
@@ -0,0 +1 @@
+3bfe20df65af690a2471a64737b9021af4de2c0502369673820ee48a40d3f53a
\ No newline at end of file
diff --git a/db/schema_migrations/20240930154302 b/db/schema_migrations/20240930154302
new file mode 100644
index 0000000000000000000000000000000000000000..d94105f6a98c437a05c76ebc1f7cfe10f8cd5bd1
--- /dev/null
+++ b/db/schema_migrations/20240930154302
@@ -0,0 +1 @@
+afbcc248e293796fa532187be6c3eac72670cde338f6274e5eba36c378ff3af1
\ No newline at end of file
diff --git a/db/schema_migrations/20240930154303 b/db/schema_migrations/20240930154303
new file mode 100644
index 0000000000000000000000000000000000000000..67f9cacfd4c1af14531f38347d9b8fccd21e2350
--- /dev/null
+++ b/db/schema_migrations/20240930154303
@@ -0,0 +1 @@
+e02bd928089badc13d78bb9345667495534101b578dcf7d49d5902341b496277
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index c676f5e746b47d3a5ca60f8a02790fabfc0cd2b3..b83c929dee5e3b6d7a136610eba8997d7101d5d5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -985,6 +985,22 @@ RETURN NEW;
 END
 $$;
 
+CREATE FUNCTION trigger_1c0f1ca199a3() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+IF NEW."project_id" IS NULL THEN
+  SELECT "project_id"
+  INTO NEW."project_id"
+  FROM "ci_resource_groups"
+  WHERE "ci_resource_groups"."id" = NEW."resource_group_id";
+END IF;
+
+RETURN NEW;
+
+END
+$$;
+
 CREATE FUNCTION trigger_1ed40f4d5f4e() RETURNS trigger
     LANGUAGE plpgsql
     AS $$
@@ -8860,7 +8876,8 @@ CREATE TABLE ci_resources (
     updated_at timestamp with time zone NOT NULL,
     resource_group_id bigint NOT NULL,
     build_id bigint,
-    partition_id bigint
+    partition_id bigint,
+    project_id bigint
 );
 
 CREATE SEQUENCE ci_resources_id_seq
@@ -28149,6 +28166,8 @@ CREATE INDEX index_ci_resources_on_build_id ON ci_resources USING btree (build_i
 
 CREATE INDEX index_ci_resources_on_partition_id_build_id ON ci_resources USING btree (partition_id, build_id);
 
+CREATE INDEX index_ci_resources_on_project_id ON ci_resources USING btree (project_id);
+
 CREATE UNIQUE INDEX index_ci_resources_on_resource_group_id_and_build_id ON ci_resources USING btree (resource_group_id, build_id);
 
 CREATE INDEX index_ci_runner_machines_on_contacted_at_desc_and_id_desc ON ci_runner_machines USING btree (contacted_at DESC, id DESC);
@@ -33407,6 +33426,8 @@ CREATE TRIGGER trigger_174b23fa3dfb BEFORE INSERT OR UPDATE ON approval_project_
 
 CREATE TRIGGER trigger_18bc439a6741 BEFORE INSERT OR UPDATE ON packages_conan_metadata FOR EACH ROW EXECUTE FUNCTION trigger_18bc439a6741();
 
+CREATE TRIGGER trigger_1c0f1ca199a3 BEFORE INSERT OR UPDATE ON ci_resources FOR EACH ROW EXECUTE FUNCTION trigger_1c0f1ca199a3();
+
 CREATE TRIGGER trigger_1ed40f4d5f4e BEFORE INSERT OR UPDATE ON packages_maven_metadata FOR EACH ROW EXECUTE FUNCTION trigger_1ed40f4d5f4e();
 
 CREATE TRIGGER trigger_206cbe2dc1a2 BEFORE INSERT OR UPDATE ON packages_package_files FOR EACH ROW EXECUTE FUNCTION trigger_206cbe2dc1a2();
diff --git a/lib/gitlab/background_migration/backfill_ci_resources_project_id.rb b/lib/gitlab/background_migration/backfill_ci_resources_project_id.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9a9ce844476bb0677cfe877fcf3a66bdf378c632
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_ci_resources_project_id.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module BackgroundMigration
+    class BackfillCiResourcesProjectId < BackfillDesiredShardingKeyJob
+      operation_name :backfill_ci_resources_project_id
+      feature_category :continuous_integration
+    end
+  end
+end
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index e78de9527a09d5df7d0b47c45063667751999c08..12cefdf63e0f30a621413b13eddecd2ade9d71b9 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -90,6 +90,7 @@
     ci_pipeline_variables: %w[partition_id pipeline_id project_id],
     ci_pipelines: %w[partition_id auto_canceled_by_partition_id],
     ci_unit_test_failures: %w[project_id],
+    ci_resources: %w[project_id],
     p_ci_pipelines: %w[partition_id auto_canceled_by_partition_id auto_canceled_by_id],
     p_ci_runner_machine_builds: %w[project_id],
     ci_runners: %w[sharding_key_id], # This value is meant to populate the partitioned table, no other usage
diff --git a/spec/lib/gitlab/background_migration/backfill_ci_resources_project_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_ci_resources_project_id_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a7abffa684310fc0b1fcf4de918d969b31fc4124
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_ci_resources_project_id_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillCiResourcesProjectId,
+  feature_category: :continuous_integration,
+  schema: 20240930154300,
+  migration: :gitlab_ci do
+  include_examples 'desired sharding key backfill job' do
+    let(:batch_table) { :ci_resources }
+    let(:backfill_column) { :project_id }
+    let(:backfill_via_table) { :ci_resource_groups }
+    let(:backfill_via_column) { :project_id }
+    let(:backfill_via_foreign_key) { :resource_group_id }
+  end
+end
diff --git a/spec/migrations/20240930154304_queue_backfill_ci_resources_project_id_spec.rb b/spec/migrations/20240930154304_queue_backfill_ci_resources_project_id_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6cf800caea00f4a660553b9d478d5ce458b53d9b
--- /dev/null
+++ b/spec/migrations/20240930154304_queue_backfill_ci_resources_project_id_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillCiResourcesProjectId, migration: :gitlab_ci, feature_category: :continuous_integration do
+  let!(:batched_migration) { described_class::MIGRATION }
+
+  it 'schedules a new batched migration' do
+    reversible_migration do |migration|
+      migration.before -> {
+        expect(batched_migration).not_to have_scheduled_batched_migration
+      }
+
+      migration.after -> {
+        expect(batched_migration).to have_scheduled_batched_migration(
+          table_name: :ci_resources,
+          column_name: :id,
+          interval: described_class::DELAY_INTERVAL,
+          batch_size: described_class::BATCH_SIZE,
+          sub_batch_size: described_class::SUB_BATCH_SIZE,
+          gitlab_schema: :gitlab_ci,
+          job_arguments: [
+            :project_id,
+            :ci_resource_groups,
+            :project_id,
+            :resource_group_id
+          ]
+        )
+      }
+    end
+  end
+end