diff --git a/ansible/inventory/group_vars/all/apt b/ansible/inventory/group_vars/all/apt
index 80218456c..89c0999f3 100644
--- a/ansible/inventory/group_vars/all/apt
+++ b/ansible/inventory/group_vars/all/apt
@@ -41,6 +41,14 @@ apt_keys: []
 # Default is an empty list.
 apt_repositories: []
 
+# List of Apt preferences options. Each item is a dict with the following
+# keys:
+# * content: free-form preferences file content
+# * filename: name of a file in /etc/apt/preferences.d/ in which to write
+#   the configuration
+# Default is an empty list.
+apt_preferences: []
+
 # Whether to disable repositories in /etc/apt/sources.list. This may be used
 # when replacing the distribution repositories via apt_repositories.
 # Default is false.
diff --git a/ansible/roles/apt/defaults/main.yml b/ansible/roles/apt/defaults/main.yml
index b20e2a170..3eb4eed74 100644
--- a/ansible/roles/apt/defaults/main.yml
+++ b/ansible/roles/apt/defaults/main.yml
@@ -44,6 +44,14 @@ apt_keys: []
 # Default is an empty list.
 apt_repositories: []
 
+# List of Apt preferences options. Each item is a dict with the following
+# keys:
+# * content: free-form preferences file content
+# * filename: name of a file in /etc/apt/preferences.d/ in which to write
+#   the configuration
+# Default is an empty list.
+apt_preferences: []
+
 # Whether to disable repositories in /etc/apt/sources.list. This may be used
 # when replacing the distribution repositories via apt_repositories.
 # Default is false.
diff --git a/ansible/roles/apt/tasks/main.yml b/ansible/roles/apt/tasks/main.yml
index 7bdf2cf01..bb1822d23 100644
--- a/ansible/roles/apt/tasks/main.yml
+++ b/ansible/roles/apt/tasks/main.yml
@@ -7,4 +7,6 @@
 
 - import_tasks: repos.yml
 
+- import_tasks: preferences.yml
+
 - import_tasks: auth.yml
diff --git a/ansible/roles/apt/tasks/preferences.yml b/ansible/roles/apt/tasks/preferences.yml
new file mode 100644
index 000000000..b401245da
--- /dev/null
+++ b/ansible/roles/apt/tasks/preferences.yml
@@ -0,0 +1,12 @@
+---
+- name: Ensure Apt preferences are configured
+  copy:
+    content: "{{ item.content }}"
+    dest: "/etc/apt/preferences.d/{{ item.filename }}"
+    owner: root
+    group: root
+    mode: 0664
+  loop: "{{ apt_preferences }}"
+  loop_control:
+    label: "{{ item.filename }}"
+  become: true
diff --git a/doc/source/configuration/reference/hosts.rst b/doc/source/configuration/reference/hosts.rst
index e69de543d..72191a946 100644
--- a/doc/source/configuration/reference/hosts.rst
+++ b/doc/source/configuration/reference/hosts.rst
@@ -351,6 +351,7 @@ For example, the following configuration tells Apt to use 2 attempts when
 downloading packages:
 
 .. code-block:: yaml
+   :caption: ``apt.yml``
 
    apt_config:
      - content: |
@@ -444,6 +445,37 @@ that is signed by the key.
        components: all
        signed_by: example-key.asc
 
+Apt preferences
+---------------
+
+Arbitrary global preferences options for Apt may be defined via the
+``apt_preferences`` variable in ``etc/kayobe/apt.yml``. The format is a list,
+with each item mapping to a dict/map with the following items:
+
+* ``content``: free-form preferences file content
+* ``filename``: name of a file in ``/etc/apt/preferences.d/`` in which to
+  write the configuration
+
+The default of ``apt_preferences`` is an empty list.
+
+For example, the following configuration tells Apt to only pin a specific
+package from a custom repo, while preventing installing any other packages from
+there:
+
+.. code-block:: yaml
+   :caption: ``apt.yml``
+
+   apt_preferences:
+     - content: |
+         Package: *
+         Pin: origin your.custom.repo
+         Pin-Priority: 1
+
+         Package: specific-package
+         Pin: origin your.custom.repo
+         Pin-Priority: 500
+       filename: 99-pin-custom-repo
+
 Apt auth configuration
 ----------------------
 
diff --git a/etc/kayobe/apt.yml b/etc/kayobe/apt.yml
index e4bb5b179..1a2e5446c 100644
--- a/etc/kayobe/apt.yml
+++ b/etc/kayobe/apt.yml
@@ -41,6 +41,14 @@
 # Default is an empty list.
 #apt_repositories:
 
+# List of Apt preferences options. Each item is a dict with the following
+# keys:
+# * content: free-form preferences file content
+# * filename: name of a file in /etc/apt/preferences.d/ in which to write
+#   the configuration
+# Default is an empty list.
+#apt_preferences:
+
 # Whether to disable repositories in /etc/apt/sources.list. This may be used
 # when replacing the distribution repositories via apt_repositories.
 # Default is false.
diff --git a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2
index ae454d644..b90872197 100644
--- a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2
+++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2
@@ -149,6 +149,12 @@ apt_repositories:
   - url:  http://packages.treasuredata.com/4/ubuntu/jammy/
     components: contrib
     signed_by: td-agent.asc
+apt_preferences:
+  - content: |
+      Package: fake-package
+      Pin: origin fake.repo.url
+      Pin-Priority: 1
+    filename: 99fakepackage
 apt_disable_sources_list: true
 apt_auth:
   - machine: https://apt.example.com
diff --git a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py
index 22cceb429..d4286ef5f 100644
--- a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py
+++ b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py
@@ -210,6 +210,15 @@ def test_apt_config(host):
     assert apt_config.content_string == "Acquire::Retries 1;\n"
 
 
+@pytest.mark.skipif(not _is_apt(), reason="Apt only supported on Ubuntu")
+def test_apt_preferences(host):
+    apt_preferences = host.file("/etc/apt/preferences.d/99fakepackage")
+    assert apt_preferences.exists
+    assert apt_preferences.content_string == ("Package: fake-package\n"
+                                              "Pin: origin fake.repo.url\n"
+                                              "Pin-Priority: 1\n")
+
+
 @pytest.mark.skipif(not _is_apt(), reason="Apt only supported on Ubuntu")
 def test_apt_custom_package_repository_is_available(host):
     with host.sudo():
diff --git a/releasenotes/notes/support-setting-apt-preferences-12913cd782f45dd7.yaml b/releasenotes/notes/support-setting-apt-preferences-12913cd782f45dd7.yaml
new file mode 100644
index 000000000..1ba663f69
--- /dev/null
+++ b/releasenotes/notes/support-setting-apt-preferences-12913cd782f45dd7.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Adds support for configuring Apt preferences under
+    ``/etc/apt/preferences.d``.