diff --git a/.tool-versions b/.tool-versions
index b1692b6d8861260da4885cdc4a4925ecb7ba17e6_LnRvb2wtdmVyc2lvbnM=..9a90e4de35417d447beceba4eb9bfd681fc17d74_LnRvb2wtdmVyc2lvbnM= 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,2 @@
 ruby 3.1.4
-vale 2.29.6
+vale 3.0.7
diff --git a/doc/.vale/gitlab/HistoryItems.yml b/doc/.vale/gitlab/HistoryItems.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9a90e4de35417d447beceba4eb9bfd681fc17d74_ZG9jLy52YWxlL2dpdGxhYi9IaXN0b3J5SXRlbXMueW1s
--- /dev/null
+++ b/doc/.vale/gitlab/HistoryItems.yml
@@ -0,0 +1,13 @@
+---
+# Warning: gitlab.HistoryItems
+#
+# Ensures history items are properly formatted.
+#
+extends: existence
+message: "History items must always start with '> -', one item per line, even if there is only one item."
+link: https://docs.gitlab.com/ee/development/documentation/versions.html#add-a-version-history-item
+level: error
+scope: raw
+raw:
+  - '(?m)(?<=^#+[^\n]*\n\n)> [^-]|'
+  - '^> - [^\n]*\n[^\n>`]'
diff --git a/doc/.vale/gitlab/LatinTerms.yml b/doc/.vale/gitlab/LatinTerms.yml
index b1692b6d8861260da4885cdc4a4925ecb7ba17e6_ZG9jLy52YWxlL2dpdGxhYi9MYXRpblRlcm1zLnltbA==..9a90e4de35417d447beceba4eb9bfd681fc17d74_ZG9jLy52YWxlL2dpdGxhYi9MYXRpblRlcm1zLnltbA== 100644
--- a/doc/.vale/gitlab/LatinTerms.yml
+++ b/doc/.vale/gitlab/LatinTerms.yml
@@ -15,3 +15,4 @@
   e\. g\.: for example
   i\.e\.: that is
   i\. e\.: that is
+  via: "with', 'through', or 'by using"
diff --git a/doc/.vale/gitlab/Prerequisites.yml b/doc/.vale/gitlab/Prerequisites.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9a90e4de35417d447beceba4eb9bfd681fc17d74_ZG9jLy52YWxlL2dpdGxhYi9QcmVyZXF1aXNpdGVzLnltbA==
--- /dev/null
+++ b/doc/.vale/gitlab/Prerequisites.yml
@@ -0,0 +1,14 @@
+---
+# Error: gitlab.Prerequisites
+#
+# The "Prerequisites:" line should always be plural.
+#
+# For a list of all options, see https://vale.sh/docs/topics/styles/
+extends: existence
+message: "Pluralize 'Prerequisites', even if it includes only one item."
+link: https://docs.gitlab.com/ee/development/documentation/topic_types/task.html#task-prerequisites
+level: warning
+nonword: true
+scope: text
+raw:
+  - '^Prerequisite:'
diff --git a/doc/.vale/gitlab/SubstitutionWarning.yml b/doc/.vale/gitlab/SubstitutionWarning.yml
index b1692b6d8861260da4885cdc4a4925ecb7ba17e6_ZG9jLy52YWxlL2dpdGxhYi9TdWJzdGl0dXRpb25XYXJuaW5nLnltbA==..9a90e4de35417d447beceba4eb9bfd681fc17d74_ZG9jLy52YWxlL2dpdGxhYi9TdWJzdGl0dXRpb25XYXJuaW5nLnltbA== 100644
--- a/doc/.vale/gitlab/SubstitutionWarning.yml
+++ b/doc/.vale/gitlab/SubstitutionWarning.yml
@@ -30,9 +30,10 @@
   ex: "for example"
   file name: "filename"
   filesystem: "file system"
+  fullscreen: "full screen"
   info: "information"
   installation from source: self-compiled installation
   installations from source: self-compiled installations
   it is recommended: "you should"
   logged in user: "authenticated user"
   logged-in user: "authenticated user"
@@ -33,9 +34,10 @@
   info: "information"
   installation from source: self-compiled installation
   installations from source: self-compiled installations
   it is recommended: "you should"
   logged in user: "authenticated user"
   logged-in user: "authenticated user"
+  machine-learning: "machine learning"
   modal dialog: "dialog"
   modal window: "dialog"
   modal: "dialog"
@@ -47,6 +49,8 @@
   once that: "after that"
   once the: "after the"
   once you: "after you"
+  pack file: packfile
+  pack files: packfiles
   pop-up window: "dialog"
   pop-up: "dialog"
   popup: "dialog"
@@ -59,6 +63,6 @@
   sub-group: "subgroup"
   sub-groups: "subgroups"
   timezone: "time zone"
