diff --git a/app/assets/javascripts/security_configuration/components/continous_container_registry_scan.vue b/app/assets/javascripts/security_configuration/components/continous_container_registry_scan.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7898d55f09164975020bbef87b25207328cfc068
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/components/continous_container_registry_scan.vue
@@ -0,0 +1,117 @@
+<script>
+import { GlToggle, GlLink, GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import SetContainerScanningForRegistry from '~/security_configuration/graphql/set_container_scanning_for_registry.graphql';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { __, s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+
+export default {
+  components: { GlToggle, GlLink, GlAlert, GlLoadingIcon },
+  mixins: [glFeatureFlagsMixin()],
+  inject: ['containerScanningForRegistryEnabled', 'projectFullPath'],
+  i18n: {
+    title: s__('CVS|Continuous Container Scanning'),
+    description: s__(
+      'CVS|Scan for vulnerabilities when a container image or the advisory database is updated.',
+    ),
+    learnMore: __('Learn more'),
+  },
+  props: {
+    feature: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      toggleValue: this.containerScanningForRegistryEnabled,
+      errorMessage: '',
+      isRunningMutation: false,
+    };
+  },
+  computed: {
+    isFeatureConfigured() {
+      return this.feature.available && this.feature.configured;
+    },
+  },
+  methods: {
+    reportError(error) {
+      this.errorMessage = error;
+    },
+    clearError() {
+      this.errorMessage = '';
+    },
+    async toggleCVS(checked) {
+      const oldValue = this.toggleValue;
+
+      try {
+        this.isRunningMutation = true;
+        this.toggleValue = checked;
+
+        this.clearError();
+
+        const { data } = await this.$apollo.mutate({
+          mutation: SetContainerScanningForRegistry,
+          variables: {
+            input: {
+              projectPath: this.projectFullPath,
+              enable: checked,
+            },
+          },
+        });
+
+        const { errors } = data.setContainerScanningForRegistry;
+
+        if (errors.length > 0) {
+          throw new Error(errors[0].message);
+        } else {
+          this.toggleValue =
+            data.setContainerScanningForRegistry.containerScanningForRegistryEnabled;
+        }
+      } catch (error) {
+        this.toggleValue = oldValue;
+        this.reportError(error);
+      } finally {
+        this.isRunningMutation = false;
+      }
+    },
+  },
+  CVSHelpPagePath: helpPagePath(
+    'user/application_security/continuous_vulnerability_scanning/index',
+  ),
+};
+</script>
+
+<template>
+  <div v-if="glFeatures.containerScanningForRegistry">
+    <h4 class="gl-font-base gl-mt-6">
+      {{ $options.i18n.title }}
+    </h4>
+    <gl-alert
+      v-if="errorMessage"
+      class="gl-mb-5 gl-mt-2"
+      variant="danger"
+      @dismiss="errorMessage = ''"
+      >{{ errorMessage }}</gl-alert
+    >
+
+    <div class="gl-display-flex gl-align-items-center">
+      <gl-toggle
+        :disabled="!isFeatureConfigured || isRunningMutation"
+        :value="toggleValue"
+        :label="s__('CVS|Toggle CVS')"
+        label-position="hidden"
+        @change="toggleCVS"
+      />
+      <gl-loading-icon v-if="isRunningMutation" inline class="gl-ml-3" />
+    </div>
+
+    <p class="gl-mb-0 gl-mt-5">
+      {{ $options.i18n.description }}
+      <gl-link :href="$options.CVSHelpPagePath" target="_blank">{{
+        $options.i18n.learnMore
+      }}</gl-link>
+      <br />
+    </p>
+  </div>
+</template>
diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue
index 2100da78219a54d55c9c74d715fd9153d3642a19..74736bfb62276e0976ff2b91f8e6746835f3b62e 100644
--- a/app/assets/javascripts/security_configuration/components/feature_card.vue
+++ b/app/assets/javascripts/security_configuration/components/feature_card.vue
@@ -218,5 +218,7 @@ export default {
         {{ $options.i18n.configurationGuide }}
       </gl-button>
     </div>
+
+    <component :is="feature.slotComponent" v-if="feature.slotComponent" :feature="feature" />
   </gl-card>
 </template>
diff --git a/app/assets/javascripts/security_configuration/constants.js b/app/assets/javascripts/security_configuration/constants.js
index f8ab8b4685a1deab4303c5337196b760f7accaf1..46dbc2a1c93d4271097185391cbf1782fcf8a5b4 100644
--- a/app/assets/javascripts/security_configuration/constants.js
+++ b/app/assets/javascripts/security_configuration/constants.js
@@ -8,12 +8,15 @@ import {
   REPORT_TYPE_SAST,
   REPORT_TYPE_SAST_IAC,
   REPORT_TYPE_SECRET_DETECTION,
+  REPORT_TYPE_CONTAINER_SCANNING,
 } from '~/vue_shared/security_reports/constants';
 
 import configureSastMutation from './graphql/configure_sast.mutation.graphql';
 import configureSastIacMutation from './graphql/configure_iac.mutation.graphql';
 import configureSecretDetectionMutation from './graphql/configure_secret_detection.mutation.graphql';
 
+import ContinuousContainerRegistryScan from './components/continous_container_registry_scan.vue';
+
 /**
  * Translations for Security Configuration Page
  * Make sure to add new scanner translations to the SCANNER_NAMES_MAP below.
@@ -61,6 +64,12 @@ export const SCANNER_NAMES_MAP = {
   GENERIC: s__('ciReport|Manually added'),
 };
 
+export const securityFeatures = {
+  [REPORT_TYPE_CONTAINER_SCANNING]: {
+    slotComponent: ContinuousContainerRegistryScan,
+  },
+};
+
 export const featureToMutationMap = {
   [REPORT_TYPE_SAST]: {
     mutationId: 'configureSast',
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index 40c826613050309ddb08cf1f36f6602e8113cdcd..54de63bc8030e808cacfe44b2d616af860f802d0 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -4,6 +4,7 @@ import createDefaultClient from '~/lib/graphql';
 import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
 import SecurityConfigurationApp from './components/app.vue';
 import { augmentFeatures } from './utils';
+import { securityFeatures } from './constants';
 
 export const initSecurityConfiguration = (el) => {
   if (!el) {
@@ -25,9 +26,13 @@ export const initSecurityConfiguration = (el) => {
     autoDevopsHelpPagePath,
     autoDevopsPath,
     vulnerabilityTrainingDocsPath,
+    containerScanningForRegistryEnabled,
   } = el.dataset;
 
-  const { augmentedSecurityFeatures } = augmentFeatures(features ? JSON.parse(features) : []);
+  const { augmentedSecurityFeatures } = augmentFeatures(
+    securityFeatures,
+    features ? JSON.parse(features) : [],
+  );
 
   return new Vue({
     el,
@@ -39,6 +44,7 @@ export const initSecurityConfiguration = (el) => {
       autoDevopsHelpPagePath,
       autoDevopsPath,
       vulnerabilityTrainingDocsPath,
+      containerScanningForRegistryEnabled,
     },
     render(createElement) {
       return createElement(SecurityConfigurationApp, {
diff --git a/app/assets/javascripts/security_configuration/utils.js b/app/assets/javascripts/security_configuration/utils.js
index 23f86b30445ca3367bc5c374785933602271cf84..aa0f4bf92cd4831a3c8b368f1a45923ea33d57a0 100644
--- a/app/assets/javascripts/security_configuration/utils.js
+++ b/app/assets/javascripts/security_configuration/utils.js
@@ -10,10 +10,11 @@ import { REPORT_TYPE_DAST } from '~/vue_shared/security_reports/constants';
  * This function takes the nested securityFeatures config and flattens it to the top level object.
  * It then filters out any scanner features that lack a security config for rednering in the UI
  * @param [{}] features
+ * @param {Object} securityFeatures Object containing client side UI options
  * @returns {Object} Object with enriched features from constants divided into Security and Compliance Features
  */
 
-export const augmentFeatures = (features = []) => {
+export const augmentFeatures = (securityFeatures, features = []) => {
   const featuresByType = features.reduce((acc, feature) => {
     acc[feature.type] = convertObjectPropsToCamelCase(feature, { deep: true });
     return acc;
@@ -30,6 +31,7 @@ export const augmentFeatures = (features = []) => {
     const augmented = {
       ...feature,
       ...featuresByType[feature.type],
+      ...securityFeatures[feature.type],
     };
 
     // Secondary layer copies some values from the first layer
diff --git a/config/known_invalid_graphql_queries.yml b/config/known_invalid_graphql_queries.yml
index b9618558908896d98fdefc37f4344c8ec548d97e..579ee08383368b3d49af7b21948eecb92ba9d2f3 100644
--- a/config/known_invalid_graphql_queries.yml
+++ b/config/known_invalid_graphql_queries.yml
@@ -1,3 +1,4 @@
 ---
 filenames:
   - ee/app/assets/javascripts/oncall_schedules/graphql/mutations/update_oncall_schedule_rotation.mutation.graphql
+  - app/assets/javascripts/security_configuration/graphql/set_container_scanning_for_registry.graphql
\ No newline at end of file
diff --git a/ee/app/controllers/ee/projects/security/configuration_controller.rb b/ee/app/controllers/ee/projects/security/configuration_controller.rb
index f67e588944af96a65828b838885f8a9659a4483d..074d3ab6e21108f7113ba4cc61ab3142a4574551 100644
--- a/ee/app/controllers/ee/projects/security/configuration_controller.rb
+++ b/ee/app/controllers/ee/projects/security/configuration_controller.rb
@@ -12,6 +12,10 @@ module ConfigurationController
           before_action :ensure_security_dashboard_feature_enabled!, except: [:show]
           before_action :authorize_read_security_dashboard!, except: [:show]
 
+          before_action only: [:show] do
+            push_frontend_feature_flag(:container_scanning_for_registry)
+          end
+
           feature_category :static_application_security_testing, [:show]
 
           urgency :low, [:show]
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 128ffd4466c329d86c6d23f0928024c563fb8f5f..e9b915f784cc03885ce8c10cf251479cdd7fcdb6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9924,6 +9924,15 @@ msgstr ""
 msgid "CVE|Why Request a CVE ID?"
 msgstr ""
 
+msgid "CVS|Continuous Container Scanning"
+msgstr ""
+
+msgid "CVS|Scan for vulnerabilities when a container image or the advisory database is updated."
+msgstr ""
+
+msgid "CVS|Toggle CVS"
+msgstr ""
+
 msgid "Cadence is not automated"
 msgstr ""
 
diff --git a/spec/frontend/security_configuration/components/continuous_container_registry_scan_spec.js b/spec/frontend/security_configuration/components/continuous_container_registry_scan_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..046e0b16c40824c133e4564c1631d6d127b1c567
--- /dev/null
+++ b/spec/frontend/security_configuration/components/continuous_container_registry_scan_spec.js
@@ -0,0 +1,129 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlToggle } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import Vue from 'vue';
+import SetContainerScanningForRegistry from '~/security_configuration/graphql/set_container_scanning_for_registry.graphql';
+import ContinuousContainerRegistryScan from '~/security_configuration/components/continous_container_registry_scan.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+
+Vue.use(VueApollo);
+
+const getSetCVSMockResponse = (enabled = true) => ({
+  data: {
+    setContainerScanningForRegistry: {
+      containerScanningForRegistryEnabled: enabled,
+      errors: [],
+    },
+  },
+});
+
+const defaultProvide = {
+  containerScanningForRegistryEnabled: true,
+  projectFullPath: 'project/full/path',
+};
+
+describe('ContinuousContainerRegistryScan', () => {
+  let wrapper;
+  let apolloProvider;
+  let requestHandlers;
+
+  const createComponent = (options = {}) => {
+    requestHandlers = {
+      setCVSMutationHandler: jest.fn().mockResolvedValue(getSetCVSMockResponse(options.enabled)),
+    };
+
+    apolloProvider = createMockApollo([
+      [SetContainerScanningForRegistry, requestHandlers.setCVSMutationHandler],
+    ]);
+
+    wrapper = shallowMount(ContinuousContainerRegistryScan, {
+      propsData: {
+        feature: {
+          available: true,
+          configured: true,
+        },
+      },
+      provide: {
+        glFeatures: {
+          containerScanningForRegistry: true,
+        },
+        ...defaultProvide,
+      },
+      apolloProvider,
+      ...options,
+    });
+  };
+
+  beforeEach(() => {
+    createComponent();
+  });
+
+  afterEach(() => {
+    apolloProvider = null;
+  });
+
+  const findToggle = () => wrapper.findComponent(GlToggle);
+
+  it('renders the component', () => {
+    expect(wrapper.exists()).toBe(true);
+  });
+
+  it('renders the correct title', () => {
+    expect(wrapper.text()).toContain('Continuous Container Scanning');
+  });
+
+  it('renders the toggle component with correct values', () => {
+    expect(findToggle().exists()).toBe(true);
+    expect(findToggle().props('value')).toBe(defaultProvide.containerScanningForRegistryEnabled);
+  });
+
+  it('should disable toggle when feature is not configured', () => {
+    createComponent({
+      propsData: {
+        feature: {
+          available: true,
+          configured: false,
+        },
+      },
+    });
+    expect(findToggle().props('disabled')).toBe(true);
+  });
+
+  it.each([true, false])(
+    'calls mutation on toggle change with correct payload',
+    async (enabled) => {
+      createComponent({ enabled });
+
+      findToggle().vm.$emit('change', enabled);
+
+      expect(requestHandlers.setCVSMutationHandler).toHaveBeenCalledWith({
+        input: {
+          projectPath: 'project/full/path',
+          enable: enabled,
+        },
+      });
+
+      await waitForPromises();
+
+      expect(findToggle().props('value')).toBe(enabled);
+    },
+  );
+
+  describe('when feature flag is disabled', () => {
+    beforeEach(() => {
+      createComponent({
+        provide: {
+          glFeatures: {
+            containerScanningForRegistry: false,
+          },
+          ...defaultProvide,
+        },
+      });
+    });
+
+    it('should not render toggle', () => {
+      expect(findToggle().exists()).toBe(false);
+    });
+  });
+});
diff --git a/spec/frontend/security_configuration/components/feature_card_spec.js b/spec/frontend/security_configuration/components/feature_card_spec.js
index f1826e0e138e30e0646c8cf7ddf318e12e57dbd0..43135743c076ed6d41b205a54f1072e78ff56ff2 100644
--- a/spec/frontend/security_configuration/components/feature_card_spec.js
+++ b/spec/frontend/security_configuration/components/feature_card_spec.js
@@ -1,5 +1,6 @@
 import { GlIcon } from '@gitlab/ui';
 import { mount } from '@vue/test-utils';
+import Vue from 'vue';
 import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import { securityFeatures } from 'jest/security_configuration/mock_data';
 import FeatureCard from '~/security_configuration/components/feature_card.vue';
@@ -13,6 +14,10 @@ import {
 import { manageViaMRErrorMessage } from '../constants';
 import { makeFeature } from './utils';
 
+const MockComponent = Vue.component('MockComponent', {
+  render: (createElement) => createElement('span'),
+});
+
 describe('FeatureCard component', () => {
   let feature;
   let wrapper;
@@ -389,4 +394,17 @@ describe('FeatureCard component', () => {
       });
     });
   });
+
+  describe('when a slot component is passed', () => {
+    beforeEach(() => {
+      feature = makeFeature({
+        slotComponent: MockComponent,
+      });
+      createComponent({ feature });
+    });
+
+    it('renders the component properly', () => {
+      expect(wrapper.findComponent(MockComponent).exists()).toBe(true);
+    });
+  });
 });
diff --git a/spec/frontend/security_configuration/utils_spec.js b/spec/frontend/security_configuration/utils_spec.js
index f2eeaca89878a8c600f27d8c318e000f214af829..f1e2933d96b507468a41caf70da18ba2786b92d9 100644
--- a/spec/frontend/security_configuration/utils_spec.js
+++ b/spec/frontend/security_configuration/utils_spec.js
@@ -1,5 +1,5 @@
 import { augmentFeatures, translateScannerNames } from '~/security_configuration/utils';
-import { SCANNER_NAMES_MAP } from '~/security_configuration/constants';
+import { SCANNER_NAMES_MAP, securityFeatures } from '~/security_configuration/constants';
 
 describe('augmentFeatures', () => {
   const mockSecurityFeatures = [
@@ -12,6 +12,16 @@ describe('augmentFeatures', () => {
     },
   ];
 
+  const mockSecurityFeaturesWithSlot = [
+    {
+      name: 'CONTAINER_REGISTRY',
+      type: 'CONTAINER_REGISTRY',
+      security_features: {
+        type: 'CONTAINER_REGISTRY',
+      },
+    },
+  ];
+
   const expectedMockSecurityFeatures = [
     {
       name: 'SAST',
@@ -22,6 +32,16 @@ describe('augmentFeatures', () => {
     },
   ];
 
+  const expectedMockSecurityWithSlotFeatures = [
+    {
+      name: 'CONTAINER_REGISTRY',
+      type: 'CONTAINER_REGISTRY',
+      securityFeatures: {
+        type: 'CONTAINER_REGISTRY',
+      },
+    },
+  ];
+
   const expectedInvalidMockSecurityFeatures = [
     {
       foo: 'bar',
@@ -129,6 +149,10 @@ describe('augmentFeatures', () => {
     augmentedSecurityFeatures: expectedMockSecurityFeatures,
   };
 
+  const expectedOutputWithSlot = {
+    augmentedSecurityFeatures: expectedMockSecurityWithSlotFeatures,
+  };
+
   const expectedInvalidOutputDefault = {
     augmentedSecurityFeatures: expectedInvalidMockSecurityFeatures,
   };
@@ -172,32 +196,48 @@ describe('augmentFeatures', () => {
 
   describe('returns an object with augmentedSecurityFeatures  when', () => {
     it('given an properly formatted array', () => {
-      expect(augmentFeatures(mockSecurityFeatures)).toEqual(expectedOutputDefault);
+      expect(augmentFeatures(securityFeatures, mockSecurityFeatures)).toEqual(
+        expectedOutputDefault,
+      );
     });
 
     it('given an invalid populated array', () => {
       expect(
-        augmentFeatures([{ ...mockSecurityFeatures[0], ...mockInvalidCustomFeature[0] }]),
+        augmentFeatures(securityFeatures, [
+          { ...mockSecurityFeatures[0], ...mockInvalidCustomFeature[0] },
+        ]),
       ).toEqual(expectedInvalidOutputDefault);
     });
 
     it('features have secondary key', () => {
       expect(
-        augmentFeatures([{ ...mockSecurityFeatures[0], ...mockFeaturesWithSecondary[0] }]),
+        augmentFeatures(securityFeatures, [
+          { ...mockSecurityFeatures[0], ...mockFeaturesWithSecondary[0] },
+        ]),
       ).toEqual(expectedOutputSecondary);
     });
 
     it('given a valid populated array', () => {
       expect(
-        augmentFeatures([{ ...mockSecurityFeatures[0], ...mockValidCustomFeature[0] }]),
+        augmentFeatures(securityFeatures, [
+          { ...mockSecurityFeatures[0], ...mockValidCustomFeature[0] },
+        ]),
       ).toEqual(expectedOutputCustomFeature);
     });
+
+    it('when a custom vue slot is defined', () => {
+      expect(augmentFeatures(securityFeatures, mockSecurityFeaturesWithSlot)).toEqual(
+        expectedOutputWithSlot,
+      );
+    });
   });
 
   describe('returns an object with camelcased keys', () => {
     it('given a customfeature in snakecase', () => {
       expect(
-        augmentFeatures([{ ...mockSecurityFeatures[0], ...mockValidCustomFeatureSnakeCase[0] }]),
+        augmentFeatures(securityFeatures, [
+          { ...mockSecurityFeatures[0], ...mockValidCustomFeatureSnakeCase[0] },
+        ]),
       ).toEqual(expectedOutputCustomFeature);
     });
   });
@@ -205,7 +245,7 @@ describe('augmentFeatures', () => {
   describe('follows onDemandAvailable', () => {
     it('deletes badge when false', () => {
       expect(
-        augmentFeatures([
+        augmentFeatures(securityFeatures, [
           {
             ...mockSecurityFeaturesDast[0],
             ...mockValidCustomFeatureWithOnDemandAvailableFalse[0],
@@ -216,7 +256,7 @@ describe('augmentFeatures', () => {
 
     it('keeps badge when true', () => {
       expect(
-        augmentFeatures([
+        augmentFeatures(securityFeatures, [
           { ...mockSecurityFeaturesDast[0], ...mockValidCustomFeatureWithOnDemandAvailableTrue[0] },
         ]),
       ).toEqual(expectedOutputCustomFeatureWithOnDemandAvailableTrue);