diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue
index 85dfa12c7566c900ca488aabbc55e043df23e3f2..f494a3bd224f23d80e408103c668a4d04df277c6 100644
--- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue
+++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue
@@ -1,11 +1,12 @@
 <script>
-import { GlLoadingIcon, GlTableLite } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon, GlTableLite } from '@gitlab/ui';
 import { createAlert } from '~/alert';
 import { __, s__ } from '~/locale';
 import getCiCatalogResourceComponents from '../../graphql/queries/get_ci_catalog_resource_components.query.graphql';
 
 export default {
   components: {
+    GlButton,
     GlLoadingIcon,
     GlTableLite,
   },
@@ -70,6 +71,8 @@ export default {
     },
   ],
   i18n: {
+    copyText: __('Copy value'),
+    copyAriaText: __('Copy to clipboard'),
     inputTitle: s__('CiCatalogComponent|Inputs'),
     fetchError: s__("CiCatalogComponent|There was an error fetching this resource's components"),
   },
@@ -88,7 +91,24 @@ export default {
       >
         <h3 class="gl-font-size-h2" data-testid="component-name">{{ component.name }}</h3>
         <p class="gl-mt-5">{{ component.description }}</p>
-        <pre class="gl-w-85p gl-py-4">{{ generateSnippet(component.path) }}</pre>
+        <div class="gl-display-flex">
+          <pre
+            class="gl-w-85p gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0 gl-border-r-none"
+          ><span>{{ generateSnippet(component.path) }}</span>
+        </pre>
+          <div class="gl--flex-center gl-bg-gray-10 gl-border gl-border-l-none">
+            <gl-button
+              class="gl-p-4! gl-mr-3!"
+              category="tertiary"
+              icon="copy-to-clipboard"
+              size="small"
+              :title="$options.i18n.copyText"
+              :data-clipboard-text="generateSnippet(component.path)"
+              data-testid="copy-to-clipboard"
+              :aria-label="$options.i18n.copyAriaText"
+            />
+          </div>
+        </div>
         <div class="gl-mt-5">
           <b class="gl-display-block gl-mb-4"> {{ $options.i18n.inputTitle }}</b>
           <gl-table-lite :items="component.inputs.nodes" :fields="$options.fields">
diff --git a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js
index a41996d20b3af5974a73cf69bb65b14775e3c044..93f9f0b7e2307e285424a97c189c8a40a3014385 100644
--- a/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js
+++ b/spec/frontend/ci/catalog/components/details/ci_resource_components_spec.js
@@ -38,6 +38,7 @@ describe('CiResourceComponents', () => {
   };
 
   const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+  const findCopyToClipboardButton = (i) => wrapper.findAllByTestId('copy-to-clipboard').at(i);
   const findComponents = () => wrapper.findAllByTestId('component-section');
 
   beforeEach(() => {
@@ -98,6 +99,15 @@ describe('CiResourceComponents', () => {
       });
     });
 
+    it('adds a copy-to-clipboard button', () => {
+      components.forEach((component, i) => {
+        const button = findCopyToClipboardButton(i);
+
+        expect(button.props().icon).toBe('copy-to-clipboard');
+        expect(button.attributes('data-clipboard-text')).toContain(component.path);
+      });
+    });
+
     describe('inputs', () => {
       it('renders the component parameter attributes', () => {
         const [firstComponent] = components;