-  utilize: "use"
+  utiliz(?:es?|ing): "use"
   we recommend: "you should"
   within: "in"
diff --git a/doc/.vale/gitlab/Substitutions.yml b/doc/.vale/gitlab/Substitutions.yml
index b1692b6d8861260da4885cdc4a4925ecb7ba17e6_ZG9jLy52YWxlL2dpdGxhYi9TdWJzdGl0dXRpb25zLnltbA==..9a90e4de35417d447beceba4eb9bfd681fc17d74_ZG9jLy52YWxlL2dpdGxhYi9TdWJzdGl0dXRpb25zLnltbA== 100644
--- a/doc/.vale/gitlab/Substitutions.yml
+++ b/doc/.vale/gitlab/Substitutions.yml
@@ -7,7 +7,7 @@
 # For a list of all options, see https://vale.sh/docs/topics/styles/
 extends: substitution
 message: "Use '%s' instead of '%s'."
-link: https://about.gitlab.com/handbook/communication/#top-misused-terms
+link: https://handbook.gitlab.com/handbook/communication/top-misused-terms/
 level: error
 ignorecase: true
 swap:
@@ -11,37 +11,8 @@
 level: error
 ignorecase: true
 swap:
-  codequality: code quality
-  Customer [Pp]ortal: Customers Portal
-  disallow: prevent
-  frontmatter: front matter
-  GitLabber: GitLab team member
-  GitLabbers: GitLab team members
-  GitLab-shell: GitLab Shell
-  gitlab omnibus: Linux package
-  param: parameter
-  params: parameters
-  pg: PostgreSQL
-  'postgres$': PostgreSQL
-  golang: Go
-  raketask: Rake task
-  raketasks: Rake tasks
-  rspec: RSpec
-  self hosted: self-managed
-  self-hosted: self-managed
-  styleguide: style guide
-  to login: to log in
-  can login: can log in
-  to log-in: to log in
-  can log-in: can log in
-  to signin: to sign in
-  can signin: can sign in
-  to sign-in: to sign in
-  can sign-in: can sign in
-  x509: X.509
-  yml: YAML
   admin user: administrator
   admin users: administrators
   administrator permission: administrator access
   administrator permissions: administrator access
   administrator role: administrator access
@@ -43,9 +14,15 @@
   admin user: administrator
   admin users: administrators
   administrator permission: administrator access
   administrator permissions: administrator access
   administrator role: administrator access
-  the administrator access level: administrator access
+  at least the Owner role: the Owner role
+  can login: can log in
+  can log-in: can log in
+  can signin: can sign in
+  can sign-in: can sign in
+  codequality: code quality
+  Customer [Pp]ortal: Customers Portal
   developer access: the Developer role
   developer permission: the Developer role
   developer permissions: the Developer role
@@ -49,6 +26,16 @@
   developer access: the Developer role
   developer permission: the Developer role
   developer permissions: the Developer role
+  disallow: prevent
+  '(?<!GitLab )Duo': GitLab Duo
+  frontmatter: front matter
+  GitLab self hosted: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+  GitLab self-hosted: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+  GitLabber: GitLab team member
+  GitLabbers: GitLab team members
+  GitLab-shell: GitLab Shell
+  gitlab omnibus: Linux package
+  golang: Go
   guest access: the Guest role
   guest permission: the Guest role
   guest permissions: the Guest role
@@ -58,6 +45,13 @@
   owner access: the Owner role
   owner permission: the Owner role
   owner permissions: the Owner role
+  param: parameter
+  params: parameters
+  pg: PostgreSQL
+  'postgres$': PostgreSQL
+  raketask: Rake task
+  raketasks: Rake tasks
+  rspec: RSpec
   reporter access: the Reporter role
   reporter permission: the Reporter role
   reporter permissions: the Reporter role
@@ -61,4 +55,13 @@
   reporter access: the Reporter role
   reporter permission: the Reporter role
   reporter permissions: the Reporter role
-  at least the Owner role: the Owner role
+  self hosted GitLab: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+  self-hosted GitLab: GitLab self-managed # https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html#gitlab-self-managed
+  styleguide: style guide
+  the administrator access level: administrator access
+  to login: to log in
+  to log-in: to log in
+  to signin: to sign in
+  to sign-in: to sign in
+  x509: X.509
+  yml: YAML
diff --git a/doc/.vale/gitlab/Wordy.yml b/doc/.vale/gitlab/Wordy.yml
index b1692b6d8861260da4885cdc4a4925ecb7ba17e6_ZG9jLy52YWxlL2dpdGxhYi9Xb3JkeS55bWw=..9a90e4de35417d447beceba4eb9bfd681fc17d74_ZG9jLy52YWxlL2dpdGxhYi9Xb3JkeS55bWw= 100644
--- a/doc/.vale/gitlab/Wordy.yml
+++ b/doc/.vale/gitlab/Wordy.yml
@@ -10,6 +10,7 @@
 level: suggestion
 ignorecase: true
 swap:
