diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue
index c28e94322e5afaa1c708cc9a135fca60691caa97..d5b964f07ab70dbd7532b6d8c8cbfe8160ee0f7a 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_protection_rules.vue
@@ -5,12 +5,14 @@ import {
   GlCard,
   GlKeysetPagination,
   GlLoadingIcon,
+  GlModal,
   GlModalDirective,
   GlTable,
 } from '@gitlab/ui';
 import protectionRulesQuery from '~/packages_and_registries/settings/project/graphql/queries/get_container_protection_rules.query.graphql';
 import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
 import ContainerProtectionRuleForm from '~/packages_and_registries/settings/project/components/container_protection_rule_form.vue';
+import deleteContainerProtectionRuleMutation from '~/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql';
 import { s__, __ } from '~/locale';
 
 const PAGINATION_DEFAULT_PER_PAGE = 10;
@@ -36,6 +38,7 @@ export default {
     GlCard,
     GlKeysetPagination,
     GlLoadingIcon,
+    GlModal,
     GlTable,
     SettingsBlock,
   },
@@ -46,8 +49,16 @@ export default {
   i18n: {
     settingBlockTitle: s__('ContainerRegistry|Protected containers'),
     settingBlockDescription: s__(
-      'ContainerRegistry|When a container is protected then only certain user roles are able to update and delete the protected container. This helps to avoid tampering with the container.',
+      'ContainerRegistry|When a container is protected then only certain user roles are able to push and delete the protected container image. This helps to avoid tampering with the container image.',
     ),
+    protectionRuleDeletionConfirmModal: {
+      title: s__(
+        'ContainerRegistry|Are you sure you want to delete the container protection rule?',
+      ),
+      description: s__(
+        'ContainerRegistry|Users with at least the Developer role for this project will be able to push and delete container images.',
+      ),
+    },
   },
   apollo: {
     protectionRulesQueryPayload: {
@@ -81,6 +92,7 @@ export default {
     tableItems() {
       return this.protectionRulesQueryResult.map((protectionRule) => {
         return {
+          id: protectionRule.id,
           deleteProtectedUpToAccessLevel:
             ACCESS_LEVEL_GRAPHQL_VALUE_TO_LABEL[protectionRule.deleteProtectedUpToAccessLevel],
           pushProtectedUpToAccessLevel:
@@ -107,6 +119,19 @@ export default {
     isAddProtectionRuleButtonDisabled() {
       return this.protectionRuleFormVisibility;
     },
+    modalActionPrimary() {
+      return {
+        text: __('Delete'),
+        attributes: {
+          variant: 'danger',
+        },
+      };
+    },
+    modalActionCancel() {
+      return {
+        text: __('Cancel'),
+      };
+    },
   },
   methods: {
     showProtectionRuleForm() {
@@ -150,6 +175,32 @@ export default {
     isProtectionRuleMutationInProgress(item) {
       return this.protectionRuleMutationItem === item && this.protectionRuleMutationInProgress;
     },
+    deleteProtectionRule(protectionRule) {
+      this.clearAlertMessage();
+
+      this.protectionRuleMutationInProgress = true;
+
+      return this.$apollo
+        .mutate({
+          mutation: deleteContainerProtectionRuleMutation,
+          variables: { input: { id: protectionRule.id } },
+        })
+        .then(({ data }) => {
+          const [errorMessage] = data?.deleteContainerRegistryProtectionRule?.errors ?? [];
+          if (errorMessage) {
+            this.alertErrorMessage = errorMessage;
+            return;
+          }
+          this.refetchProtectionRules();
+          this.$toast.show(s__('ContainerRegistry|Protection rule deleted.'));
+        })
+        .catch((e) => {
+          this.alertErrorMessage = e.message;
+        })
+        .finally(() => {
+          this.resetProtectionRuleMutation();
+        });
+    },
   },
   fields: [
     {
@@ -167,7 +218,14 @@ export default {
       label: I18N_DELETE_PROTECTED_UP_TO_ACCESS_LEVEL,
       tdClass: 'gl-vertical-align-middle!',
     },
+    {
+      key: 'rowActions',
+      label: '',
+      thClass: 'gl-display-none',
+      tdClass: 'gl-vertical-align-middle! gl-text-right',
+    },
   ],
+  modal: { id: 'delete-protection-rule-confirmation-modal' },
 };
 </script>
 
@@ -227,6 +285,18 @@ export default {
             <template #table-busy>
               <gl-loading-icon size="sm" class="gl-my-5" />
             </template>
+
+            <template #cell(rowActions)="{ item }">
+              <gl-button
+                v-gl-modal="$options.modal.id"
+                category="secondary"
+                variant="danger"
+                size="small"
+                :disabled="isProtectionRuleDeleteButtonDisabled(item)"
+                @click="showProtectionRuleDeletionConfirmModal(item)"
+                >{{ s__('ContainerRegistry|Delete rule') }}</gl-button
+              >
+            </template>
           </gl-table>
 
           <div v-if="shouldShowPagination" class="gl-display-flex gl-justify-content-center">
@@ -241,6 +311,17 @@ export default {
           </div>
         </template>
       </gl-card>
+
+      <gl-modal
+        :modal-id="$options.modal.id"
+        size="sm"
+        :title="$options.i18n.protectionRuleDeletionConfirmModal.title"
+        :action-primary="modalActionPrimary"
+        :action-cancel="modalActionCancel"
+        @primary="deleteProtectionRule(protectionRuleMutationItem)"
+      >
+        <p>{{ $options.i18n.protectionRuleDeletionConfirmModal.description }}</p>
+      </gl-modal>
     </template>
   </settings-block>
 </template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..36b76ad2639de2fbeb4f86b1ae6319f0b40e931c
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql
@@ -0,0 +1,13 @@
+mutation deleteContainerRegistryProtectionRule(
+  $input: DeleteContainerRegistryProtectionRuleInput!
+) {
+  deleteContainerRegistryProtectionRule(input: $input) {
+    containerRegistryProtectionRule {
+      id
+      repositoryPathPattern
+      pushProtectedUpToAccessLevel
+      deleteProtectedUpToAccessLevel
+    }
+    errors
+  }
+}
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1817711dda22a872f4a2b50305919637bedfc282..402cf85248488db5ba54c4967ca4f395236948c9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13889,6 +13889,9 @@ msgstr ""
 msgid "ContainerRegistry|Add rule"
 msgstr ""
 
+msgid "ContainerRegistry|Are you sure you want to delete the container protection rule?"
+msgstr ""
+
 msgid "ContainerRegistry|Build an image"
 msgstr ""
 
@@ -13973,6 +13976,9 @@ msgstr ""
 msgid "ContainerRegistry|Delete protected up to access level"
 msgstr ""
 
+msgid "ContainerRegistry|Delete rule"
+msgstr ""
+
 msgid "ContainerRegistry|Delete selected tags"
 msgstr ""
 
@@ -14063,6 +14069,9 @@ msgstr ""
 msgid "ContainerRegistry|Protected containers"
 msgstr ""
 
+msgid "ContainerRegistry|Protection rule deleted."
+msgstr ""
+
 msgid "ContainerRegistry|Published %{timeInfo}"
 msgstr ""
 
@@ -14221,10 +14230,13 @@ msgstr ""
 msgid "ContainerRegistry|To widen your search, change or remove the filters above."
 msgstr ""
 
+msgid "ContainerRegistry|Users with at least the Developer role for this project will be able to push and delete container images."
+msgstr ""
+
 msgid "ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review  %{docLinkStart}the troubleshooting documentation%{docLinkEnd}."
 msgstr ""
 
-msgid "ContainerRegistry|When a container is protected then only certain user roles are able to update and delete the protected container. This helps to avoid tampering with the container."
+msgid "ContainerRegistry|When a container is protected then only certain user roles are able to push and delete the protected container image. This helps to avoid tampering with the container image."
 msgstr ""
 
 msgid "ContainerRegistry|While the rename is in progress, new uploads to the container registry are blocked. Ongoing uploads may fail and need to be retried."
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js
index 44fe8baa1d59b023e60c5dd1d5e48c250d66a55e..40333dc3bdb4751efa3b98f0a33a22879288b517 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_protection_rules_spec.js
@@ -1,14 +1,20 @@
-import { GlLoadingIcon, GlKeysetPagination } from '@gitlab/ui';
+import { GlLoadingIcon, GlKeysetPagination, GlModal } from '@gitlab/ui';
 import Vue from 'vue';
 import VueApollo from 'vue-apollo';
 import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { getBinding } from 'helpers/vue_mock_directive';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
 import ContainerProtectionRuleForm from '~/packages_and_registries/settings/project/components/container_protection_rule_form.vue';
 import ContainerProtectionRules from '~/packages_and_registries/settings/project/components/container_protection_rules.vue';
 import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
 import ContainerProtectionRuleQuery from '~/packages_and_registries/settings/project/graphql/queries/get_container_protection_rules.query.graphql';
-import { containerProtectionRulesData, containerProtectionRuleQueryPayload } from '../mock_data';
+import deleteContainerProtectionRuleMutation from '~/packages_and_registries/settings/project/graphql/mutations/delete_container_protection_rule.mutation.graphql';
+import {
+  containerProtectionRulesData,
+  containerProtectionRuleQueryPayload,
+  deleteContainerProtectionRuleMutationPayload,
+} from '../mock_data';
 
 Vue.use(VueApollo);
 
@@ -26,12 +32,15 @@ describe('Container protection rules project settings', () => {
   const findTable = () =>
     extendedWrapper(wrapper.findByRole('table', { name: /protected containers/i }));
   const findTableBody = () => extendedWrapper(findTable().findAllByRole('rowgroup').at(1));
-  const findTableRow = (i) => extendedWrapper(findTableBody().findAllByRole('row').at(i));
   const findTableLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+  const findTableRow = (i) => extendedWrapper(findTableBody().findAllByRole('row').at(i));
+  const findTableRowButtonDelete = (i) =>
+    findTableRow(i).findByRole('button', { name: /delete rule/i });
   const findAddProtectionRuleForm = () => wrapper.findComponent(ContainerProtectionRuleForm);
   const findAddProtectionRuleFormSubmitButton = () =>
     wrapper.findByRole('button', { name: /add protection rule/i });
   const findAlert = () => wrapper.findByRole('alert');
+  const findModal = () => wrapper.findComponent(GlModal);
 
   const mountComponent = (mountFn = mountExtended, provide = defaultProvidedValues, config) => {
     wrapper = mountFn(ContainerProtectionRules, {
@@ -53,9 +62,15 @@ describe('Container protection rules project settings', () => {
     containerProtectionRuleQueryResolver = jest
       .fn()
       .mockResolvedValue(containerProtectionRuleQueryPayload()),
+    deleteContainerProtectionRuleMutationResolver = jest
+      .fn()
+      .mockResolvedValue(deleteContainerProtectionRuleMutationPayload()),
     config = {},
   } = {}) => {
-    const requestHandlers = [[ContainerProtectionRuleQuery, containerProtectionRuleQueryResolver]];
+    const requestHandlers = [
+      [ContainerProtectionRuleQuery, containerProtectionRuleQueryResolver],
+      [deleteContainerProtectionRuleMutation, deleteContainerProtectionRuleMutationResolver],
+    ];
 
     fakeApollo = createMockApollo(requestHandlers);
 
@@ -77,7 +92,7 @@ describe('Container protection rules project settings', () => {
   describe('table "container protection rules"', () => {
     const findTableRowCell = (i, j) => findTableRow(i).findAllByRole('cell').at(j);
 
-    it('renders table with Container protection rules', async () => {
+    it('renders table with container protection rules', async () => {
       createComponent();
 
       await waitForPromises();
@@ -86,7 +101,6 @@ describe('Container protection rules project settings', () => {
 
       containerProtectionRuleQueryPayload().data.project.containerRegistryProtectionRules.nodes.forEach(
         (protectionRule, i) => {
-          expect(findTableRow(i).findAllByRole('cell').length).toBe(3);
           expect(findTableRowCell(i, 0).text()).toBe(protectionRule.repositoryPathPattern);
           expect(findTableRowCell(i, 1).text()).toBe('Maintainer');
           expect(findTableRowCell(i, 2).text()).toBe('Maintainer');
@@ -255,6 +269,158 @@ describe('Container protection rules project settings', () => {
         });
       });
     });
+
+    describe('column "rowActions"', () => {
+      describe('button "Delete"', () => {
+        it('exists in table', async () => {
+          createComponent();
+
+          await waitForPromises();
+
+          expect(findTableRowButtonDelete(0).exists()).toBe(true);
+        });
+
+        describe('when button is clicked', () => {
+          it('renders the "delete container protection rule" confirmation modal', async () => {
+            createComponent();
+
+            await waitForPromises();
+
+            const modalId = getBinding(findTableRowButtonDelete(0).element, 'gl-modal');
+
+            expect(findModal().props('modal-id')).toBe(modalId);
+            expect(findModal().props('title')).toBe(
+              'Are you sure you want to delete the container protection rule?',
+            );
+            expect(findModal().text()).toBe(
+              'Users with at least the Developer role for this project will be able to push and delete container images.',
+            );
+          });
+        });
+      });
+    });
+  });
+
+  describe('modal "confirmation for delete action"', () => {
+    const createComponentAndClickButtonDeleteInTableRow = async ({
+      tableRowIndex = 0,
+      deleteContainerProtectionRuleMutationResolver = jest
+        .fn()
+        .mockResolvedValue(deleteContainerProtectionRuleMutationPayload()),
+    } = {}) => {
+      createComponent({ deleteContainerProtectionRuleMutationResolver });
+
+      await waitForPromises();
+
+      findTableRowButtonDelete(tableRowIndex).trigger('click');
+    };
+
+    describe('when modal button "primary" clicked', () => {
+      const clickOnModalPrimaryBtn = () => findModal().vm.$emit('primary');
+
+      it('disables the button when graphql mutation is executed', async () => {
+        await createComponentAndClickButtonDeleteInTableRow();
+
+        await clickOnModalPrimaryBtn();
+
+        expect(findTableRowButtonDelete(0).props().disabled).toBe(true);
+
+        expect(findTableRowButtonDelete(1).props().disabled).toBe(false);
+      });
+
+      it('sends graphql mutation', async () => {
+        const deleteContainerProtectionRuleMutationResolver = jest
+          .fn()
+          .mockResolvedValue(deleteContainerProtectionRuleMutationPayload());
+
+        await createComponentAndClickButtonDeleteInTableRow({
+          deleteContainerProtectionRuleMutationResolver,
+        });
+
+        await clickOnModalPrimaryBtn();
+
+        expect(deleteContainerProtectionRuleMutationResolver).toHaveBeenCalledTimes(1);
+        expect(deleteContainerProtectionRuleMutationResolver).toHaveBeenCalledWith({
+          input: { id: containerProtectionRulesData[0].id },
+        });
+      });
+
+      it('handles erroneous graphql mutation', async () => {
+        const alertErrorMessage = 'Client error message';
+        const deleteContainerProtectionRuleMutationResolver = jest
+          .fn()
+          .mockRejectedValue(new Error(alertErrorMessage));
+
+        await createComponentAndClickButtonDeleteInTableRow({
+          deleteContainerProtectionRuleMutationResolver,
+        });
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect(findAlert().isVisible()).toBe(true);
+        expect(findAlert().text()).toBe(alertErrorMessage);
+      });
+
+      it('handles graphql mutation with error response', async () => {
+        const alertErrorMessage = 'Server error message';
+        const deleteContainerProtectionRuleMutationResolver = jest.fn().mockResolvedValue(
+          deleteContainerProtectionRuleMutationPayload({
+            containerRegistryProtectionRule: null,
+            errors: [alertErrorMessage],
+          }),
+        );
+
+        await createComponentAndClickButtonDeleteInTableRow({
+          deleteContainerProtectionRuleMutationResolver,
+        });
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect(findAlert().isVisible()).toBe(true);
+        expect(findAlert().text()).toBe(alertErrorMessage);
+      });
+
+      it('refetches package protection rules after successful graphql mutation', async () => {
+        const deleteContainerProtectionRuleMutationResolver = jest
+          .fn()
+          .mockResolvedValue(deleteContainerProtectionRuleMutationPayload());
+
+        const containerProtectionRuleQueryResolver = jest
+          .fn()
+          .mockResolvedValue(containerProtectionRuleQueryPayload());
+
+        createComponent({
+          containerProtectionRuleQueryResolver,
+          deleteContainerProtectionRuleMutationResolver,
+        });
+
+        await waitForPromises();
+
+        expect(containerProtectionRuleQueryResolver).toHaveBeenCalledTimes(1);
+
+        await findTableRowButtonDelete(0).trigger('click');
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect(containerProtectionRuleQueryResolver).toHaveBeenCalledTimes(2);
+      });
+
+      it('shows a toast with success message', async () => {
+        await createComponentAndClickButtonDeleteInTableRow();
+
+        await clickOnModalPrimaryBtn();
+
+        await waitForPromises();
+
+        expect($toast.show).toHaveBeenCalledWith('Protection rule deleted.');
+      });
+    });
   });
 
   describe('button "Add protection rule"', () => {
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
index 4674b666c4d0669f8007426a60716c96ebf12563..8cd37bd7b938e2e426ec0653677a69a23d1a797d 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
@@ -224,3 +224,15 @@ export const createContainerProtectionRuleMutationInput = {
 export const createContainerProtectionRuleMutationPayloadErrors = [
   'Repository path pattern has already been taken',
 ];
+
+export const deleteContainerProtectionRuleMutationPayload = ({
+  containerRegistryProtectionRule = { ...containerProtectionRulesData[0] },
+  errors = [],
+} = {}) => ({
+  data: {
+    deleteContainerRegistryProtectionRule: {
+      containerRegistryProtectionRule,
+      errors,
+    },
+  },
+});