diff --git a/gems/gitlab-housekeeper/bin/gitlab-housekeeper b/gems/gitlab-housekeeper/bin/gitlab-housekeeper
index 1ad32b6562b97c75aa345fed74e1ef33d630bf9d..e81508b859dd5068b0bdc7adbc9e92d0ff6910bb 100755
--- a/gems/gitlab-housekeeper/bin/gitlab-housekeeper
+++ b/gems/gitlab-housekeeper/bin/gitlab-housekeeper
@@ -20,6 +20,10 @@ OptionParser.new do |opts|
     options[:keeps] = k
   end
 
+  opts.on('--filter-identifiers some-identifier-regex,another-regex', Array, 'Skip any changes where none of the identifiers match these regexes. The identifiers is an array, so at least one element must match at least one regex.') do |filters|
+    options[:filter_identifiers] = filters.map { |f| Regexp.new(f) }
+  end
+
   opts.on('-h', '--help', 'Prints this help') do
     abort opts.to_s
   end
diff --git a/gems/gitlab-housekeeper/lib/gitlab/housekeeper/change.rb b/gems/gitlab-housekeeper/lib/gitlab/housekeeper/change.rb
index 749e05317d3973747e2c3fc04585bc01da4cc730..190b83e30723544332f808df16227489a0b796a7 100644
--- a/gems/gitlab-housekeeper/lib/gitlab/housekeeper/change.rb
+++ b/gems/gitlab-housekeeper/lib/gitlab/housekeeper/change.rb
@@ -29,6 +29,14 @@ def commit_message
         MARKDOWN
       end
 
+      def matches_filters?(filters)
+        filters.all? do |filter|
+          identifiers.any? do |identifier|
+            identifier.match?(filter)
+          end
+        end
+      end
+
       def valid?
         @identifiers && @title && @description && @changed_files
       end
diff --git a/gems/gitlab-housekeeper/lib/gitlab/housekeeper/runner.rb b/gems/gitlab-housekeeper/lib/gitlab/housekeeper/runner.rb
index 51281ee5572db4d5258c1646a889dc585f27e37e..1826dff3f7f22c2f97ad1354eaaac342a7c2ae0d 100644
--- a/gems/gitlab-housekeeper/lib/gitlab/housekeeper/runner.rb
+++ b/gems/gitlab-housekeeper/lib/gitlab/housekeeper/runner.rb
@@ -11,7 +11,7 @@
 module Gitlab
   module Housekeeper
     class Runner
-      def initialize(max_mrs: 1, dry_run: false, keeps: nil)
+      def initialize(max_mrs: 1, dry_run: false, keeps: nil, filter_identifiers: [])
         @max_mrs = max_mrs
         @dry_run = dry_run
         @logger = Logger.new($stdout)
@@ -22,6 +22,8 @@ def initialize(max_mrs: 1, dry_run: false, keeps: nil)
                  else
                    all_keeps
                  end
+
+        @filter_identifiers = filter_identifiers
       end
 
       def run
@@ -39,6 +41,13 @@ def run
               branch_name = git.commit_in_branch(change)
               add_standard_change_data(change)
 
+              # Must be done after we commit so that we don't keep around changed files. We could checkout those files
+              # but then it might be riskier in local development in case we lose unrelated changes.
+              unless change.matches_filters?(@filter_identifiers)
+                puts "Skipping change: #{change.identifiers} due to not matching filter"
+                next
+              end
+
               if @dry_run
                 dry_run(change, branch_name)
               else
diff --git a/gems/gitlab-housekeeper/spec/gitlab/housekeeper/change_spec.rb b/gems/gitlab-housekeeper/spec/gitlab/housekeeper/change_spec.rb
index 8060507210085c663bdfcc25d60e050c640a4410..e1fecd6d16a37dbc4d2a012d315b5f7999dd40ed 100644
--- a/gems/gitlab-housekeeper/spec/gitlab/housekeeper/change_spec.rb
+++ b/gems/gitlab-housekeeper/spec/gitlab/housekeeper/change_spec.rb
@@ -51,4 +51,25 @@
       end
     end
   end
+
+  describe '#matches_filters?' do
+    let(:identifiers) { %w[this-is a-list of IdentifierS] }
+    let(:change) { create_change(identifiers: identifiers) }
+
+    it 'matches when all regexes match at least one identifier' do
+      expect(change.matches_filters?([/list/, /Ide.*fier/])).to eq(true)
+    end
+
+    it 'does not match when none of the regexes match' do
+      expect(change.matches_filters?([/nomatch/, /Ide.*fffffier/])).to eq(false)
+    end
+
+    it 'does not match when only some of the regexes match' do
+      expect(change.matches_filters?([/nomatch/, /Ide.*fier/])).to eq(false)
+    end
+
+    it 'matches an empty list of filters' do
+      expect(change.matches_filters?([])).to eq(true)
+    end
+  end
 end
diff --git a/gems/gitlab-housekeeper/spec/gitlab/housekeeper/runner_spec.rb b/gems/gitlab-housekeeper/spec/gitlab/housekeeper/runner_spec.rb
index a967d74db90a60dbabc2ba53912440b77027f5f8..13f58f52771a1a685d14507c50b7ea8c7dfed587 100644
--- a/gems/gitlab-housekeeper/spec/gitlab/housekeeper/runner_spec.rb
+++ b/gems/gitlab-housekeeper/spec/gitlab/housekeeper/runner_spec.rb
@@ -115,6 +115,39 @@
       described_class.new(max_mrs: 2, keeps: [fake_keep]).run
     end
 
+    context 'when given filter_identifiers' do
+      it 'skips a change that does not match the filter_identifiers' do
+        # Branches get created. We allow branches to be created for filtered changes but we don't want to push them.
+        allow(git).to receive(:commit_in_branch).and_return("the-branch-should-not-be-pushed")
+        expect(git).to receive(:commit_in_branch).with(change2)
+          .and_return('the-identifier-for-the-second-change')
+
+        # Branches get shown and pushed
+        expect(::Gitlab::Housekeeper::Shell).to receive(:execute)
+          .with('git', '--no-pager', 'diff', '--color=always', 'master',
+            'the-identifier-for-the-second-change', '--', 'change1.txt', 'change2.txt')
+        expect(::Gitlab::Housekeeper::Shell).to receive(:execute)
+          .with('git', 'push', '-f', 'housekeeper',
+            'the-identifier-for-the-second-change:the-identifier-for-the-second-change')
+
+        # Merge requests get created
+        expect(gitlab_client).to receive(:create_or_update_merge_request)
+          .with(
+            change: change2,
+            source_project_id: '123',
+            source_branch: 'the-identifier-for-the-second-change',
+            target_branch: 'master',
+            target_project_id: '456',
+            update_title: true,
+            update_description: true,
+            update_labels: true,
+            update_reviewers: true
+          )
+
+        described_class.new(max_mrs: 2, keeps: [fake_keep], filter_identifiers: [/second/]).run
+      end
+    end
+
     context 'when title, description, code has changed already' do
       it 'does not update the changed details' do
         # First change has updated code and description so should only update title