diff --git a/files/gitlab-ctl-commands/lib/praefect.rb b/files/gitlab-ctl-commands/lib/praefect.rb
new file mode 100644
index 0000000000000000000000000000000000000000..76e7a95f8f5baca58354579f61e50ec6c2e9b58d_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9saWIvcHJhZWZlY3QucmI=
--- /dev/null
+++ b/files/gitlab-ctl-commands/lib/praefect.rb
@@ -0,0 +1,89 @@
+require 'optparse'
+
+module Praefect
+  EXEC_PATH = '/opt/gitlab/embedded/bin/praefect'.freeze
+  DIR_PATH = '/var/opt/gitlab/praefect'.freeze
+
+  USAGE ||= <<~EOS.freeze
+    Usage:
+      gitlab-ctl praefect command [options]
+
+    COMMANDS:
+      remove-repository     Remove repository from Gitaly cluster
+  EOS
+
+  def self.parse_options!(args)
+    loop do
+      break if args.shift == 'praefect'
+    end
+
+    global = OptionParser.new do |opts|
+      opts.on('-h', '--help', 'Usage help') do
+        Kernel.puts USAGE
+        Kernel.exit 0
+      end
+    end
+
+    options = {}
+    commands = {
+      'remove-repository' => OptionParser.new do |opts|
+        opts.banner = "Usage: gitlab-ctl praefect remove-repository [options]"
+
+        opts.on("-h", "--help", "Prints this help") do
+          Kernel.puts opts
+          Kernel.exit 0
+        end
+
+        opts.on('--virtual-storage-name NAME', 'Name of the virtual storage where the repository resides (mandatory)') do |virtual_storage_name|
+          options[:virtual_storage_name] = virtual_storage_name
+        end
+
+        opts.on('--repository-relative-path PATH', 'Relative path to the repository on the disk (mandatory)') do |repository_relative_path|
+          options[:repository_relative_path] = repository_relative_path
+        end
+
+        opts.on('--dir DIR', 'Directory in which Praefect is installed') do |dir|
+          options[:dir] = dir
+        end
+      end
+    }
+
+    global.order!(args)
+
+    command = args.shift
+
+    raise OptionParser::ParseError, "Praefect command is not specified." \
+      if command.nil? || command.empty?
+
+    raise OptionParser::ParseError, "Unknown Praefect command: #{command}" \
+      unless commands.key?(command)
+
+    commands[command].parse!(args)
+
+    raise OptionParser::ParseError, "Option --virtual-storage-name must be specified" \
+      unless options.key?(:virtual_storage_name)
+
+    raise OptionParser::ParseError, "Option --repository-relative-path must be specified" \
+      unless options.key?(:repository_relative_path)
+
+    options[:command] = command
+    options
+  end
+
+  def self.execute(options)
+    config_file_path = File.join(options.fetch(:dir, DIR_PATH), 'config.toml')
+
+    [EXEC_PATH, config_file_path].each do |path|
+      next if File.exist?(path)
+
+      Kernel.abort "Could not find '#{path}' file. Is your package installed correctly?"
+    end
+
+    command = [EXEC_PATH, "-config", config_file_path, options[:command]]
+    command += ["-virtual-storage", options[:virtual_storage_name]]
+    command += ["-repository", options[:repository_relative_path]]
+
+    status = Kernel.system(*command)
+    Kernel.exit!(1) unless status
+  end
+end
diff --git a/files/gitlab-ctl-commands/praefect.rb b/files/gitlab-ctl-commands/praefect.rb
new file mode 100644
index 0000000000000000000000000000000000000000..76e7a95f8f5baca58354579f61e50ec6c2e9b58d_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9wcmFlZmVjdC5yYg==
--- /dev/null
+++ b/files/gitlab-ctl-commands/praefect.rb
@@ -0,0 +1,18 @@
+require 'optparse'
+
+require "#{base_path}/embedded/service/omnibus-ctl/lib/gitlab_ctl"
+require "#{base_path}/embedded/service/omnibus-ctl/lib/praefect"
+
+add_command_under_category('praefect', 'gitaly', 'Interact with Gitaly cluster', 2) do
+  begin
+    options = Praefect.parse_options!(ARGV)
+  rescue OptionParser::ParseError => e
+    warn "#{e}\n\n#{Praefect::USAGE}"
+    exit 128
+  end
+
+  puts "Running #{options[:command]}"
+  Praefect.execute(options)
+  puts "Done."
+  exit 0
+end
diff --git a/spec/gitlab-ctl-commands/lib/praefect_spec.rb b/spec/gitlab-ctl-commands/lib/praefect_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..76e7a95f8f5baca58354579f61e50ec6c2e9b58d_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi9wcmFlZmVjdF9zcGVjLnJi
--- /dev/null
+++ b/spec/gitlab-ctl-commands/lib/praefect_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+require 'optparse'
+
+require_relative('../../../files/gitlab-ctl-commands/lib/praefect')
+
+RSpec.describe Praefect do
+  describe '.parse_options!' do
+    before do
+      allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" }
+    end
+
+    it 'throws an error when command is not specified' do
+      expect { Praefect.parse_options!(%w(praefect)) }.to raise_error(OptionParser::ParseError, /Praefect command is not specified/)
+    end
+
+    it 'throws an error when unknown command is specified' do
+      expect { Praefect.parse_options!(%w(praefect unknown-command)) }.to raise_error(OptionParser::ParseError, /Unknown Praefect command: unknown-command/)
+    end
+
+    it 'throws an error when unknown option is specified' do
+      expect { Praefect.parse_options!(%w(praefect remove-repository --unknown)) }.to raise_error(OptionParser::InvalidOption, /unknown/)
+    end
+
+    it 'throws an error when an argument for --virtual-storage-name is not specified' do
+      expect { Praefect.parse_options!(%w(praefect remove-repository --virtual-storage-name)) }.to raise_error(OptionParser::MissingArgument, /virtual-storage-name/)
+    end
+
+    it 'throws an error when --virtual-storage-name is not specified' do
+      expect { Praefect.parse_options!(%w(praefect remove-repository)) }.to raise_error(OptionParser::ParseError, /Option --virtual-storage-name must be specified/)
+    end
+
+    it 'throws an error when an argument for --repository-relative-path is not specified' do
+      expect { Praefect.parse_options!(%w(praefect remove-repository --repository-relative-path)) }.to raise_error(OptionParser::MissingArgument, /repository-relative-path/)
+    end
+
+    it 'throws an error when --repository-relative-path is not specified' do
+      expect { Praefect.parse_options!(%w(praefect remove-repository --virtual-storage-name name)) }.to raise_error(OptionParser::ParseError, /Option --repository-relative-path must be specified/)
+    end
+
+    it 'successfully parses correct params' do
+      expected_options = { command: 'remove-repository', virtual_storage_name: 'name', repository_relative_path: 'path' }
+      expect(Praefect.parse_options!(%w(praefect remove-repository --virtual-storage-name name --repository-relative-path path))).to eq(expected_options)
+    end
+
+    context 'when help option is passed' do
+      it 'shows help message and exit for global help option' do
+        expect(Kernel).to receive(:puts) do |msg|
+          expect(msg.to_s).to match(/gitlab-ctl praefect command/)
+        end
+
+        expect { Praefect.parse_options!(%w(praefect -h)) }.to raise_error('Kernel.exit(0)')
+      end
+
+      ['remove-repository'].each do |cmd|
+        it "shows help message and exit for #{cmd} help option" do
+          expect(Kernel).to receive(:puts) do |msg|
+            expect(msg.to_s).to match(/gitlab-ctl praefect remove-repository/)
+          end
+
+          expect { Praefect.parse_options!(%W(praefect #{cmd} -h)) }.to raise_error('Kernel.exit(0)')
+        end
+      end
+    end
+  end
+
+  describe '#execute' do
+    context 'when package is not installed correctly' do
+      it 'aborts the execution if a path does not exit' do
+        allow(File).to receive(:exists?).twice.and_return(false)
+        expect(Kernel).to receive(:abort).and_raise('aborted')
+
+        expect { Praefect.execute({}) }.to raise_error('aborted')
+      end
+    end
+
+    it 'executes the command' do
+      allow(File).to receive(:exist?).and_return(true)
+      expect(Kernel).not_to receive(:abort)
+
+      expect(Kernel).to receive(:system).with(
+        Praefect::EXEC_PATH, '-config', 'dir/config.toml', 'cmd',
+        '-virtual-storage', 'storage-name', '-repository', 'repository-path'
+      ).and_return('result!')
+      expect(Kernel).not_to receive(:exit!)
+
+      options = {
+        virtual_storage_name: 'storage-name',
+        repository_relative_path: 'repository-path',
+        command: 'cmd',
+        dir: 'dir'
+      }
+      Praefect.execute(options)
+    end
+  end
+end