diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
index 4cc9cc190e813a2e4191601f3093fa0d0c044ea6..06af69ff250e1bdea526242fc5ce3b53ef14df79 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
@@ -6,16 +6,24 @@ import {
   SHOW_SETUP_SUCCESS_ALERT,
   UPDATE_SETTINGS_SUCCESS_MESSAGE,
 } from '~/packages_and_registries/settings/project/constants';
-import ContainerExpirationPolicy from './container_expiration_policy.vue';
-import PackagesCleanupPolicy from './packages_cleanup_policy.vue';
+import ContainerExpirationPolicy from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue';
+import PackagesCleanupPolicy from '~/packages_and_registries/settings/project/components/packages_cleanup_policy.vue';
 
 export default {
   components: {
     ContainerExpirationPolicy,
+    DependencyProxyPackagesSettings: () =>
+      import(
+        'ee_component/packages_and_registries/settings/project/components/dependency_proxy_packages_settings.vue'
+      ),
     GlAlert,
     PackagesCleanupPolicy,
   },
-  inject: ['showContainerRegistrySettings', 'showPackageRegistrySettings'],
+  inject: [
+    'showContainerRegistrySettings',
+    'showPackageRegistrySettings',
+    'showDependencyProxySettings',
+  ],
   i18n: {
     UPDATE_SETTINGS_SUCCESS_MESSAGE,
   },
@@ -54,5 +62,6 @@ export default {
     </gl-alert>
     <packages-cleanup-policy v-if="showPackageRegistrySettings" />
     <container-expiration-policy v-if="showContainerRegistrySettings" />
+    <dependency-proxy-packages-settings v-if="showDependencyProxySettings" />
   </div>
 </template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
index 57c8d07e620418a6bc4900803844b67242e56844..326265430d957913cc9bbb3f9acf67266704c91b 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js
@@ -23,6 +23,7 @@ export default () => {
     helpPagePath,
     showContainerRegistrySettings,
     showPackageRegistrySettings,
+    showDependencyProxySettings,
   } = el.dataset;
   return new Vue({
     el,
@@ -40,6 +41,7 @@ export default () => {
       helpPagePath,
       showContainerRegistrySettings: parseBoolean(showContainerRegistrySettings),
       showPackageRegistrySettings: parseBoolean(showPackageRegistrySettings),
+      showDependencyProxySettings: parseBoolean(showDependencyProxySettings),
     },
     render(createElement) {
       return createElement('registry-settings-app', {});
diff --git a/ee/app/assets/javascripts/packages_and_registries/settings/project/components/dependency_proxy_packages_settings.vue b/ee/app/assets/javascripts/packages_and_registries/settings/project/components/dependency_proxy_packages_settings.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4d497855bfd6f2da6b52df2025f589a4bdac4add
--- /dev/null
+++ b/ee/app/assets/javascripts/packages_and_registries/settings/project/components/dependency_proxy_packages_settings.vue
@@ -0,0 +1,71 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import * as Sentry from '~/sentry/sentry_browser_wrapper';
+import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
+import getDependencyProxyPackagesSettings from 'ee_component/packages_and_registries/settings/project/graphql/queries/get_dependency_proxy_packages_settings.query.graphql';
+import DependencyProxyPackagesSettingsForm from 'ee_component/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form.vue';
+
+export default {
+  name: 'DependencyProxyPackagesSettings',
+  components: {
+    DependencyProxyPackagesSettingsForm,
+    GlAlert,
+    SettingsBlock,
+  },
+  inject: {
+    projectPath: {
+      default: '',
+    },
+  },
+  apollo: {
+    dependencyProxyPackagesSettings: {
+      query: getDependencyProxyPackagesSettings,
+      variables() {
+        return {
+          projectPath: this.projectPath,
+        };
+      },
+      update: (data) => data.project?.dependencyProxyPackagesSetting || {},
+      error(e) {
+        this.fetchSettingsError = e;
+        Sentry.captureException(e);
+      },
+    },
+  },
+  data() {
+    return {
+      dependencyProxyPackagesSettings: {},
+      fetchSettingsError: false,
+    };
+  },
+};
+</script>
+
+<template>
+  <settings-block>
+    <template #title>
+      <span data-testid="title">{{ s__('DependencyProxy|Dependency Proxy') }}</span></template
+    >
+    <template #description>
+      <span data-testid="description">
+        {{
+          s__(
+            'DependencyProxy|Enable the Dependency Proxy for packages, and configure connection settings for external registries.',
+          )
+        }}
+      </span>
+    </template>
+    <template #default>
+      <gl-alert v-if="fetchSettingsError" variant="warning" :dismissible="false">
+        {{
+          s__('DependencyProxy|Something went wrong while fetching the dependency proxy settings.')
+        }}
+      </gl-alert>
+      <dependency-proxy-packages-settings-form
+        v-else
+        v-model="dependencyProxyPackagesSettings"
+        :is-loading="$apollo.queries.dependencyProxyPackagesSettings.loading"
+      />
+    </template>
+  </settings-block>
+</template>
diff --git a/ee/app/assets/javascripts/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form.vue b/ee/app/assets/javascripts/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6c7d2268afd5b03c571a11c6d4518b42dd005550
--- /dev/null
+++ b/ee/app/assets/javascripts/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form.vue
@@ -0,0 +1,65 @@
+<script>
+import { GlFormGroup, GlFormInput, GlSkeletonLoader, GlToggle } from '@gitlab/ui';
+
+export default {
+  name: 'DependencyProxyPackagesSettingsForm',
+  components: {
+    GlFormGroup,
+    GlFormInput,
+    GlSkeletonLoader,
+    GlToggle,
+  },
+  props: {
+    value: {
+      type: Object,
+      required: true,
+    },
+    isLoading: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      mavenExternalRegistryPassword: '',
+    };
+  },
+  computed: {
+    prefilledForm() {
+      return {
+        ...this.value,
+      };
+    },
+  },
+};
+</script>
+
+<template>
+  <gl-skeleton-loader v-if="isLoading" />
+  <form v-else>
+    <gl-toggle
+      v-model="prefilledForm.enabled"
+      :label="s__('DependencyProxy|Enable Dependency Proxy')"
+    />
+    <h5 class="gl-mt-6">{{ s__('PackageRegistry|Maven') }}</h5>
+    <gl-form-group
+      :label="__('URL')"
+      :description="s__('DependencyProxy|Base URL of the external registry.')"
+    >
+      <gl-form-input v-model="prefilledForm.mavenExternalRegistryUrl" width="xl" />
+    </gl-form-group>
+    <gl-form-group
+      :label="__('Username')"
+      :description="s__('DependencyProxy|Username of the external registry.')"
+    >
+      <gl-form-input v-model="prefilledForm.mavenExternalRegistryUsername" width="xl" />
+    </gl-form-group>
+    <gl-form-group
+      :label="__('Password')"
+      :description="s__('DependencyProxy|Password for your external registry.')"
+    >
+      <gl-form-input v-model="mavenExternalRegistryPassword" width="xl" type="password" />
+    </gl-form-group>
+  </form>
+</template>
diff --git a/ee/app/assets/javascripts/packages_and_registries/settings/project/graphql/fragments/dependency_proxy_packages_settings.fragment.graphql b/ee/app/assets/javascripts/packages_and_registries/settings/project/graphql/fragments/dependency_proxy_packages_settings.fragment.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..0036587d743dac15c07af2fa34ba6d04efa1def8
--- /dev/null
+++ b/ee/app/assets/javascripts/packages_and_registries/settings/project/graphql/fragments/dependency_proxy_packages_settings.fragment.graphql
@@ -0,0 +1,5 @@
+fragment DependencyProxyPackagesSettingFields on DependencyProxyPackagesSetting {
+  enabled
+  mavenExternalRegistryUrl
+  mavenExternalRegistryUsername
+}
diff --git a/ee/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_dependency_proxy_packages_settings.query.graphql b/ee/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_dependency_proxy_packages_settings.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..d718805dbe4a95efc1ae1ed0a885f306bc135de4
--- /dev/null
+++ b/ee/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_dependency_proxy_packages_settings.query.graphql
@@ -0,0 +1,10 @@
+#import "../fragments/dependency_proxy_packages_settings.fragment.graphql"
+
+query getDependencyProxyPackagesSettings($projectPath: ID!) {
+  project(fullPath: $projectPath) {
+    id
+    dependencyProxyPackagesSetting {
+      ...DependencyProxyPackagesSettingFields
+    }
+  }
+}
diff --git a/ee/spec/frontend/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form_spec.js b/ee/spec/frontend/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..aeef4742bdc2d9d3cbdeb9053110014896619e3a
--- /dev/null
+++ b/ee/spec/frontend/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form_spec.js
@@ -0,0 +1,100 @@
+import { GlFormGroup, GlFormInput, GlSkeletonLoader, GlToggle } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import DependencyProxyPackagesSettingsForm from 'ee_component/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form.vue';
+import { dependencyProxyPackagesSettingsPayload } from '../mock_data';
+
+describe('Dependency proxy packages settings form', () => {
+  let wrapper;
+
+  const {
+    data: {
+      project: { dependencyProxyPackagesSetting },
+    },
+  } = dependencyProxyPackagesSettingsPayload();
+
+  const defaultProps = {
+    value: { ...dependencyProxyPackagesSetting },
+  };
+
+  const findForm = () => wrapper.find('form');
+  const findEnableProxyToggle = () => wrapper.findComponent(GlToggle);
+  const findLoader = () => wrapper.findComponent(GlSkeletonLoader);
+
+  const mountComponent = ({ props = defaultProps } = {}) => {
+    wrapper = shallowMountExtended(DependencyProxyPackagesSettingsForm, {
+      propsData: { ...props },
+    });
+  };
+
+  describe('form', () => {
+    it('is hidden when isLoading is set to true', () => {
+      mountComponent({
+        props: {
+          ...defaultProps,
+          isLoading: true,
+        },
+      });
+
+      expect(findForm().exists()).toBe(false);
+      expect(findLoader().exists()).toBe(true);
+    });
+
+    it('is visible when isLoading is set to false', () => {
+      mountComponent();
+
+      expect(findForm().exists()).toBe(true);
+      expect(findLoader().exists()).toBe(false);
+    });
+  });
+
+  describe('enable proxy toggle', () => {
+    it('when enabled', () => {
+      mountComponent();
+
+      expect(findEnableProxyToggle().props()).toMatchObject({
+        label: 'Enable Dependency Proxy',
+        value: true,
+      });
+    });
+
+    it('when disabled', () => {
+      mountComponent({
+        props: {
+          value: {
+            ...defaultProps.value,
+            enabled: false,
+          },
+        },
+      });
+
+      expect(findEnableProxyToggle().props('value')).toBe(false);
+    });
+  });
+
+  describe('maven registry', () => {
+    it('renders header', () => {
+      mountComponent();
+
+      expect(wrapper.find('h5').text()).toBe('Maven');
+    });
+
+    it.each`
+      index | field         | description                               | value
+      ${0}  | ${'URL'}      | ${'Base URL of the external registry.'}   | ${defaultProps.value.mavenExternalRegistryUrl}
+      ${1}  | ${'Username'} | ${'Username of the external registry.'}   | ${defaultProps.value.mavenExternalRegistryUsername}
+      ${2}  | ${'Password'} | ${'Password for your external registry.'} | ${''}
+    `('renders $field', ({ index, field, description, value }) => {
+      mountComponent();
+
+      const formGroup = wrapper.findAllComponents(GlFormGroup).at(index);
+      const formInput = formGroup.findComponent(GlFormInput);
+
+      expect(formGroup.attributes()).toMatchObject({
+        label: field,
+        description,
+      });
+
+      expect(formInput.attributes('value')).toBe(value);
+    });
+  });
+});
diff --git a/ee/spec/frontend/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_spec.js b/ee/spec/frontend/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8cc5fdeb75ac64b230cceb617ad31ec8fcdffb2a
--- /dev/null
+++ b/ee/spec/frontend/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_spec.js
@@ -0,0 +1,99 @@
+import { GlAlert } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import DependencyProxyPackagesSettings from 'ee_component/packages_and_registries/settings/project/components/dependency_proxy_packages_settings.vue';
+import DependencyProxyPackagesSettingsForm from 'ee_component/packages_and_registries/settings/project/components/dependency_proxy_packages_settings_form.vue';
+import dependencyProxyPackagesSettingsQuery from 'ee_component/packages_and_registries/settings/project/graphql/queries/get_dependency_proxy_packages_settings.query.graphql';
+import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
+
+import {
+  dependencyProxyPackagesSettingsPayload,
+  dependencyProxyPackagesSettingsData,
+} from '../mock_data';
+
+Vue.use(VueApollo);
+
+describe('Dependency proxy packages project settings', () => {
+  let wrapper;
+  let fakeApollo;
+
+  const defaultProvidedValues = {
+    projectPath: 'path',
+  };
+
+  const findAlert = () => wrapper.findComponent(GlAlert);
+  const findFormComponent = () => wrapper.findComponent(DependencyProxyPackagesSettingsForm);
+  const findTitle = () => wrapper.findByTestId('title');
+  const findDescription = () => wrapper.findByTestId('description');
+  const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
+
+  const mountComponent = (provide = defaultProvidedValues, config) => {
+    wrapper = shallowMountExtended(DependencyProxyPackagesSettings, {
+      stubs: {
+        SettingsBlock,
+      },
+      provide,
+      ...config,
+    });
+  };
+
+  const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => {
+    const requestHandlers = [[dependencyProxyPackagesSettingsQuery, resolver]];
+
+    fakeApollo = createMockApollo(requestHandlers);
+    mountComponent(provide, {
+      apolloProvider: fakeApollo,
+    });
+  };
+
+  afterEach(() => {
+    fakeApollo = null;
+  });
+
+  it('renders settings block component', () => {
+    mountComponentWithApollo();
+
+    expect(findSettingsBlock().exists()).toBe(true);
+  });
+
+  it('has the correct header text and description', () => {
+    mountComponentWithApollo();
+
+    expect(findTitle().text()).toBe('Dependency Proxy');
+    expect(findDescription().text()).toBe(
+      'Enable the Dependency Proxy for packages, and configure connection settings for external registries.',
+    );
+  });
+
+  it('renders the setting form', async () => {
+    mountComponentWithApollo({
+      resolver: jest.fn().mockResolvedValue(dependencyProxyPackagesSettingsPayload()),
+    });
+    await waitForPromises();
+
+    expect(findFormComponent().exists()).toBe(true);
+    expect(findFormComponent().props('value')).toEqual(dependencyProxyPackagesSettingsData);
+  });
+
+  describe('fetchSettingsError', () => {
+    beforeEach(async () => {
+      mountComponentWithApollo({
+        resolver: jest.fn().mockRejectedValue(new Error('GraphQL error')),
+      });
+      await waitForPromises();
+    });
+
+    it('the form is hidden', () => {
+      expect(findFormComponent().exists()).toBe(false);
+    });
+
+    it('shows an alert', () => {
+      expect(findAlert().html()).toContain(
+        'Something went wrong while fetching the dependency proxy settings.',
+      );
+    });
+  });
+});
diff --git a/ee/spec/frontend/packages_and_registries/settings/project/mock_data.js b/ee/spec/frontend/packages_and_registries/settings/project/mock_data.js
new file mode 100644
index 0000000000000000000000000000000000000000..42c7cd5c7da645536371f547f936f9d60f26224a
--- /dev/null
+++ b/ee/spec/frontend/packages_and_registries/settings/project/mock_data.js
@@ -0,0 +1,18 @@
+export const dependencyProxyPackagesSettingsData = {
+  __typename: 'DependencyProxyPackagesSetting',
+  enabled: true,
+  mavenExternalRegistryUrl: 'https://test.dev',
+  mavenExternalRegistryUsername: 'user1',
+};
+
+export const dependencyProxyPackagesSettingsPayload = (override) => ({
+  data: {
+    project: {
+      id: '1',
+      dependencyProxyPackagesSetting: {
+        ...dependencyProxyPackagesSettingsData,
+        ...override,
+      },
+    },
+  },
+});
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a616fc197dfade2e3bc0af5b607c2fb7dc8f91d0..287df86a49b6d2e8a3d05dbdacb3eac6ef74942b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16559,6 +16559,9 @@ msgstr ""
 msgid "DependencyProxy|All items in the cache are scheduled for removal."
 msgstr ""
 
+msgid "DependencyProxy|Base URL of the external registry."
+msgstr ""
+
 msgid "DependencyProxy|Cached %{time}"
 msgstr ""
 
@@ -16595,18 +16598,27 @@ msgstr ""
 msgid "DependencyProxy|Enable Dependency Proxy"
 msgstr ""
 
+msgid "DependencyProxy|Enable the Dependency Proxy for packages, and configure connection settings for external registries."
+msgstr ""
+
 msgid "DependencyProxy|Enable the Dependency Proxy to cache container images from Docker Hub and automatically clear the cache."
 msgstr ""
 
 msgid "DependencyProxy|Image list"
 msgstr ""
 
+msgid "DependencyProxy|Password for your external registry."
+msgstr ""
+
 msgid "DependencyProxy|Pull image by digest example"
 msgstr ""
 
 msgid "DependencyProxy|Scheduled for deletion"
 msgstr ""
 
+msgid "DependencyProxy|Something went wrong while fetching the dependency proxy settings."
+msgstr ""
+
 msgid "DependencyProxy|There are no images in the cache"
 msgstr ""
 
@@ -16616,6 +16628,9 @@ msgstr ""
 msgid "DependencyProxy|To store docker images in Dependency Proxy cache, pull an image by tag in your %{codeStart}.gitlab-ci.yml%{codeEnd} file. In this example, the image is %{codeStart}alpine:latest%{codeEnd}"
 msgstr ""
 
+msgid "DependencyProxy|Username of the external registry."
+msgstr ""
+
 msgid "DependencyProxy|When enabled, images older than 90 days will be removed from the cache."
 msgstr ""
 
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
index 1242590945427a7a229752a6ff2584f910203155..dfcabd14489f1ceb6151cb650bc32ea1343e5b2d 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
@@ -6,6 +6,7 @@ import * as commonUtils from '~/lib/utils/common_utils';
 import component from '~/packages_and_registries/settings/project/components/registry_settings_app.vue';
 import ContainerExpirationPolicy from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue';
 import PackagesCleanupPolicy from '~/packages_and_registries/settings/project/components/packages_cleanup_policy.vue';
+import DependencyProxyPackagesSettings from 'ee_component/packages_and_registries/settings/project/components/dependency_proxy_packages_settings.vue';
 import {
   SHOW_SETUP_SUCCESS_ALERT,
   UPDATE_SETTINGS_SUCCESS_MESSAGE,
@@ -18,11 +19,16 @@ describe('Registry Settings app', () => {
 
   const findContainerExpirationPolicy = () => wrapper.findComponent(ContainerExpirationPolicy);
   const findPackagesCleanupPolicy = () => wrapper.findComponent(PackagesCleanupPolicy);
+  const findDependencyProxyPackagesSettings = () =>
+    wrapper.findComponent(DependencyProxyPackagesSettings);
   const findAlert = () => wrapper.findComponent(GlAlert);
 
   const defaultProvide = {
+    projectPath: 'path',
     showContainerRegistrySettings: true,
     showPackageRegistrySettings: true,
+    showDependencyProxySettings: false,
+    ...(IS_EE && { showDependencyProxySettings: true }),
   };
 
   const mountComponent = (provide = defaultProvide) => {
@@ -82,6 +88,7 @@ describe('Registry Settings app', () => {
       'container cleanup policy $showContainerRegistrySettings and package cleanup policy is $showPackageRegistrySettings',
       ({ showContainerRegistrySettings, showPackageRegistrySettings }) => {
         mountComponent({
+          ...defaultProvide,
           showContainerRegistrySettings,
           showPackageRegistrySettings,
         });
@@ -90,5 +97,16 @@ describe('Registry Settings app', () => {
         expect(findPackagesCleanupPolicy().exists()).toBe(showPackageRegistrySettings);
       },
     );
+
+    if (IS_EE) {
+      it.each([true, false])('when showDependencyProxySettings is %s', (value) => {
+        mountComponent({
+          ...defaultProvide,
+          showDependencyProxySettings: value,
+        });
+
+        expect(findDependencyProxyPackagesSettings().exists()).toBe(value);
+      });
+    }
   });
 });