+  a number of: "Specify the number or remove the phrase."
   as well as: "Use 'and' instead of 'as well as'."
   note that: "Remove the phrase 'note that'."
   please: "Use 'please' only if we've inconvenienced the user."
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index b1692b6d8861260da4885cdc4a4925ecb7ba17e6_ZG9jLy52YWxlL2dpdGxhYi9zcGVsbGluZy1leGNlcHRpb25zLnR4dA==..9a90e4de35417d447beceba4eb9bfd681fc17d74_ZG9jLy52YWxlL2dpdGxhYi9zcGVsbGluZy1leGNlcHRpb25zLnR4dA== 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -109,6 +109,7 @@
 bugfixes
 bugfixing
 Bugzilla
+Buildah
 Buildkite
 buildpack
 buildpacks
@@ -176,6 +177,7 @@
 codenames
 Codepen
 CodeSandbox
+Codey
 Cognito
 Coinbase
 colocate
@@ -282,6 +284,7 @@
 DevOps
 Dhall
 dialogs
+Diffblue
 disambiguates
 discoverability
 dismissable
@@ -350,6 +353,7 @@
 Fastzip
 favicon
 favorited
+Fediverse
 ffaker
 Figma
 Filebeat
@@ -370,6 +374,7 @@
 focusable
 Forgerock
 formatters
+Fortanix
 Fortinet
 FQDNs
 FreshBooks
@@ -525,6 +530,7 @@
 ldapsearch
 Lefthook
 Leiningen
+Lemmy
 libFuzzer
 Libgcrypt
 Libravatar
@@ -644,6 +650,7 @@
 onboarding
 OpenID
 OpenShift
+OpenTelemetry
 Opsgenie
 Opstrace
 ORMs
@@ -879,7 +886,6 @@
 sharding
 SHAs
 shfmt
-Shimo
 Shippo
 Shopify
 Sidekiq
@@ -958,6 +964,7 @@
 subqueries
 subquery
 subquerying
+Subreddit
 substring
 substrings
 subtask
diff --git a/gitlab-ci-config/gitlab-com.yml b/gitlab-ci-config/gitlab-com.yml
index b1692b6d8861260da4885cdc4a4925ecb7ba17e6_Z2l0bGFiLWNpLWNvbmZpZy9naXRsYWItY29tLnltbA==..9a90e4de35417d447beceba4eb9bfd681fc17d74_Z2l0bGFiLWNpLWNvbmZpZy9naXRsYWItY29tLnltbA== 100644
--- a/gitlab-ci-config/gitlab-com.yml
+++ b/gitlab-ci-config/gitlab-com.yml
@@ -308,7 +308,7 @@
 
 # Perform documentation linting on Markdown files
 docs-lint markdown:
-  image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.18-vale-2.29.6-markdownlint-0.37.0-markdownlint2-0.10.0
+  image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.19-vale-3.0.7-markdownlint-0.39.0-markdownlint2-0.12.1
   stage: check
   cache: {}
   needs: []
@@ -325,9 +325,9 @@
 
 # Perform link checks on published HTML files
 docs-lint links:
-  image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-html:alpine-3.18-ruby-3.2.2-08fa6df8
+  image: registry.gitlab.com/gitlab-org/gitlab-docs/lint-html:alpine-3.19-ruby-3.2.3-eee6c4c0
   stage: check
   cache: {}
   needs: []
   before_script: []
   script:
@@ -329,11 +329,13 @@
   stage: check
   cache: {}
   needs: []
   before_script: []
   script:
-    # Prepare docs for build
-    - mv doc/ /tmp/gitlab-docs/content/omnibus
+    # Put documentation in location expected by nanoc and configured in nanoc.yaml. For more information, see:
+    # https://gitlab.com/gitlab-org/gitlab-docs/-/blob/eee6c4c03621ff13743828ad74a8f0e8b96dfe7c/nanoc.yaml#L43
+    - mkdir /tmp/omnibus-gitlab
+    - mv doc/ /tmp/omnibus-gitlab
     - cd /tmp/gitlab-docs
     # Build HTML from Markdown
     - make compile
     # Check the internal links and anchors (in parallel)
@@ -336,7 +338,9 @@
     - cd /tmp/gitlab-docs
     # Build HTML from Markdown
     - make compile
     # Check the internal links and anchors (in parallel)
+    # Links from the home page aren't checked because they aren't rendered here. For information, see:
+    # https://gitlab.com/gitlab-org/gitlab-docs/-/blob/eee6c4c03621ff13743828ad74a8f0e8b96dfe7c/content/index.erb#L6
     - "parallel time bundle exec nanoc check ::: internal_links internal_anchors"
   rules:
     - if: '$PIPELINE_TYPE =~ /_TEST_PIPELINE$/'