Repository: linkerd/linkerd2
Pull request #12763 (2024-06-23 17:17:06)

build(deps-dev): bump eslint-plugin-jsx-a11y from 6.7.1 to 6.9.0 in /web/app
Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.7.1 to 6.9.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases">eslint-plugin-jsx-a11y's releases</a>.</em></p>
<blockquote>
<h2>v6.9.0</h2>
<h3>Added</h3>
<ul>
<li>add support for Flat Config <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/6b5f096f10b47326d68e2893152a48a79c8555b4"><code>6b5f096</code></a></li>
<li><code>no-noninteractive-element-to-interactive-role</code>: allow <code>menuitemradio</code> and <code>menuitemcheckbox</code> on &lt;li&gt; <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/c0733f94031fe3eec6b4d54176afe47929bb0a84"><code>c0733f9</code></a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li><code>img-redundant-alt</code>: fixed multibyte character support <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/969"><code>[#969](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/969)</code></a></li>
<li>Revert &quot;[Fix] <code>isNonInteractiveElement</code>: Upgrade aria-query to 5.3.0 and axobject-query to 3.2.1&quot; <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/75d5dd722bd67186d97afa7b151fd6fee5885c70"><code>75d5dd7</code></a></li>
<li>ensure <code>summary</code> remains non-interactive <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/6a048dacf2b98eaa204e2a5a70dc7e3d48d9463a"><code>6a048da</code></a></li>
</ul>
<h3>Changed</h3>
<ul>
<li>[meta] fix changelog links <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/960"><code>[#960](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/960)</code></a></li>
<li>[Robustness] use <code>safe-regex-test</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/4c7e7815c12a797587bb8e3cdced7f3003848964"><code>4c7e781</code></a></li>
<li>[actions] update actions/checkout <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/51a1ca7b4d83d4fbd1ea62888f7f2dc21ece6788"><code>51a1ca7</code></a></li>
<li>[Deps] pin <code>aria-query</code> and <code>axobject-query</code>, add <code>ls-engines</code> test to CI <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/32fd82c628d7f3e4ec8c06a1994f4eca1be2be4f"><code>32fd82c</code></a></li>
<li>[Deps] remove <code>@babel/runtime</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/0a98ad83ffa7f4b66458cc1c39db2ef32bb2c480"><code>0a98ad8</code></a></li>
<li>[Deps] unpin <code>axe-core</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/b3559cf89be6b5352cd77ffa025831b3d793d565"><code>b3559cf</code></a></li>
<li>[Deps] move <code>object.entries</code> to dev deps <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/1be7b709eececd83f1d5f67a60b2c97cfe9a561d"><code>1be7b70</code></a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.8.0...v6.9.0">https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.8.0...v6.9.0</a></p>
<h2>v6.8.0</h2>
<p><strong>Full Changelog</strong>: <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.7.1...v6.8.0">https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.7.1...v6.8.0</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md">eslint-plugin-jsx-a11y's changelog</a>.</em></p>
<blockquote>
<h2><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.8.0...v6.9.0">v6.9.0</a> - 2024-06-19</h2>
<h3>Fixed</h3>
<ul>
<li>[Fix] <code>img-redundant-alt</code>: fixed multibyte character support <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/969"><code>[#969](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/969)</code></a></li>
<li>[meta] fix changelog links <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/960"><code>[#960](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/960)</code></a></li>
</ul>
<h3>Commits</h3>
<ul>
<li>[New] add support for Flat Config <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/6b5f096f10b47326d68e2893152a48a79c8555b4"><code>6b5f096</code></a></li>
<li>Revert &quot;[Fix] <code>isNonInteractiveElement</code>: Upgrade aria-query to 5.3.0 and axobject-query to 3.2.1&quot; <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/75d5dd722bd67186d97afa7b151fd6fee5885c70"><code>75d5dd7</code></a></li>
<li>[Robustness] use <code>safe-regex-test</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/4c7e7815c12a797587bb8e3cdced7f3003848964"><code>4c7e781</code></a></li>
<li>[actions] update actions/checkout <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/51a1ca7b4d83d4fbd1ea62888f7f2dc21ece6788"><code>51a1ca7</code></a></li>
<li>[Dev Deps] update <code>@babel/cli</code>, <code>@babel/core</code>, <code>@babel/eslint-parser</code>, <code>@babel/plugin-transform-flow-strip-types</code>, <code>@babel/register</code>, <code>eslint-doc-generator</code>, <code>object.entries</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/1271ac1d6e5dcf9a2bc2c086faaf062335629171"><code>1271ac1</code></a></li>
<li>[Dev Deps] update <code>@babel/cli</code>, <code>@babel/core</code>, <code>@babel/register</code>, <code>aud</code>, <code>eslint-plugin-import</code>, <code>npmignore</code>, <code>object.assign</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/540cb7aefead582f237071d55a40f098d0885478"><code>540cb7a</code></a></li>
<li>[Deps] update <code>@babel/runtime</code>, <code>array-includes</code>, <code>es-iterator-helpers</code>, <code>hasown</code>, <code>object.fromentries</code>, <code>safe-regex-test</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/5d1440825a8838ae10dc94cc3a4a7e1e967644b4"><code>5d14408</code></a></li>
<li>[Deps] pin <code>aria-query</code> and <code>axobject-query</code>, add <code>ls-engines</code> test to CI <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/32fd82c628d7f3e4ec8c06a1994f4eca1be2be4f"><code>32fd82c</code></a></li>
<li>[Dev Deps] update <code>@babel/core</code>, <code>@babel/eslint-parser</code>, <code>@babel/plugin-transform-flow-strip-types</code>, <code>eslint-doc-generator</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/d1b41142248a7cca45bb5f0b96ff23ee87fb9411"><code>d1b4114</code></a></li>
<li>[Fix] ensure <code>summary</code> remains non-interactive <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/6a048dacf2b98eaa204e2a5a70dc7e3d48d9463a"><code>6a048da</code></a></li>
<li>[Deps] remove <code>@babel/runtime</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/0a98ad83ffa7f4b66458cc1c39db2ef32bb2c480"><code>0a98ad8</code></a></li>
<li>[New] <code>no-noninteractive-element-to-interactive-role</code>: allow <code>menuitemradio</code> and <code>menuitemcheckbox</code> on &lt;li&gt; <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/c0733f94031fe3eec6b4d54176afe47929bb0a84"><code>c0733f9</code></a></li>
<li>[Deps] update <code>@babel/runtime</code>, <code>safe-regex-test</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/0d5321a5457c5f0da0ca216053cc5b4f571b53ae"><code>0d5321a</code></a></li>
<li>[actions] pin codecov to v3.1.5 <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/961817f61fa56cd7815c6940c27ef08469b1516b"><code>961817f</code></a></li>
<li>[Deps] unpin <code>axe-core</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/b3559cf89be6b5352cd77ffa025831b3d793d565"><code>b3559cf</code></a></li>
<li>[Deps] move <code>object.entries</code> to dev deps <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/1be7b709eececd83f1d5f67a60b2c97cfe9a561d"><code>1be7b70</code></a></li>
<li>[Deps] update <code>@babel/runtime</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/2a48abb5effa911e7d1a8575e1c9768c947a33f1"><code>2a48abb</code></a></li>
<li>[Deps] update <code>@babel/runtime</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/1adec3517fc2c9797212ca4d38858deed917e7be"><code>1adec35</code></a></li>
</ul>
<h2><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.7.1...v6.8.0">v6.8.0</a> - 2023-11-01</h2>
<h3>Merged</h3>
<ul>
<li>Allow <code>title</code> attribute or <code>aria-label</code> attribute instead of accessible child in the &quot;anchor-has-content&quot; rule <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/pull/727"><code>[#727](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/727)</code></a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>[Docs] <code>aria-activedescendant-has-tabindex</code>: align with changes from <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/708">#708</a> <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/924"><code>[#924](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/924)</code></a></li>
<li>[Fix] <code>control-has-associated-label</code>: don't accept whitespace as an accessible label <a href="https://redirect.github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/918"><code>[#918](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/918)</code></a></li>
</ul>
<h3>Commits</h3>
<ul>
<li>[Tests] migrate helper parsers function from <code>eslint-plugin-react</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/ce4d57f853ce7f71bd31edaa524eeb3ff1d27cf1"><code>ce4d57f</code></a></li>
<li>[Refactor] use <code>es-iterator-helpers</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/52de82403752bb2ccbcac3379925650a0112d4af"><code>52de824</code></a></li>
<li>[New] <code>mouse-events-have-key-events</code>: add <code>hoverInHandlers</code>/<code>hoverOutHandlers</code> config <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/db64898fa591f17827053ad3c2ddeafdf7297dd6"><code>db64898</code></a></li>
<li>[New] add <code>polymorphicPropName</code> setting for polymorphic components <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/fffb05b38c8eee926ee758e9ceb9eae4e697fbdd"><code>fffb05b</code></a></li>
<li>[Fix] <code>isNonInteractiveElement</code>: Upgrade aria-query to 5.3.0 and axobject-query to 3.2.1 <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/64bfea6352a704470a760fa6ea25cfc5a50414db"><code>64bfea6</code></a></li>
<li>[Refactor] use <code>hasown</code> instead of <code>has</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/9a8edde7f2e80b7d104dd576f91526c6c4cbebb9"><code>9a8edde</code></a></li>
<li>[actions] update used actions <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/10c061a70cac067641e3a084d0fb464960544505"><code>10c061a</code></a></li>
<li>[Dev Deps] update <code>@babel/cli</code>, <code>@babel/core</code>, <code>@babel/eslint-parser</code>, <code>@babel/plugin-transform-flow-strip-types</code>, <code>@babel/register</code>, <code>aud</code>, <code>eslint-doc-generator</code>, <code>eslint-plugin-import</code>, <code>minimist</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/6d5022d4894fa88d3c15c8b858114e8b2a8a440f"><code>6d5022d</code></a></li>
<li>[Dev Deps] update <code>@babel/cli</code>, <code>@babel/core</code>, <code>@babel/eslint-parser</code>, <code>@babel/register</code>, <code>eslint-doc-generator</code>, <code>eslint-plugin-import</code> <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/4dc7f1e5c611aeea2f81dc50d4ec0b206566181a"><code>4dc7f1e</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/882bd4dc8a4898be1ca0e69561f7885b8ea2c085"><code>882bd4d</code></a> v6.9.0</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/961817f61fa56cd7815c6940c27ef08469b1516b"><code>961817f</code></a> [actions] pin codecov to v3.1.5</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/6a048dacf2b98eaa204e2a5a70dc7e3d48d9463a"><code>6a048da</code></a> [Fix] ensure <code>summary</code> remains non-interactive</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/75d5dd722bd67186d97afa7b151fd6fee5885c70"><code>75d5dd7</code></a> Revert &quot;[Fix] <code>isNonInteractiveElement</code>: Upgrade aria-query to 5.3.0 and axob...</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/32fd82c628d7f3e4ec8c06a1994f4eca1be2be4f"><code>32fd82c</code></a> [Deps] pin <code>aria-query</code> and <code>axobject-query</code>, add <code>ls-engines</code> test to CI</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/0a98ad83ffa7f4b66458cc1c39db2ef32bb2c480"><code>0a98ad8</code></a> [Deps] remove <code>@babel/runtime</code></li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/6b5f096f10b47326d68e2893152a48a79c8555b4"><code>6b5f096</code></a> [New] add support for Flat Config</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/51a1ca7b4d83d4fbd1ea62888f7f2dc21ece6788"><code>51a1ca7</code></a> [actions] update actions/checkout</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/5d1440825a8838ae10dc94cc3a4a7e1e967644b4"><code>5d14408</code></a> [Deps] update <code>@babel/runtime</code>, <code>array-includes</code>, <code>es-iterator-helpers</code>, `has...</li>
<li><a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/commit/1271ac1d6e5dcf9a2bc2c086faaf062335629171"><code>1271ac1</code></a> [Dev Deps] update <code>@babel/cli</code>, <code>@babel/core</code>, <code>@babel/eslint-parser</code>, `@babe...</li>
<li>Additional commits viewable in <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.7.1...v6.9.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=eslint-plugin-jsx-a11y&package-manager=npm_and_yarn&previous-version=6.7.1&new-version=6.9.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index ae9655576..fe42bda5c 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -9,7 +9,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
-      - uses: tj-actions/changed-files@eaf854ef0c266753e1abec356dcf17d92695b251
+      - uses: tj-actions/changed-files@cc733854b1f224978ef800d29e4709d5ee2883e4
         id: changed
         with:
           files: |
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 51cc18a0c..37eac3a96 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -28,7 +28,7 @@ jobs:
       - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
       - id: tag
         run: echo "tag=$(CI_FORCE_CLEAN=1 bin/root-tag)" >> "$GITHUB_OUTPUT"
-      - uses: tj-actions/changed-files@eaf854ef0c266753e1abec356dcf17d92695b251
+      - uses: tj-actions/changed-files@cc733854b1f224978ef800d29e4709d5ee2883e4
         id: core
         with:
           files: |
diff --git a/.proxy-version b/.proxy-version
index e6c040c7b..15b691f35 100644
--- a/.proxy-version
+++ b/.proxy-version
@@ -1,1 +1,1 @@
-v2.237.0
+v2.238.0
diff --git a/Cargo.lock b/Cargo.lock
index 8c5b7f2a3..9dd1c22a8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -244,9 +244,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
 
 [[package]]
 name = "cc"
-version = "1.0.99"
+version = "1.0.103"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
+checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410"
 
 [[package]]
 name = "cfg-if"
@@ -444,9 +444,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
 
 [[package]]
 name = "either"
-version = "1.12.0"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
 
 [[package]]
 name = "equivalent"
@@ -1138,9 +1138,9 @@ dependencies = [
 
 [[package]]
 name = "lazy_static"
-version = "1.4.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
@@ -1220,7 +1220,6 @@ dependencies = [
  "futures",
  "http",
  "hyper",
- "itertools",
  "linkerd-policy-controller-core",
  "linkerd2-proxy-api",
  "maplit",
@@ -1440,9 +1439,9 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.36.0"
+version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
+checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
 dependencies = [
  "memchr",
 ]
@@ -2043,9 +2042,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.117"
+version = "1.0.118"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
 dependencies = [
  "itoa",
  "ryu",
@@ -2202,9 +2201,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.6.0"
+version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82"
 dependencies = [
  "tinyvec_macros",
 ]
diff --git a/charts/linkerd-control-plane/README.md b/charts/linkerd-control-plane/README.md
index 47f44c1f7..07eb87d52 100644
--- a/charts/linkerd-control-plane/README.md
+++ b/charts/linkerd-control-plane/README.md
@@ -289,12 +289,6 @@ Kubernetes: `>=1.22.0-0`
 | proxyInit.logFormat | string | plain | Log format (`plain` or `json`) for the proxy-init |
 | proxyInit.logLevel | string | info | Log level for the proxy-init |
 | proxyInit.privileged | bool | false | Privileged mode allows the container processes to inherit all security capabilities and bypass any security limitations enforced by the kubelet. When used with 'runAsRoot: true', the container will behave exactly as if it was running as root on the host. May escape cgroup limits and see other processes and devices on the host. |
-| proxyInit.resources.cpu.limit | string | `"100m"` | Maximum amount of CPU units that the proxy-init container can use |
-| proxyInit.resources.cpu.request | string | `"100m"` | Amount of CPU units that the proxy-init container requests |
-| proxyInit.resources.ephemeral-storage.limit | string | `""` | Maximum amount of ephemeral storage that the proxy-init container can use |
-| proxyInit.resources.ephemeral-storage.request | string | `""` | Amount of ephemeral storage that the proxy-init container requests |
-| proxyInit.resources.memory.limit | string | `"20Mi"` | Maximum amount of memory that the proxy-init container can use |
-| proxyInit.resources.memory.request | string | `"20Mi"` | Amount of memory that the proxy-init container requests |
 | proxyInit.runAsGroup | int | `65534` | This value is used only if runAsRoot is false; otherwise runAsGroup will be 0 |
 | proxyInit.runAsRoot | bool | `false` | Allow overriding the runAsNonRoot behaviour (<https://github.com/linkerd/linkerd2/issues/7308>) |
 | proxyInit.runAsUser | int | `65534` | This value is used only if runAsRoot is false; otherwise runAsUser will be 0 |
diff --git a/charts/linkerd-control-plane/templates/destination.yaml b/charts/linkerd-control-plane/templates/destination.yaml
index 4513d315e..847e4570c 100644
--- a/charts/linkerd-control-plane/templates/destination.yaml
+++ b/charts/linkerd-control-plane/templates/destination.yaml
@@ -125,6 +125,12 @@ spec:
 {{ $_ := set $tree.Values.proxy "workloadKind" "deployment" -}}
 {{ $_ := set $tree.Values.proxy "component" "linkerd-destination" -}}
 {{ $_ := set $tree.Values.proxy "waitBeforeExitSeconds" 0 -}}
+{{- if not (empty .Values.destinationProxyResources) }}
+{{- $c := dig "cores" .Values.proxy.cores .Values.destinationProxyResources }}
+{{- $_ := set $tree.Values.proxy "cores" $c }}
+{{- $r := merge .Values.destinationProxyResources .Values.proxy.resources }}
+{{- $_ := set $tree.Values.proxy "resources" $r }}
+{{- end }}
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -176,12 +182,6 @@ spec:
       {{- $_ := set $tree "component" "destination" -}}
       {{- include "linkerd.affinity" $tree | nindent 6 }}
       containers:
-      {{- if not (empty .Values.destinationProxyResources) }}
-      {{- $c := dig "cores" .Values.proxy.cores .Values.destinationProxyResources }}
-      {{- $_ := set $tree.Values.proxy "cores" $c }}
-      {{- $r := merge .Values.destinationProxyResources .Values.proxy.resources }}
-      {{- $_ := set $tree.Values.proxy "resources" $r }}
-      {{- end }}
       {{- $_ := set $tree.Values.proxy "await" $tree.Values.proxy.await }}
       {{- $_ := set $tree.Values.proxy "loadTrustBundleFromConfigMap" true }}
       {{- $_ := set $tree.Values.proxy "podInboundPorts" "8086,8090,8443,9443,9990,9996,9997" }}
diff --git a/charts/linkerd-control-plane/templates/identity.yaml b/charts/linkerd-control-plane/templates/identity.yaml
index 243b7e87e..bd3bcbe31 100644
--- a/charts/linkerd-control-plane/templates/identity.yaml
+++ b/charts/linkerd-control-plane/templates/identity.yaml
@@ -100,6 +100,12 @@ spec:
 {{ $_ := set $tree.Values.proxy "workloadKind" "deployment" -}}
 {{ $_ := set $tree.Values.proxy "component" "linkerd-identity" -}}
 {{ $_ := set $tree.Values.proxy "waitBeforeExitSeconds" 0 -}}
+{{- if not (empty .Values.identityProxyResources) }}
+{{- $c := dig "cores" .Values.proxy.cores .Values.identityProxyResources }}
+{{- $_ := set $tree.Values.proxy "cores" $c }}
+{{- $r := merge .Values.identityProxyResources .Values.proxy.resources }}
+{{- $_ := set $tree.Values.proxy "resources" $r }}
+{{- end }}
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -211,12 +217,6 @@ spec:
           name: identity-issuer
         - mountPath: /var/run/linkerd/identity/trust-roots/
           name: trust-roots
-      {{- if not (empty .Values.identityProxyResources) }}
-      {{- $c := dig "cores" .Values.proxy.cores .Values.identityProxyResources }}
-      {{- $_ := set $tree.Values.proxy "cores" $c }}
-      {{- $r := merge .Values.identityProxyResources .Values.proxy.resources }}
-      {{- $_ := set $tree.Values.proxy "resources" $r }}
-      {{- end }}
       {{- $_ := set $tree.Values.proxy "await" false }}
       {{- $_ := set $tree.Values.proxy "loadTrustBundleFromConfigMap" true }}
       {{- $_ := set $tree.Values.proxy "podInboundPorts" "8080,9990" }}
diff --git a/charts/linkerd-control-plane/templates/proxy-injector.yaml b/charts/linkerd-control-plane/templates/proxy-injector.yaml
index f0231dd95..0f6b3bb87 100644
--- a/charts/linkerd-control-plane/templates/proxy-injector.yaml
+++ b/charts/linkerd-control-plane/templates/proxy-injector.yaml
@@ -6,6 +6,12 @@
 {{ $_ := set $tree.Values.proxy "workloadKind" "deployment" -}}
 {{ $_ := set $tree.Values.proxy "component" "linkerd-proxy-injector" -}}
 {{ $_ := set $tree.Values.proxy "waitBeforeExitSeconds" 0 -}}
+{{- if not (empty .Values.proxyInjectorProxyResources) }}
+{{- $c := dig "cores" .Values.proxy.cores .Values.proxyInjectorProxyResources }}
+{{- $_ := set $tree.Values.proxy "cores" $c }}
+{{- $r := merge .Values.proxyInjectorProxyResources .Values.proxy.resources }}
+{{- $_ := set $tree.Values.proxy "resources" $r }}
+{{- end }}
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -56,12 +62,6 @@ spec:
       {{- $_ := set $tree "component" "proxy-injector" -}}
       {{- include "linkerd.affinity" $tree | nindent 6 }}
       containers:
-      {{- if not (empty .Values.proxyInjectorProxyResources) }}
-      {{- $c := dig "cores" .Values.proxy.cores .Values.proxyInjectorProxyResources }}
-      {{- $_ := set $tree.Values.proxy "cores" $c }}
-      {{- $r := merge .Values.proxyInjectorProxyResources .Values.proxy.resources }}
-      {{- $_ := set $tree.Values.proxy "resources" $r }}
-      {{- end }}
       {{- $_ := set $tree.Values.proxy "await" $tree.Values.proxy.await }}
       {{- $_ := set $tree.Values.proxy "loadTrustBundleFromConfigMap" true }}
       {{- $_ := set $tree.Values.proxy "podInboundPorts" "8443,9995" }}
diff --git a/charts/linkerd-control-plane/values.yaml b/charts/linkerd-control-plane/values.yaml
index 72214320b..43e09e40e 100644
--- a/charts/linkerd-control-plane/values.yaml
+++ b/charts/linkerd-control-plane/values.yaml
@@ -295,22 +295,6 @@ proxyInit:
     pullPolicy: ""
     # -- Tag for the proxy-init container image
     version: v2.4.1
-  resources:
-    cpu:
-      # -- Maximum amount of CPU units that the proxy-init container can use
-      limit: 100m
-      # -- Amount of CPU units that the proxy-init container requests
-      request: 100m
-    memory:
-      # -- Maximum amount of memory that the proxy-init container can use
-      limit: 20Mi
-      # -- Amount of memory that the proxy-init container requests
-      request: 20Mi
-    ephemeral-storage:
-      # -- Maximum amount of ephemeral storage that the proxy-init container can use
-      limit: ""
-      # -- Amount of ephemeral storage that the proxy-init container requests
-      request: ""
   closeWaitTimeoutSecs: 0
   # -- Privileged mode allows the container processes to inherit all security
   # capabilities and bypass any security limitations enforced by the kubelet.
diff --git a/charts/partials/templates/_network-validator.tpl b/charts/partials/templates/_network-validator.tpl
index e3920a491..e31233311 100644
--- a/charts/partials/templates/_network-validator.tpl
+++ b/charts/partials/templates/_network-validator.tpl
@@ -2,7 +2,7 @@
 name: linkerd-network-validator
 image: {{.Values.proxy.image.name}}:{{.Values.proxy.image.version | default .Values.linkerdVersion }}
 imagePullPolicy: {{.Values.proxy.image.pullPolicy | default .Values.imagePullPolicy}}
-{{ include "partials.resources" .Values.proxyInit.resources }}
+{{ include "partials.resources" .Values.proxy.resources }}
 {{- if or .Values.networkValidator.enableSecurityContext }}
 securityContext:
   allowPrivilegeEscalation: false
diff --git a/charts/partials/templates/_proxy-init.tpl b/charts/partials/templates/_proxy-init.tpl
index 95cd7f2c4..a307b1407 100644
--- a/charts/partials/templates/_proxy-init.tpl
+++ b/charts/partials/templates/_proxy-init.tpl
@@ -46,7 +46,7 @@ args:
 image: {{.Values.proxyInit.image.name}}:{{.Values.proxyInit.image.version}}
 imagePullPolicy: {{.Values.proxyInit.image.pullPolicy | default .Values.imagePullPolicy}}
 name: linkerd-init
-{{ include "partials.resources" .Values.proxyInit.resources }}
+{{ include "partials.resources" .Values.proxy.resources }}
 securityContext:
   {{- if or .Values.proxyInit.closeWaitTimeoutSecs .Values.proxyInit.privileged }}
   allowPrivilegeEscalation: true
diff --git a/controller/proxy-injector/fake/data/pod-with-debug.patch.json b/controller/proxy-injector/fake/data/pod-with-debug.patch.json
index ba82e0c71..b13285617 100644
--- a/controller/proxy-injector/fake/data/pod-with-debug.patch.json
+++ b/controller/proxy-injector/fake/data/pod-with-debug.patch.json
@@ -62,16 +62,7 @@
       "image": "cr.l5d.io/linkerd/proxy-init:v2.4.1",
       "imagePullPolicy": "IfNotPresent",
       "name": "linkerd-init",
-      "resources": {
-        "limits": {
-          "cpu": "100m",
-          "memory": "20Mi"
-        },
-        "requests": {
-          "cpu": "100m",
-          "memory": "20Mi"
-        }
-      },
+      "resources": null,
       "securityContext": {
         "allowPrivilegeEscalation": false,
         "capabilities": {
diff --git a/controller/proxy-injector/fake/data/pod-with-ns-annotations.patch.json b/controller/proxy-injector/fake/data/pod-with-ns-annotations.patch.json
index a814c6617..15aa5b336 100644
--- a/controller/proxy-injector/fake/data/pod-with-ns-annotations.patch.json
+++ b/controller/proxy-injector/fake/data/pod-with-ns-annotations.patch.json
@@ -72,16 +72,7 @@
       "image": "cr.l5d.io/linkerd/proxy-init:v2.4.1",
       "imagePullPolicy": "IfNotPresent",
       "name": "linkerd-init",
-      "resources": {
-        "limits": {
-          "cpu": "100m",
-          "memory": "20Mi"
-        },
-        "requests": {
-          "cpu": "100m",
-          "memory": "20Mi"
-        }
-      },
+      "resources": null,
       "securityContext": {
         "allowPrivilegeEscalation": false,
         "capabilities": {
diff --git a/controller/proxy-injector/fake/data/pod.patch.json b/controller/proxy-injector/fake/data/pod.patch.json
index d6cead305..7418ecb0d 100644
--- a/controller/proxy-injector/fake/data/pod.patch.json
+++ b/controller/proxy-injector/fake/data/pod.patch.json
@@ -62,16 +62,7 @@
       "image": "cr.l5d.io/linkerd/proxy-init:v2.4.1",
       "imagePullPolicy": "IfNotPresent",
       "name": "linkerd-init",
-      "resources": {
-        "limits": {
-          "cpu": "100m",
-          "memory": "20Mi"
-        },
-        "requests": {
-          "cpu": "100m",
-          "memory": "20Mi"
-        }
-      },
+      "resources": null,
       "securityContext": {
         "allowPrivilegeEscalation": false,
         "capabilities": {
diff --git a/go.mod b/go.mod
index 726a3a368..c92166d8a 100644
--- a/go.mod
+++ b/go.mod
@@ -28,7 +28,7 @@ require (
 	github.com/pkg/browser v0.0.0-20170505125900-c90ca0c84f15
 	github.com/prometheus/client_golang v1.19.1
 	github.com/prometheus/client_model v0.6.1
-	github.com/prometheus/common v0.54.0
+	github.com/prometheus/common v0.55.0
 	github.com/sergi/go-diff v1.3.1
 	github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
 	github.com/sirupsen/logrus v1.9.3
@@ -121,7 +121,7 @@ require (
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-	github.com/prometheus/procfs v0.12.0 // indirect
+	github.com/prometheus/procfs v0.15.1 // indirect
 	github.com/rivo/uniseg v0.4.4 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/shopspring/decimal v1.3.1 // indirect
@@ -139,7 +139,7 @@ require (
 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
 	golang.org/x/crypto v0.24.0 // indirect
 	golang.org/x/mod v0.18.0 // indirect
-	golang.org/x/oauth2 v0.19.0 // indirect
+	golang.org/x/oauth2 v0.21.0 // indirect
 	golang.org/x/sync v0.7.0 // indirect
 	golang.org/x/sys v0.21.0 // indirect
 	golang.org/x/term v0.21.0 // indirect
diff --git a/go.sum b/go.sum
index 2ef306201..720ab9171 100644
--- a/go.sum
+++ b/go.sum
@@ -373,13 +373,13 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p
 github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
-github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8=
-github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
-github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
 github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -536,8 +536,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
-golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/jaeger/charts/linkerd-jaeger/README.md b/jaeger/charts/linkerd-jaeger/README.md
index baa0d33b6..e0d5a5f79 100644
--- a/jaeger/charts/linkerd-jaeger/README.md
+++ b/jaeger/charts/linkerd-jaeger/README.md
@@ -113,7 +113,7 @@ Kubernetes: `>=1.22.0-0`
 | namespaceMetadata.image.name | string | `"extension-init"` | Docker image name for the namespace-metadata instance |
 | namespaceMetadata.image.pullPolicy | string | imagePullPolicy | Pull policy for the namespace-metadata instance |
 | namespaceMetadata.image.registry | string | `"cr.l5d.io/linkerd"` | Docker registry for the namespace-metadata instance |
-| namespaceMetadata.image.tag | string | `"v0.1.0"` | Docker image tag for the namespace-metadata instance |
+| namespaceMetadata.image.tag | string | `"v0.1.1"` | Docker image tag for the namespace-metadata instance |
 | namespaceMetadata.nodeSelector | object | `{"kubernetes.io/os":"linux"}` | NodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
 | namespaceMetadata.tolerations | string | `nil` | Tolerations section, See the [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for more information |
 | nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Default nodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
diff --git a/jaeger/charts/linkerd-jaeger/values.yaml b/jaeger/charts/linkerd-jaeger/values.yaml
index eda4b73ed..1d98bf526 100644
--- a/jaeger/charts/linkerd-jaeger/values.yaml
+++ b/jaeger/charts/linkerd-jaeger/values.yaml
@@ -236,7 +236,7 @@ namespaceMetadata:
     # -- Docker image name for the namespace-metadata instance
     name: extension-init
     # -- Docker image tag for the namespace-metadata instance
-    tag: v0.1.0
+    tag: v0.1.1
     # -- Pull policy for the namespace-metadata instance
     # @default -- imagePullPolicy
     pullPolicy: ""
diff --git a/multicluster/charts/linkerd-multicluster/README.md b/multicluster/charts/linkerd-multicluster/README.md
index 70aeff11d..7313a1998 100644
--- a/multicluster/charts/linkerd-multicluster/README.md
+++ b/multicluster/charts/linkerd-multicluster/README.md
@@ -96,7 +96,7 @@ Kubernetes: `>=1.22.0-0`
 | namespaceMetadata.image.name | string | `"extension-init"` | Docker image name for the namespace-metadata instance |
 | namespaceMetadata.image.pullPolicy | string | imagePullPolicy | Pull policy for the namespace-metadata instance |
 | namespaceMetadata.image.registry | string | `"cr.l5d.io/linkerd"` | Docker registry for the namespace-metadata instance |
-| namespaceMetadata.image.tag | string | `"v0.1.0"` | Docker image tag for the namespace-metadata instance |
+| namespaceMetadata.image.tag | string | `"v0.1.1"` | Docker image tag for the namespace-metadata instance |
 | namespaceMetadata.nodeSelector | object | `{}` | Node selectors for the namespace-metadata instance |
 | namespaceMetadata.tolerations | list | `[]` | Tolerations for the namespace-metadata instance |
 | podLabels | object | `{}` | Additional labels to add to all pods |
diff --git a/multicluster/charts/linkerd-multicluster/values.yaml b/multicluster/charts/linkerd-multicluster/values.yaml
index a44f073b1..11377cdae 100644
--- a/multicluster/charts/linkerd-multicluster/values.yaml
+++ b/multicluster/charts/linkerd-multicluster/values.yaml
@@ -78,7 +78,7 @@ namespaceMetadata:
     # -- Docker image name for the namespace-metadata instance
     name: extension-init
     # -- Docker image tag for the namespace-metadata instance
-    tag: v0.1.0
+    tag: v0.1.1
     # -- Pull policy for the namespace-metadata instance
     # @default -- imagePullPolicy
     pullPolicy: ""
diff --git a/pkg/charts/linkerd2/values.go b/pkg/charts/linkerd2/values.go
index 2949c418e..d34bb3730 100644
--- a/pkg/charts/linkerd2/values.go
+++ b/pkg/charts/linkerd2/values.go
@@ -169,23 +169,24 @@ type (
 
 	// ProxyInit contains the fields to set the proxy-init container
 	ProxyInit struct {
-		Capabilities         *Capabilities    `json:"capabilities"`
-		IgnoreInboundPorts   string           `json:"ignoreInboundPorts"`
-		IgnoreOutboundPorts  string           `json:"ignoreOutboundPorts"`
-		KubeAPIServerPorts   string           `json:"kubeAPIServerPorts"`
-		SkipSubnets          string           `json:"skipSubnets"`
-		LogLevel             string           `json:"logLevel"`
-		LogFormat            string           `json:"logFormat"`
-		Image                *Image           `json:"image"`
-		SAMountPath          *VolumeMountPath `json:"saMountPath"`
-		XTMountPath          *VolumeMountPath `json:"xtMountPath"`
-		Resources            *Resources       `json:"resources"`
-		CloseWaitTimeoutSecs int64            `json:"closeWaitTimeoutSecs"`
-		Privileged           bool             `json:"privileged"`
-		RunAsRoot            bool             `json:"runAsRoot"`
-		RunAsUser            int64            `json:"runAsUser"`
-		RunAsGroup           int64            `json:"runAsGroup"`
-		IptablesMode         string           `json:"iptablesMode"`
+		Capabilities        *Capabilities    `json:"capabilities"`
+		IgnoreInboundPorts  string           `json:"ignoreInboundPorts"`
+		IgnoreOutboundPorts string           `json:"ignoreOutboundPorts"`
+		KubeAPIServerPorts  string           `json:"kubeAPIServerPorts"`
+		SkipSubnets         string           `json:"skipSubnets"`
+		LogLevel            string           `json:"logLevel"`
+		LogFormat           string           `json:"logFormat"`
+		Image               *Image           `json:"image"`
+		SAMountPath         *VolumeMountPath `json:"saMountPath"`
+		XTMountPath         *VolumeMountPath `json:"xtMountPath"`
+		/* DEPRECATED: should be removed after stable-2.16.0, left in for bc */
+		Resources            *Resources `json:"resources"`
+		CloseWaitTimeoutSecs int64      `json:"closeWaitTimeoutSecs"`
+		Privileged           bool       `json:"privileged"`
+		RunAsRoot            bool       `json:"runAsRoot"`
+		RunAsUser            int64      `json:"runAsUser"`
+		RunAsGroup           int64      `json:"runAsGroup"`
+		IptablesMode         string     `json:"iptablesMode"`
 	}
 
 	NetworkValidator struct {
diff --git a/policy-controller/core/src/outbound.rs b/policy-controller/core/src/outbound.rs
index 4f5cb40c3..a895fb961 100644
--- a/policy-controller/core/src/outbound.rs
+++ b/policy-controller/core/src/outbound.rs
@@ -1,6 +1,6 @@
 use crate::routes::{
-    FailureInjectorFilter, GroupKindNamespaceName, HeaderModifierFilter, HostMatch, HttpRouteMatch,
-    RequestRedirectFilter,
+    FailureInjectorFilter, GroupKindNamespaceName, GrpcRouteMatch, HeaderModifierFilter, HostMatch,
+    HttpRouteMatch, RequestRedirectFilter,
 };
 use ahash::AHashMap as HashMap;
 use anyhow::Result;
@@ -20,6 +20,8 @@ pub trait DiscoverOutboundPolicy<T> {
 
 pub type OutboundPolicyStream = Pin<Box<dyn Stream<Item = OutboundPolicy> + Send + Sync + 'static>>;
 
+pub type RouteSet<M> = HashMap<GroupKindNamespaceName, OutboundRoute<M>>;
+
 pub struct OutboundDiscoverTarget {
     pub service_name: String,
     pub service_namespace: String,
@@ -27,21 +29,10 @@ pub struct OutboundDiscoverTarget {
     pub source_namespace: String,
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub enum TypedOutboundRoute {
-    Http(OutboundRoute<HttpRouteMatch>),
-}
-
-#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub enum OutboundRouteCollection {
-    #[default]
-    Empty,
-    Http(HashMap<GroupKindNamespaceName, OutboundRoute<HttpRouteMatch>>),
-}
-
 #[derive(Clone, Debug, PartialEq)]
 pub struct OutboundPolicy {
-    pub routes: OutboundRouteCollection,
+    pub http_routes: RouteSet<HttpRouteMatch>,
+    pub grpc_routes: RouteSet<GrpcRouteMatch>,
     pub authority: String,
     pub name: String,
     pub namespace: String,
@@ -51,9 +42,9 @@ pub struct OutboundPolicy {
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
-pub struct OutboundRoute<MatchType> {
+pub struct OutboundRoute<M> {
     pub hostnames: Vec<HostMatch>,
-    pub rules: Vec<OutboundRouteRule<MatchType>>,
+    pub rules: Vec<OutboundRouteRule<M>>,
 
     /// This is required for ordering returned routes
     /// by their creation timestamp.
@@ -61,8 +52,8 @@ pub struct OutboundRoute<MatchType> {
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
-pub struct OutboundRouteRule<MatchType> {
-    pub matches: Vec<MatchType>,
+pub struct OutboundRouteRule<M> {
+    pub matches: Vec<M>,
     pub backends: Vec<Backend>,
     pub request_timeout: Option<time::Duration>,
     pub backend_request_timeout: Option<time::Duration>,
@@ -113,51 +104,3 @@ pub enum Filter {
     RequestRedirect(RequestRedirectFilter),
     FailureInjector(FailureInjectorFilter),
 }
-
-// === impl TypedOutboundRoute ===
-
-impl From<OutboundRoute<HttpRouteMatch>> for TypedOutboundRoute {
-    fn from(route: OutboundRoute<HttpRouteMatch>) -> Self {
-        Self::Http(route)
-    }
-}
-
-// === impl OutboundRouteCollection ===
-
-impl OutboundRouteCollection {
-    pub fn is_empty(&self) -> bool {
-        matches!(self, Self::Empty)
-    }
-
-    pub fn remove(&mut self, key: &GroupKindNamespaceName) {
-        match self {
-            Self::Empty => {}
-            Self::Http(routes) => {
-                routes.remove(key);
-                if routes.is_empty() {
-                    *self = Self::Empty;
-                }
-            }
-        }
-    }
-
-    pub fn insert<Route: Into<TypedOutboundRoute>>(
-        &mut self,
-        key: GroupKindNamespaceName,
-        route: Route,
-    ) -> Result<Option<TypedOutboundRoute>> {
-        let route = route.into();
-
-        match (self, route) {
-            (this @ Self::Empty, TypedOutboundRoute::Http(route)) => {
-                let mut routes = HashMap::default();
-                let inserted = routes.insert(key, route).map(Into::into);
-                *this = Self::Http(routes);
-                Ok(inserted)
-            }
-            (Self::Http(routes), TypedOutboundRoute::Http(route)) => {
-                Ok(routes.insert(key, route).map(Into::into))
-            }
-        }
-    }
-}
diff --git a/policy-controller/core/src/routes.rs b/policy-controller/core/src/routes.rs
index 41cbf7ae3..4051ea559 100644
--- a/policy-controller/core/src/routes.rs
+++ b/policy-controller/core/src/routes.rs
@@ -72,6 +72,12 @@ pub struct HttpRouteMatch {
     pub method: Option<Method>,
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct GrpcRouteMatch {
+    pub headers: Vec<HeaderMatch>,
+    pub method: Option<GrpcMethodMatch>,
+}
+
 #[derive(Clone, Debug)]
 pub enum PathMatch {
     Exact(String),
@@ -91,6 +97,12 @@ pub enum QueryParamMatch {
     Regex(String, Regex),
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct GrpcMethodMatch {
+    pub method: Option<String>,
+    pub service: Option<String>,
+}
+
 // === impl GroupKindName ===
 
 impl Ord for GroupKindName {
diff --git a/policy-controller/grpc/Cargo.toml b/policy-controller/grpc/Cargo.toml
index 0c6251327..3d58542a4 100644
--- a/policy-controller/grpc/Cargo.toml
+++ b/policy-controller/grpc/Cargo.toml
@@ -12,7 +12,6 @@ http = "0.2"
 drain = "0.1"
 futures = { version = "0.3", default-features = false }
 hyper = { version = "0.14", features = ["http2", "server", "tcp"] }
-itertools = "0.12"
 linkerd-policy-controller-core = { path = "../core" }
 maplit = "1"
 prost-types = "0.12.6"
diff --git a/policy-controller/grpc/src/outbound.rs b/policy-controller/grpc/src/outbound.rs
index 41adb37b5..7d01cb001 100644
--- a/policy-controller/grpc/src/outbound.rs
+++ b/policy-controller/grpc/src/outbound.rs
@@ -1,8 +1,10 @@
-use crate::{routes, workload};
+extern crate http as http_crate;
+
+use crate::workload;
 use futures::prelude::*;
-use itertools::Itertools;
+use http_crate::uri::Authority;
 use linkerd2_proxy_api::{
-    self as api, destination,
+    self as api,
     meta::{metadata, Metadata},
     outbound::{
         self,
@@ -11,12 +13,15 @@ use linkerd2_proxy_api::{
 };
 use linkerd_policy_controller_core::{
     outbound::{
-        Backend, DiscoverOutboundPolicy, Filter, OutboundDiscoverTarget, OutboundPolicy,
-        OutboundPolicyStream, OutboundRoute, OutboundRouteCollection, OutboundRouteRule,
+        DiscoverOutboundPolicy, OutboundDiscoverTarget, OutboundPolicy, OutboundPolicyStream,
+        OutboundRoute,
     },
-    routes::{GroupKindNamespaceName, HttpRouteMatch},
+    routes::GroupKindNamespaceName,
 };
-use std::{net::SocketAddr, num::NonZeroU16, str::FromStr, sync::Arc, time};
+use std::{num::NonZeroU16, str::FromStr, sync::Arc, time};
+
+mod grpc;
+mod http;
 
 #[derive(Clone, Debug)]
 pub struct OutboundPolicyServer<T> {
@@ -86,7 +91,7 @@ where
         authority: &str,
     ) -> Result<(String, String, NonZeroU16), tonic::Status> {
         let auth = authority
-            .parse::<http::uri::Authority>()
+            .parse::<Authority>()
             .map_err(|_| tonic::Status::invalid_argument("invalid authority"))?;
 
         let mut host = auth.host();
@@ -200,7 +205,7 @@ fn response_stream(drain: drain::Watch, mut rx: OutboundPolicyStream) -> BoxWatc
 }
 
 fn to_service(outbound: OutboundPolicy) -> outbound::OutboundPolicy {
-    let backend = default_backend(&outbound);
+    let backend: outbound::Backend = default_backend(&outbound);
 
     let kind = if outbound.opaque {
         outbound::proxy_protocol::Kind::Opaque(outbound::proxy_protocol::Opaque {
@@ -225,55 +230,15 @@ fn to_service(outbound: OutboundPolicy) -> outbound::OutboundPolicy {
             }),
         });
 
-        match outbound.routes {
-            OutboundRouteCollection::Empty => {
-                let routes = vec![default_outbound_http_route(backend.clone())];
-
-                outbound::proxy_protocol::Kind::Detect(outbound::proxy_protocol::Detect {
-                    timeout: Some(
-                        time::Duration::from_secs(10)
-                            .try_into()
-                            .expect("failed to convert detect timeout to protobuf"),
-                    ),
-                    opaque: Some(outbound::proxy_protocol::Opaque {
-                        routes: vec![default_outbound_opaq_route(backend)],
-                    }),
-                    http1: Some(outbound::proxy_protocol::Http1 {
-                        routes: routes.clone(),
-                        failure_accrual: accrual.clone(),
-                    }),
-                    http2: Some(outbound::proxy_protocol::Http2 {
-                        routes,
-                        failure_accrual: accrual,
-                    }),
-                })
-            }
-            OutboundRouteCollection::Http(routes) => {
-                let routes = routes
-                    .into_iter()
-                    .sorted_by(timestamp_then_name)
-                    .map(|(gknn, route)| convert_outbound_http_route(gknn, route, backend.clone()))
-                    .collect::<Vec<_>>();
-
-                outbound::proxy_protocol::Kind::Detect(outbound::proxy_protocol::Detect {
-                    timeout: Some(
-                        time::Duration::from_secs(10)
-                            .try_into()
-                            .expect("failed to convert detect timeout to protobuf"),
-                    ),
-                    opaque: Some(outbound::proxy_protocol::Opaque {
-                        routes: vec![default_outbound_opaq_route(backend)],
-                    }),
-                    http1: Some(outbound::proxy_protocol::Http1 {
-                        routes: routes.clone(),
-                        failure_accrual: accrual.clone(),
-                    }),
-                    http2: Some(outbound::proxy_protocol::Http2 {
-                        routes,
-                        failure_accrual: accrual,
-                    }),
-                })
-            }
+        let mut http_routes = outbound.http_routes.into_iter().collect::<Vec<_>>();
+        let mut grpc_routes = outbound.grpc_routes.into_iter().collect::<Vec<_>>();
+
+        if !grpc_routes.is_empty() {
+            grpc_routes.sort_by(timestamp_then_name);
+            grpc::protocol(backend, grpc_routes.into_iter(), accrual)
+        } else {
+            http_routes.sort_by(timestamp_then_name);
+            http::protocol(backend, http_routes.into_iter(), accrual)
         }
     };
 
@@ -294,9 +259,9 @@ fn to_service(outbound: OutboundPolicy) -> outbound::OutboundPolicy {
     }
 }
 
-fn timestamp_then_name<MatchType>(
-    (left_id, left_route): &(GroupKindNamespaceName, OutboundRoute<MatchType>),
-    (right_id, right_route): &(GroupKindNamespaceName, OutboundRoute<MatchType>),
+fn timestamp_then_name<LeftMatchType, RightMatchType>(
+    (left_id, left_route): &(GroupKindNamespaceName, OutboundRoute<LeftMatchType>),
+    (right_id, right_route): &(GroupKindNamespaceName, OutboundRoute<RightMatchType>),
 ) -> std::cmp::Ordering {
     let by_ts = match (
         &left_route.creation_timestamp,
@@ -312,197 +277,6 @@ fn timestamp_then_name<MatchType>(
     by_ts.then_with(|| left_id.name.cmp(&right_id.name))
 }
 
-fn convert_outbound_http_route(
-    gknn: GroupKindNamespaceName,
-    OutboundRoute {
-        hostnames,
-        rules,
-        creation_timestamp: _,
-    }: OutboundRoute<HttpRouteMatch>,
-    backend: outbound::Backend,
-) -> outbound::HttpRoute {
-    let metadata = Some(Metadata {
-        kind: Some(metadata::Kind::Resource(api::meta::Resource {
-            group: gknn.group.to_string(),
-            kind: gknn.kind.to_string(),
-            namespace: gknn.namespace.to_string(),
-            name: gknn.name.to_string(),
-            ..Default::default()
-        })),
-    });
-
-    let hosts = hostnames
-        .into_iter()
-        .map(routes::convert_host_match)
-        .collect();
-
-    let rules = rules
-        .into_iter()
-        .map(
-            |OutboundRouteRule {
-                 matches,
-                 backends,
-                 request_timeout,
-                 backend_request_timeout,
-                 filters,
-             }| {
-                let backend_request_timeout = backend_request_timeout
-                    .and_then(|d| convert_duration("backend request_timeout", d));
-                let backends = backends
-                    .into_iter()
-                    .map(|backend| convert_http_backend(backend_request_timeout.clone(), backend))
-                    .collect::<Vec<_>>();
-                let dist = if backends.is_empty() {
-                    outbound::http_route::distribution::Kind::FirstAvailable(
-                        outbound::http_route::distribution::FirstAvailable {
-                            backends: vec![outbound::http_route::RouteBackend {
-                                backend: Some(backend.clone()),
-                                filters: vec![],
-                                request_timeout: backend_request_timeout,
-                            }],
-                        },
-                    )
-                } else {
-                    outbound::http_route::distribution::Kind::RandomAvailable(
-                        outbound::http_route::distribution::RandomAvailable { backends },
-                    )
-                };
-                outbound::http_route::Rule {
-                    matches: matches
-                        .into_iter()
-                        .map(routes::http::convert_match)
-                        .collect(),
-                    backends: Some(outbound::http_route::Distribution { kind: Some(dist) }),
-                    filters: filters.into_iter().map(convert_to_http_filter).collect(),
-                    request_timeout: request_timeout
-                        .and_then(|d| convert_duration("request timeout", d)),
-                }
-            },
-        )
-        .collect();
-
-    outbound::HttpRoute {
-        metadata,
-        hosts,
-        rules,
-    }
-}
-
-fn convert_http_backend(
-    request_timeout: Option<prost_types::Duration>,
-    backend: Backend,
-) -> outbound::http_route::WeightedRouteBackend {
-    match backend {
-        Backend::Addr(addr) => {
-            let socket_addr = SocketAddr::new(addr.addr, addr.port.get());
-            outbound::http_route::WeightedRouteBackend {
-                weight: addr.weight,
-                backend: Some(outbound::http_route::RouteBackend {
-                    backend: Some(outbound::Backend {
-                        metadata: None,
-                        queue: Some(default_queue_config()),
-                        kind: Some(outbound::backend::Kind::Forward(
-                            destination::WeightedAddr {
-                                addr: Some(socket_addr.into()),
-                                weight: addr.weight,
-                                ..Default::default()
-                            },
-                        )),
-                    }),
-                    filters: Default::default(),
-                    request_timeout,
-                }),
-            }
-        }
-        Backend::Service(svc) => {
-            if svc.exists {
-                let filters = svc
-                    .filters
-                    .into_iter()
-                    .map(convert_to_http_filter)
-                    .collect();
-                outbound::http_route::WeightedRouteBackend {
-                    weight: svc.weight,
-                    backend: Some(outbound::http_route::RouteBackend {
-                        backend: Some(outbound::Backend {
-                            metadata: Some(Metadata {
-                                kind: Some(metadata::Kind::Resource(api::meta::Resource {
-                                    group: "core".to_string(),
-                                    kind: "Service".to_string(),
-                                    name: svc.name,
-                                    namespace: svc.namespace,
-                                    section: Default::default(),
-                                    port: u16::from(svc.port).into(),
-                                })),
-                            }),
-                            queue: Some(default_queue_config()),
-                            kind: Some(outbound::backend::Kind::Balancer(
-                                outbound::backend::BalanceP2c {
-                                    discovery: Some(outbound::backend::EndpointDiscovery {
-                                        kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(
-                                            outbound::backend::endpoint_discovery::DestinationGet {
-                                                path: svc.authority,
-                                            },
-                                        )),
-                                    }),
-                                    load: Some(default_balancer_config()),
-                                },
-                            )),
-                        }),
-                        filters,
-                        request_timeout,
-                    }),
-                }
-            } else {
-                outbound::http_route::WeightedRouteBackend {
-                    weight: svc.weight,
-                    backend: Some(outbound::http_route::RouteBackend {
-                        backend: Some(outbound::Backend {
-                            metadata: Some(Metadata {
-                                kind: Some(metadata::Kind::Default("invalid".to_string())),
-                            }),
-                            queue: Some(default_queue_config()),
-                            kind: None,
-                        }),
-                        filters: vec![outbound::http_route::Filter {
-                            kind: Some(outbound::http_route::filter::Kind::FailureInjector(
-                                api::http_route::HttpFailureInjector {
-                                    status: 500,
-                                    message: format!("Service not found {}", svc.name),
-                                    ratio: None,
-                                },
-                            )),
-                        }],
-                        request_timeout,
-                    }),
-                }
-            }
-        }
-        Backend::Invalid { weight, message } => outbound::http_route::WeightedRouteBackend {
-            weight,
-            backend: Some(outbound::http_route::RouteBackend {
-                backend: Some(outbound::Backend {
-                    metadata: Some(Metadata {
-                        kind: Some(metadata::Kind::Default("invalid".to_string())),
-                    }),
-                    queue: Some(default_queue_config()),
-                    kind: None,
-                }),
-                filters: vec![outbound::http_route::Filter {
-                    kind: Some(outbound::http_route::filter::Kind::FailureInjector(
-                        api::http_route::HttpFailureInjector {
-                            status: 500,
-                            message,
-                            ratio: None,
-                        },
-                    )),
-                }],
-                request_timeout,
-            }),
-        },
-    }
-}
-
 fn default_backend(outbound: &OutboundPolicy) -> outbound::Backend {
     outbound::Backend {
         metadata: Some(Metadata {
@@ -531,38 +305,6 @@ fn default_backend(outbound: &OutboundPolicy) -> outbound::Backend {
     }
 }
 
-fn default_outbound_http_route(backend: outbound::Backend) -> outbound::HttpRoute {
-    let metadata = Some(Metadata {
-        kind: Some(metadata::Kind::Default("http".to_string())),
-    });
-    let rules = vec![outbound::http_route::Rule {
-        matches: vec![api::http_route::HttpRouteMatch {
-            path: Some(api::http_route::PathMatch {
-                kind: Some(api::http_route::path_match::Kind::Prefix("/".to_string())),
-            }),
-            ..Default::default()
-        }],
-        backends: Some(outbound::http_route::Distribution {
-            kind: Some(outbound::http_route::distribution::Kind::FirstAvailable(
-                outbound::http_route::distribution::FirstAvailable {
-                    backends: vec![outbound::http_route::RouteBackend {
-                        backend: Some(backend),
-                        filters: vec![],
-                        request_timeout: None,
-                    }],
-                },
-            )),
-        }),
-        filters: Default::default(),
-        request_timeout: None,
-    }];
-    outbound::HttpRoute {
-        metadata,
-        rules,
-        ..Default::default()
-    }
-}
-
 fn default_outbound_opaq_route(backend: outbound::Backend) -> outbound::OpaqueRoute {
     let metadata = Some(Metadata {
         kind: Some(metadata::Kind::Default("opaq".to_string())),
@@ -607,7 +349,10 @@ fn default_queue_config() -> outbound::Queue {
     }
 }
 
-fn convert_duration(name: &'static str, duration: time::Duration) -> Option<prost_types::Duration> {
+pub(crate) fn convert_duration(
+    name: &'static str,
+    duration: time::Duration,
+) -> Option<prost_types::Duration> {
     duration
         .try_into()
         .map_err(|error| {
@@ -615,22 +360,3 @@ fn convert_duration(name: &'static str, duration: time::Duration) -> Option<pros
         })
         .ok()
 }
-
-fn convert_to_http_filter(filter: Filter) -> outbound::http_route::Filter {
-    use outbound::http_route::filter::Kind;
-
-    outbound::http_route::Filter {
-        kind: Some(match filter {
-            Filter::RequestHeaderModifier(f) => {
-                Kind::RequestHeaderModifier(routes::convert_request_header_modifier_filter(f))
-            }
-            Filter::ResponseHeaderModifier(f) => {
-                Kind::ResponseHeaderModifier(routes::convert_response_header_modifier_filter(f))
-            }
-            Filter::RequestRedirect(f) => Kind::Redirect(routes::convert_redirect_filter(f)),
-            Filter::FailureInjector(f) => {
-                Kind::FailureInjector(routes::http::convert_failure_injector_filter(f))
-            }
-        }),
-    }
-}
diff --git a/policy-controller/grpc/src/outbound/grpc.rs b/policy-controller/grpc/src/outbound/grpc.rs
new file mode 100644
index 000000000..0924bc0dc
--- /dev/null
+++ b/policy-controller/grpc/src/outbound/grpc.rs
@@ -0,0 +1,240 @@
+use std::net::SocketAddr;
+
+use super::{convert_duration, default_balancer_config, default_queue_config};
+use crate::routes::{
+    convert_host_match, convert_request_header_modifier_filter, grpc::convert_match,
+};
+use linkerd2_proxy_api::{destination, grpc_route, http_route, meta, outbound};
+use linkerd_policy_controller_core::{
+    outbound::{Backend, Filter, OutboundRoute, OutboundRouteRule},
+    routes::{FailureInjectorFilter, GroupKindNamespaceName, GrpcRouteMatch},
+};
+
+pub(crate) fn protocol(
+    default_backend: outbound::Backend,
+    routes: impl Iterator<Item = (GroupKindNamespaceName, OutboundRoute<GrpcRouteMatch>)>,
+    failure_accrual: Option<outbound::FailureAccrual>,
+) -> outbound::proxy_protocol::Kind {
+    let routes = routes
+        .map(|(gknn, route)| convert_outbound_route(gknn, route, default_backend.clone()))
+        .collect::<Vec<_>>();
+    outbound::proxy_protocol::Kind::Grpc(outbound::proxy_protocol::Grpc {
+        routes,
+        failure_accrual,
+    })
+}
+
+fn convert_outbound_route(
+    gknn: GroupKindNamespaceName,
+    OutboundRoute {
+        hostnames,
+        rules,
+        creation_timestamp: _,
+    }: OutboundRoute<GrpcRouteMatch>,
+    backend: outbound::Backend,
+) -> outbound::GrpcRoute {
+    let metadata = Some(meta::Metadata {
+        kind: Some(meta::metadata::Kind::Resource(meta::Resource {
+            group: gknn.group.to_string(),
+            kind: gknn.kind.to_string(),
+            namespace: gknn.namespace.to_string(),
+            name: gknn.name.to_string(),
+            ..Default::default()
+        })),
+    });
+
+    let hosts = hostnames.into_iter().map(convert_host_match).collect();
+
+    let rules = rules
+        .into_iter()
+        .map(
+            |OutboundRouteRule {
+                 matches,
+                 backends,
+                 request_timeout,
+                 backend_request_timeout,
+                 filters,
+             }| {
+                let backend_request_timeout = backend_request_timeout
+                    .and_then(|d| convert_duration("backend request_timeout", d));
+                let backends = backends
+                    .into_iter()
+                    .map(|backend| convert_backend(backend_request_timeout.clone(), backend))
+                    .collect::<Vec<_>>();
+                let dist = if backends.is_empty() {
+                    outbound::grpc_route::distribution::Kind::FirstAvailable(
+                        outbound::grpc_route::distribution::FirstAvailable {
+                            backends: vec![outbound::grpc_route::RouteBackend {
+                                backend: Some(backend.clone()),
+                                filters: vec![],
+                                request_timeout: backend_request_timeout,
+                            }],
+                        },
+                    )
+                } else {
+                    outbound::grpc_route::distribution::Kind::RandomAvailable(
+                        outbound::grpc_route::distribution::RandomAvailable { backends },
+                    )
+                };
+                outbound::grpc_route::Rule {
+                    matches: matches.into_iter().map(convert_match).collect(),
+                    backends: Some(outbound::grpc_route::Distribution { kind: Some(dist) }),
+                    filters: filters.into_iter().map(convert_to_filter).collect(),
+                    request_timeout: request_timeout
+                        .and_then(|d| convert_duration("request timeout", d)),
+                }
+            },
+        )
+        .collect();
+
+    outbound::GrpcRoute {
+        metadata,
+        hosts,
+        rules,
+    }
+}
+
+fn convert_backend(
+    request_timeout: Option<prost_types::Duration>,
+    backend: Backend,
+) -> outbound::grpc_route::WeightedRouteBackend {
+    match backend {
+        Backend::Addr(addr) => {
+            let socket_addr = SocketAddr::new(addr.addr, addr.port.get());
+            outbound::grpc_route::WeightedRouteBackend {
+                weight: addr.weight,
+                backend: Some(outbound::grpc_route::RouteBackend {
+                    backend: Some(outbound::Backend {
+                        metadata: None,
+                        queue: Some(default_queue_config()),
+                        kind: Some(outbound::backend::Kind::Forward(
+                            destination::WeightedAddr {
+                                addr: Some(socket_addr.into()),
+                                weight: addr.weight,
+                                ..Default::default()
+                            },
+                        )),
+                    }),
+                    filters: Default::default(),
+                    request_timeout,
+                }),
+            }
+        }
+        Backend::Service(svc) => {
+            if svc.exists {
+                let filters = svc.filters.into_iter().map(convert_to_filter).collect();
+                outbound::grpc_route::WeightedRouteBackend {
+                    weight: svc.weight,
+                    backend: Some(outbound::grpc_route::RouteBackend {
+                        backend: Some(outbound::Backend {
+                            metadata: Some(meta::Metadata {
+                                kind: Some(meta::metadata::Kind::Resource(meta::Resource {
+                                    group: "core".to_string(),
+                                    kind: "Service".to_string(),
+                                    name: svc.name,
+                                    namespace: svc.namespace,
+                                    section: Default::default(),
+                                    port: u16::from(svc.port).into(),
+                                })),
+                            }),
+                            queue: Some(default_queue_config()),
+                            kind: Some(outbound::backend::Kind::Balancer(
+                                outbound::backend::BalanceP2c {
+                                    discovery: Some(outbound::backend::EndpointDiscovery {
+                                        kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(
+                                            outbound::backend::endpoint_discovery::DestinationGet {
+                                                path: svc.authority,
+                                            },
+                                        )),
+                                    }),
+                                    load: Some(default_balancer_config()),
+                                },
+                            )),
+                        }),
+                        filters,
+                        request_timeout,
+                    }),
+                }
+            } else {
+                outbound::grpc_route::WeightedRouteBackend {
+                    weight: svc.weight,
+                    backend: Some(outbound::grpc_route::RouteBackend {
+                        backend: Some(outbound::Backend {
+                            metadata: Some(meta::Metadata {
+                                kind: Some(meta::metadata::Kind::Default("invalid".to_string())),
+                            }),
+                            queue: Some(default_queue_config()),
+                            kind: None,
+                        }),
+                        filters: vec![outbound::grpc_route::Filter {
+                            kind: Some(outbound::grpc_route::filter::Kind::FailureInjector(
+                                grpc_route::GrpcFailureInjector {
+                                    code: 500,
+                                    message: format!("Service not found {}", svc.name),
+                                    ratio: None,
+                                },
+                            )),
+                        }],
+                        request_timeout,
+                    }),
+                }
+            }
+        }
+        Backend::Invalid { weight, message } => outbound::grpc_route::WeightedRouteBackend {
+            weight,
+            backend: Some(outbound::grpc_route::RouteBackend {
+                backend: Some(outbound::Backend {
+                    metadata: Some(meta::Metadata {
+                        kind: Some(meta::metadata::Kind::Default("invalid".to_string())),
+                    }),
+                    queue: Some(default_queue_config()),
+                    kind: None,
+                }),
+                filters: vec![outbound::grpc_route::Filter {
+                    kind: Some(outbound::grpc_route::filter::Kind::FailureInjector(
+                        grpc_route::GrpcFailureInjector {
+                            code: 500,
+                            message,
+                            ratio: None,
+                        },
+                    )),
+                }],
+                request_timeout,
+            }),
+        },
+    }
+}
+
+fn convert_to_filter(filter: Filter) -> outbound::grpc_route::Filter {
+    use outbound::grpc_route::filter::Kind as GrpcFilterKind;
+
+    outbound::grpc_route::Filter {
+        kind: match filter {
+            Filter::FailureInjector(FailureInjectorFilter {
+                status,
+                message,
+                ratio,
+            }) => Some(GrpcFilterKind::FailureInjector(
+                grpc_route::GrpcFailureInjector {
+                    code: u32::from(status.as_u16()),
+                    message,
+                    ratio: Some(http_route::Ratio {
+                        numerator: ratio.numerator,
+                        denominator: ratio.denominator,
+                    }),
+                },
+            )),
+            Filter::RequestHeaderModifier(filter) => Some(GrpcFilterKind::RequestHeaderModifier(
+                convert_request_header_modifier_filter(filter),
+            )),
+            Filter::RequestRedirect(filter) => {
+                tracing::warn!(filter = ?filter, "declining to convert invalid filter type for GrpcRoute");
+                None
+            }
+            Filter::ResponseHeaderModifier(filter) => {
+                tracing::warn!(filter = ?filter, "declining to convert invalid filter type for GrpcRoute");
+                None
+            }
+        },
+    }
+}
diff --git a/policy-controller/grpc/src/outbound/http.rs b/policy-controller/grpc/src/outbound/http.rs
new file mode 100644
index 000000000..65130251e
--- /dev/null
+++ b/policy-controller/grpc/src/outbound/http.rs
@@ -0,0 +1,278 @@
+use std::{net::SocketAddr, time};
+
+use super::{
+    convert_duration, default_balancer_config, default_outbound_opaq_route, default_queue_config,
+};
+use crate::routes::{
+    convert_host_match, convert_redirect_filter, convert_request_header_modifier_filter,
+    convert_response_header_modifier_filter,
+    http::{convert_failure_injector_filter, convert_match},
+};
+use linkerd2_proxy_api::{destination, http_route, meta, outbound};
+use linkerd_policy_controller_core::{
+    outbound::{Backend, Filter, OutboundRoute, OutboundRouteRule},
+    routes::{GroupKindNamespaceName, HttpRouteMatch},
+};
+
+pub(crate) fn protocol(
+    default_backend: outbound::Backend,
+    routes: impl Iterator<Item = (GroupKindNamespaceName, OutboundRoute<HttpRouteMatch>)>,
+    accrual: Option<outbound::FailureAccrual>,
+) -> outbound::proxy_protocol::Kind {
+    let opaque_route = default_outbound_opaq_route(default_backend.clone());
+    let mut routes = routes
+        .map(|(gknn, route)| convert_outbound_route(gknn, route, default_backend.clone()))
+        .collect::<Vec<_>>();
+    if routes.is_empty() {
+        routes.push(default_outbound_route(default_backend));
+    }
+    outbound::proxy_protocol::Kind::Detect(outbound::proxy_protocol::Detect {
+        timeout: Some(
+            time::Duration::from_secs(10)
+                .try_into()
+                .expect("failed to convert detect timeout to protobuf"),
+        ),
+
+        opaque: Some(outbound::proxy_protocol::Opaque {
+            routes: vec![opaque_route],
+        }),
+        http1: Some(outbound::proxy_protocol::Http1 {
+            routes: routes.clone(),
+            failure_accrual: accrual.clone(),
+        }),
+        http2: Some(outbound::proxy_protocol::Http2 {
+            routes,
+            failure_accrual: accrual,
+        }),
+    })
+}
+
+fn convert_outbound_route(
+    gknn: GroupKindNamespaceName,
+    OutboundRoute {
+        hostnames,
+        rules,
+        creation_timestamp: _,
+    }: OutboundRoute<HttpRouteMatch>,
+    backend: outbound::Backend,
+) -> outbound::HttpRoute {
+    let metadata = Some(meta::Metadata {
+        kind: Some(meta::metadata::Kind::Resource(meta::Resource {
+            group: gknn.group.to_string(),
+            kind: gknn.kind.to_string(),
+            namespace: gknn.namespace.to_string(),
+            name: gknn.name.to_string(),
+            ..Default::default()
+        })),
+    });
+
+    let hosts = hostnames.into_iter().map(convert_host_match).collect();
+
+    let rules = rules
+        .into_iter()
+        .map(
+            |OutboundRouteRule {
+                 matches,
+                 backends,
+                 request_timeout,
+                 backend_request_timeout,
+                 filters,
+             }| {
+                let backend_request_timeout = backend_request_timeout
+                    .and_then(|d| convert_duration("backend request_timeout", d));
+                let backends = backends
+                    .into_iter()
+                    .map(|backend| convert_backend(backend_request_timeout.clone(), backend))
+                    .collect::<Vec<_>>();
+                let dist = if backends.is_empty() {
+                    outbound::http_route::distribution::Kind::FirstAvailable(
+                        outbound::http_route::distribution::FirstAvailable {
+                            backends: vec![outbound::http_route::RouteBackend {
+                                backend: Some(backend.clone()),
+                                filters: vec![],
+                                request_timeout: backend_request_timeout,
+                            }],
+                        },
+                    )
+                } else {
+                    outbound::http_route::distribution::Kind::RandomAvailable(
+                        outbound::http_route::distribution::RandomAvailable { backends },
+                    )
+                };
+                outbound::http_route::Rule {
+                    matches: matches.into_iter().map(convert_match).collect(),
+                    backends: Some(outbound::http_route::Distribution { kind: Some(dist) }),
+                    filters: filters.into_iter().map(convert_to_filter).collect(),
+                    request_timeout: request_timeout
+                        .and_then(|d| convert_duration("request timeout", d)),
+                }
+            },
+        )
+        .collect();
+
+    outbound::HttpRoute {
+        metadata,
+        hosts,
+        rules,
+    }
+}
+
+fn convert_backend(
+    request_timeout: Option<prost_types::Duration>,
+    backend: Backend,
+) -> outbound::http_route::WeightedRouteBackend {
+    match backend {
+        Backend::Addr(addr) => {
+            let socket_addr = SocketAddr::new(addr.addr, addr.port.get());
+            outbound::http_route::WeightedRouteBackend {
+                weight: addr.weight,
+                backend: Some(outbound::http_route::RouteBackend {
+                    backend: Some(outbound::Backend {
+                        metadata: None,
+                        queue: Some(default_queue_config()),
+                        kind: Some(outbound::backend::Kind::Forward(
+                            destination::WeightedAddr {
+                                addr: Some(socket_addr.into()),
+                                weight: addr.weight,
+                                ..Default::default()
+                            },
+                        )),
+                    }),
+                    filters: Default::default(),
+                    request_timeout,
+                }),
+            }
+        }
+        Backend::Service(svc) => {
+            if svc.exists {
+                let filters = svc.filters.into_iter().map(convert_to_filter).collect();
+                outbound::http_route::WeightedRouteBackend {
+                    weight: svc.weight,
+                    backend: Some(outbound::http_route::RouteBackend {
+                        backend: Some(outbound::Backend {
+                            metadata: Some(meta::Metadata {
+                                kind: Some(meta::metadata::Kind::Resource(meta::Resource {
+                                    group: "core".to_string(),
+                                    kind: "Service".to_string(),
+                                    name: svc.name,
+                                    namespace: svc.namespace,
+                                    section: Default::default(),
+                                    port: u16::from(svc.port).into(),
+                                })),
+                            }),
+                            queue: Some(default_queue_config()),
+                            kind: Some(outbound::backend::Kind::Balancer(
+                                outbound::backend::BalanceP2c {
+                                    discovery: Some(outbound::backend::EndpointDiscovery {
+                                        kind: Some(outbound::backend::endpoint_discovery::Kind::Dst(
+                                            outbound::backend::endpoint_discovery::DestinationGet {
+                                                path: svc.authority,
+                                            },
+                                        )),
+                                    }),
+                                    load: Some(default_balancer_config()),
+                                },
+                            )),
+                        }),
+                        filters,
+                        request_timeout,
+                    }),
+                }
+            } else {
+                outbound::http_route::WeightedRouteBackend {
+                    weight: svc.weight,
+                    backend: Some(outbound::http_route::RouteBackend {
+                        backend: Some(outbound::Backend {
+                            metadata: Some(meta::Metadata {
+                                kind: Some(meta::metadata::Kind::Default("invalid".to_string())),
+                            }),
+                            queue: Some(default_queue_config()),
+                            kind: None,
+                        }),
+                        filters: vec![outbound::http_route::Filter {
+                            kind: Some(outbound::http_route::filter::Kind::FailureInjector(
+                                http_route::HttpFailureInjector {
+                                    status: 500,
+                                    message: format!("Service not found {}", svc.name),
+                                    ratio: None,
+                                },
+                            )),
+                        }],
+                        request_timeout,
+                    }),
+                }
+            }
+        }
+        Backend::Invalid { weight, message } => outbound::http_route::WeightedRouteBackend {
+            weight,
+            backend: Some(outbound::http_route::RouteBackend {
+                backend: Some(outbound::Backend {
+                    metadata: Some(meta::Metadata {
+                        kind: Some(meta::metadata::Kind::Default("invalid".to_string())),
+                    }),
+                    queue: Some(default_queue_config()),
+                    kind: None,
+                }),
+                filters: vec![outbound::http_route::Filter {
+                    kind: Some(outbound::http_route::filter::Kind::FailureInjector(
+                        http_route::HttpFailureInjector {
+                            status: 500,
+                            message,
+                            ratio: None,
+                        },
+                    )),
+                }],
+                request_timeout,
+            }),
+        },
+    }
+}
+
+pub(crate) fn default_outbound_route(backend: outbound::Backend) -> outbound::HttpRoute {
+    let metadata = Some(meta::Metadata {
+        kind: Some(meta::metadata::Kind::Default("http".to_string())),
+    });
+    let rules = vec![outbound::http_route::Rule {
+        matches: vec![http_route::HttpRouteMatch {
+            path: Some(http_route::PathMatch {
+                kind: Some(http_route::path_match::Kind::Prefix("/".to_string())),
+            }),
+            ..Default::default()
+        }],
+        backends: Some(outbound::http_route::Distribution {
+            kind: Some(outbound::http_route::distribution::Kind::FirstAvailable(
+                outbound::http_route::distribution::FirstAvailable {
+                    backends: vec![outbound::http_route::RouteBackend {
+                        backend: Some(backend),
+                        filters: vec![],
+                        request_timeout: None,
+                    }],
+                },
+            )),
+        }),
+        filters: Default::default(),
+        request_timeout: None,
+    }];
+    outbound::HttpRoute {
+        metadata,
+        rules,
+        ..Default::default()
+    }
+}
+
+fn convert_to_filter(filter: Filter) -> outbound::http_route::Filter {
+    use outbound::http_route::filter::Kind;
+
+    outbound::http_route::Filter {
+        kind: Some(match filter {
+            Filter::RequestHeaderModifier(f) => {
+                Kind::RequestHeaderModifier(convert_request_header_modifier_filter(f))
+            }
+            Filter::ResponseHeaderModifier(f) => {
+                Kind::ResponseHeaderModifier(convert_response_header_modifier_filter(f))
+            }
+            Filter::RequestRedirect(f) => Kind::Redirect(convert_redirect_filter(f)),
+            Filter::FailureInjector(f) => Kind::FailureInjector(convert_failure_injector_filter(f)),
+        }),
+    }
+}
diff --git a/policy-controller/grpc/src/routes.rs b/policy-controller/grpc/src/routes.rs
index d3b82c415..86d679cac 100644
--- a/policy-controller/grpc/src/routes.rs
+++ b/policy-controller/grpc/src/routes.rs
@@ -1,9 +1,11 @@
 use linkerd2_proxy_api::{http_route as proto, http_types};
 use linkerd_policy_controller_core::routes::{
-    FailureInjectorFilter, HeaderMatch, HeaderModifierFilter, HostMatch, HttpRouteMatch, PathMatch,
-    PathModifier, QueryParamMatch, RequestRedirectFilter,
+    HeaderModifierFilter, HostMatch, PathModifier, RequestRedirectFilter,
 };
 
+pub(crate) mod grpc;
+pub(crate) mod http;
+
 pub(crate) fn convert_host_match(h: HostMatch) -> proto::HostMatch {
     proto::HostMatch {
         r#match: Some(match h {
@@ -91,78 +93,3 @@ pub(crate) fn convert_redirect_filter(
         status: u32::from(status.unwrap_or_default().as_u16()),
     }
 }
-
-pub(crate) mod http {
-    use super::{
-        proto, FailureInjectorFilter, HeaderMatch, HttpRouteMatch, PathMatch, QueryParamMatch,
-    };
-
-    pub(crate) fn convert_match(
-        HttpRouteMatch {
-            headers,
-            path,
-            query_params,
-            method,
-        }: HttpRouteMatch,
-    ) -> proto::HttpRouteMatch {
-        let headers = headers
-            .into_iter()
-            .map(|hm| match hm {
-                HeaderMatch::Exact(name, value) => proto::HeaderMatch {
-                    name: name.to_string(),
-                    value: Some(proto::header_match::Value::Exact(value.as_bytes().to_vec())),
-                },
-                HeaderMatch::Regex(name, re) => proto::HeaderMatch {
-                    name: name.to_string(),
-                    value: Some(proto::header_match::Value::Regex(re.to_string())),
-                },
-            })
-            .collect();
-
-        let path = path.map(|path| proto::PathMatch {
-            kind: Some(match path {
-                PathMatch::Exact(path) => proto::path_match::Kind::Exact(path),
-                PathMatch::Prefix(prefix) => proto::path_match::Kind::Prefix(prefix),
-                PathMatch::Regex(regex) => proto::path_match::Kind::Regex(regex.to_string()),
-            }),
-        });
-
-        let query_params = query_params
-            .into_iter()
-            .map(|qpm| match qpm {
-                QueryParamMatch::Exact(name, value) => proto::QueryParamMatch {
-                    name,
-                    value: Some(proto::query_param_match::Value::Exact(value)),
-                },
-                QueryParamMatch::Regex(name, re) => proto::QueryParamMatch {
-                    name,
-                    value: Some(proto::query_param_match::Value::Regex(re.to_string())),
-                },
-            })
-            .collect();
-
-        proto::HttpRouteMatch {
-            headers,
-            path,
-            query_params,
-            method: method.map(Into::into),
-        }
-    }
-
-    pub(crate) fn convert_failure_injector_filter(
-        FailureInjectorFilter {
-            status,
-            message,
-            ratio,
-        }: FailureInjectorFilter,
-    ) -> proto::HttpFailureInjector {
-        proto::HttpFailureInjector {
-            status: u32::from(status.as_u16()),
-            message,
-            ratio: Some(proto::Ratio {
-                numerator: ratio.numerator,
-                denominator: ratio.denominator,
-            }),
-        }
-    }
-}
diff --git a/policy-controller/grpc/src/routes/grpc.rs b/policy-controller/grpc/src/routes/grpc.rs
new file mode 100644
index 000000000..6c574cbf2
--- /dev/null
+++ b/policy-controller/grpc/src/routes/grpc.rs
@@ -0,0 +1,29 @@
+use linkerd2_proxy_api::{grpc_route, http_route};
+use linkerd_policy_controller_core::routes::{GrpcRouteMatch, HeaderMatch};
+
+pub(crate) fn convert_match(
+    GrpcRouteMatch { headers, method }: GrpcRouteMatch,
+) -> grpc_route::GrpcRouteMatch {
+    let headers = headers
+        .into_iter()
+        .map(|rule| match rule {
+            HeaderMatch::Exact(name, value) => http_route::HeaderMatch {
+                name: name.to_string(),
+                value: Some(http_route::header_match::Value::Exact(
+                    value.as_bytes().to_vec(),
+                )),
+            },
+            HeaderMatch::Regex(name, re) => http_route::HeaderMatch {
+                name: name.to_string(),
+                value: Some(http_route::header_match::Value::Regex(re.to_string())),
+            },
+        })
+        .collect();
+
+    let rpc = method.map(|value| grpc_route::GrpcRpcMatch {
+        method: value.method.unwrap_or_default(),
+        service: value.service.unwrap_or_default(),
+    });
+
+    grpc_route::GrpcRouteMatch { rpc, headers }
+}
diff --git a/policy-controller/grpc/src/routes/http.rs b/policy-controller/grpc/src/routes/http.rs
new file mode 100644
index 000000000..c5a2dae69
--- /dev/null
+++ b/policy-controller/grpc/src/routes/http.rs
@@ -0,0 +1,75 @@
+use linkerd2_proxy_api::http_route;
+use linkerd_policy_controller_core::routes::{
+    FailureInjectorFilter, HeaderMatch, HttpRouteMatch, PathMatch, QueryParamMatch,
+};
+
+pub(crate) fn convert_match(
+    HttpRouteMatch {
+        headers,
+        path,
+        query_params,
+        method,
+    }: HttpRouteMatch,
+) -> http_route::HttpRouteMatch {
+    let headers = headers
+        .into_iter()
+        .map(|hm| match hm {
+            HeaderMatch::Exact(name, value) => http_route::HeaderMatch {
+                name: name.to_string(),
+                value: Some(http_route::header_match::Value::Exact(
+                    value.as_bytes().to_vec(),
+                )),
+            },
+            HeaderMatch::Regex(name, re) => http_route::HeaderMatch {
+                name: name.to_string(),
+                value: Some(http_route::header_match::Value::Regex(re.to_string())),
+            },
+        })
+        .collect();
+
+    let path = path.map(|path| http_route::PathMatch {
+        kind: Some(match path {
+            PathMatch::Exact(path) => http_route::path_match::Kind::Exact(path),
+            PathMatch::Prefix(prefix) => http_route::path_match::Kind::Prefix(prefix),
+            PathMatch::Regex(regex) => http_route::path_match::Kind::Regex(regex.to_string()),
+        }),
+    });
+
+    let query_params = query_params
+        .into_iter()
+        .map(|qpm| match qpm {
+            QueryParamMatch::Exact(name, value) => http_route::QueryParamMatch {
+                name,
+                value: Some(http_route::query_param_match::Value::Exact(value)),
+            },
+            QueryParamMatch::Regex(name, re) => http_route::QueryParamMatch {
+                name,
+                value: Some(http_route::query_param_match::Value::Regex(re.to_string())),
+            },
+        })
+        .collect();
+
+    http_route::HttpRouteMatch {
+        headers,
+        path,
+        query_params,
+        method: method.map(Into::into),
+    }
+}
+
+pub(crate) fn convert_failure_injector_filter(
+    FailureInjectorFilter {
+        status,
+        message,
+        ratio,
+    }: FailureInjectorFilter,
+) -> http_route::HttpFailureInjector {
+    http_route::HttpFailureInjector {
+        status: u32::from(status.as_u16()),
+        message,
+        ratio: Some(http_route::Ratio {
+            numerator: ratio.numerator,
+            denominator: ratio.denominator,
+        }),
+    }
+}
diff --git a/policy-controller/k8s/index/src/outbound/index.rs b/policy-controller/k8s/index/src/outbound/index.rs
index e36c65150..1e102c4f1 100644
--- a/policy-controller/k8s/index/src/outbound/index.rs
+++ b/policy-controller/k8s/index/src/outbound/index.rs
@@ -1,20 +1,17 @@
 use crate::{
     ports::{ports_annotation, PortSet},
-    routes::{self, ExplicitGKN, RouteResource},
+    routes::{ExplicitGKN, HttpRouteResource, ImpliedGKN},
     ClusterInfo,
 };
 use ahash::AHashMap as HashMap;
 use anyhow::{bail, ensure, Result};
 use linkerd_policy_controller_core::{
-    outbound::{
-        Backend, Backoff, FailureAccrual, Filter, OutboundPolicy, OutboundRoute,
-        OutboundRouteCollection, OutboundRouteRule, TypedOutboundRoute, WeightedService,
-    },
-    routes::{GroupKindNamespaceName, HttpRouteMatch},
+    outbound::{Backend, Backoff, FailureAccrual, OutboundPolicy, OutboundRoute, RouteSet},
+    routes::{GroupKindNamespaceName, GrpcRouteMatch, HttpRouteMatch},
 };
 use linkerd_policy_controller_k8s_api::{
-    gateway::{self as k8s_gateway_api, BackendObjectReference, HttpBackendRef, ParentReference},
-    policy as linkerd_k8s_api, ResourceExt, Service, Time,
+    gateway::{self as k8s_gateway_api, ParentReference},
+    policy as linkerd_k8s_api, ResourceExt, Service,
 };
 use parking_lot::RwLock;
 use std::{hash::Hash, net::IpAddr, num::NonZeroU16, sync::Arc, time};
@@ -27,6 +24,8 @@ pub struct Index {
     service_info: HashMap<ServiceRef, ServiceInfo>,
 }
 
+mod grpc;
+mod http;
 pub mod metrics;
 
 pub type SharedIndex = Arc<RwLock<Index>>;
@@ -52,7 +51,8 @@ struct Namespace {
     service_port_routes: HashMap<ServicePort, ServiceRoutes>,
     /// Stores the route resources (by service name) that do not
     /// explicitly target a port.
-    service_routes: HashMap<String, OutboundRouteCollection>,
+    service_http_routes: HashMap<String, RouteSet<HttpRouteMatch>>,
+    service_grpc_routes: HashMap<String, RouteSet<GrpcRouteMatch>>,
     namespace: Arc<String>,
 }
 
@@ -83,13 +83,14 @@ struct ServiceRoutes {
 struct RoutesWatch {
     opaque: bool,
     accrual: Option<FailureAccrual>,
-    routes: OutboundRouteCollection,
+    http_routes: RouteSet<HttpRouteMatch>,
+    grpc_routes: RouteSet<GrpcRouteMatch>,
     watch: watch::Sender<OutboundPolicy>,
 }
 
 impl kubert::index::IndexNamespacedResource<linkerd_k8s_api::HttpRoute> for Index {
     fn apply(&mut self, route: linkerd_k8s_api::HttpRoute) {
-        self.apply(RouteResource::LinkerdHttp(route))
+        self.apply_http(HttpRouteResource::LinkerdHttp(route))
     }
 
     fn delete(&mut self, namespace: String, name: String) {
@@ -98,14 +99,14 @@ impl kubert::index::IndexNamespacedResource<linkerd_k8s_api::HttpRoute> for Inde
             .namespaced(namespace);
         tracing::debug!(?gknn, "deleting route");
         for ns_index in self.namespaces.by_ns.values_mut() {
-            ns_index.delete(&gknn);
+            ns_index.delete_http_route(&gknn);
         }
     }
 }
 
 impl kubert::index::IndexNamespacedResource<k8s_gateway_api::HttpRoute> for Index {
     fn apply(&mut self, route: k8s_gateway_api::HttpRoute) {
-        self.apply(RouteResource::GatewayHttp(route))
+        self.apply_http(HttpRouteResource::GatewayHttp(route))
     }
 
     fn delete(&mut self, namespace: String, name: String) {
@@ -113,7 +114,22 @@ impl kubert::index::IndexNamespacedResource<k8s_gateway_api::HttpRoute> for Inde
             .gkn::<k8s_gateway_api::HttpRoute>()
             .namespaced(namespace);
         for ns_index in self.namespaces.by_ns.values_mut() {
-            ns_index.delete(&gknn);
+            ns_index.delete_http_route(&gknn);
+        }
+    }
+}
+
+impl kubert::index::IndexNamespacedResource<k8s_gateway_api::GrpcRoute> for Index {
+    fn apply(&mut self, route: k8s_gateway_api::GrpcRoute) {
+        self.apply_grpc(route)
+    }
+
+    fn delete(&mut self, namespace: String, name: String) {
+        let gknn = name
+            .gkn::<k8s_gateway_api::GrpcRoute>()
+            .namespaced(namespace);
+        for ns_index in self.namespaces.by_ns.values_mut() {
+            ns_index.delete_grpc_route(&gknn);
         }
     }
 }
@@ -163,7 +179,8 @@ impl kubert::index::IndexNamespacedResource<Service> for Index {
             .by_ns
             .entry(ns.clone())
             .or_insert_with(|| Namespace {
-                service_routes: Default::default(),
+                service_http_routes: Default::default(),
+                service_grpc_routes: Default::default(),
                 service_port_routes: Default::default(),
                 namespace: Arc::new(ns),
             })
@@ -215,7 +232,8 @@ impl Index {
             .entry(service_namespace.clone())
             .or_insert_with(|| Namespace {
                 namespace: Arc::new(service_namespace.to_string()),
-                service_routes: Default::default(),
+                service_http_routes: Default::default(),
+                service_grpc_routes: Default::default(),
                 service_port_routes: Default::default(),
             });
 
@@ -238,8 +256,8 @@ impl Index {
         self.services_by_ip.get(&addr).cloned()
     }
 
-    fn apply(&mut self, route: RouteResource) {
-        tracing::debug!(name = route.name(), "indexing route");
+    fn apply_http(&mut self, route: HttpRouteResource) {
+        tracing::debug!(name = route.name(), "indexing httproute");
 
         for parent_ref in route.inner().parent_refs.iter().flatten() {
             if !is_parent_service(parent_ref) {
@@ -260,10 +278,47 @@ impl Index {
                 .entry(ns.clone())
                 .or_insert_with(|| Namespace {
                     namespace: Arc::new(ns),
-                    service_routes: Default::default(),
+                    service_http_routes: Default::default(),
+                    service_grpc_routes: Default::default(),
                     service_port_routes: Default::default(),
                 })
-                .apply(
+                .apply_http_route(
+                    route.clone(),
+                    parent_ref,
+                    &self.namespaces.cluster_info,
+                    &self.service_info,
+                );
+        }
+    }
+
+    fn apply_grpc(&mut self, route: k8s_gateway_api::GrpcRoute) {
+        tracing::debug!(name = route.name_unchecked(), "indexing grpcroute");
+
+        for parent_ref in route.spec.inner.parent_refs.iter().flatten() {
+            if !is_parent_service(parent_ref) {
+                continue;
+            }
+
+            if !route_accepted_by_service(route.status.as_ref().map(|s| &s.inner), &parent_ref.name)
+            {
+                continue;
+            }
+
+            let ns = parent_ref
+                .namespace
+                .clone()
+                .unwrap_or_else(|| route.namespace().expect("GrpcRoute must have a namespace"));
+
+            self.namespaces
+                .by_ns
+                .entry(ns.clone())
+                .or_insert_with(|| Namespace {
+                    namespace: Arc::new(ns),
+                    service_http_routes: Default::default(),
+                    service_grpc_routes: Default::default(),
+                    service_port_routes: Default::default(),
+                })
+                .apply_grpc_route(
                     route.clone(),
                     parent_ref,
                     &self.namespaces.cluster_info,
@@ -280,22 +335,23 @@ impl Index {
 }
 
 impl Namespace {
-    fn apply(
+    fn apply_http_route(
         &mut self,
-        route: RouteResource,
+        route: HttpRouteResource,
         parent_ref: &ParentReference,
         cluster_info: &ClusterInfo,
         service_info: &HashMap<ServiceRef, ServiceInfo>,
     ) {
         tracing::debug!(?route);
 
-        let outbound_route = match self.convert_route(route.clone(), cluster_info, service_info) {
-            Ok(route) => route,
-            Err(error) => {
-                tracing::error!(%error, "failed to convert route");
-                return;
-            }
-        };
+        let outbound_route =
+            match http::convert_route(&self.namespace, route.clone(), cluster_info, service_info) {
+                Ok(route) => route,
+                Err(error) => {
+                    tracing::error!(%error, "failed to convert route");
+                    return;
+                }
+            };
 
         tracing::debug!(?outbound_route);
 
@@ -310,32 +366,92 @@ impl Namespace {
             tracing::debug!(
                 ?service_port,
                 route = route.name(),
-                "inserting route for service"
+                "inserting httproute for service"
             );
 
             let service_routes =
                 self.service_routes_or_default(service_port, cluster_info, service_info);
 
-            service_routes.apply(route.gknn(), outbound_route);
+            service_routes.apply_http_route(route.gknn(), outbound_route);
         } else {
             // If the parent_ref doesn't include a port, apply this route
             // to all ServiceRoutes which match the Service name.
             self.service_port_routes.iter_mut().for_each(
                 |(ServicePort { service, port: _ }, routes)| {
                     if service == &parent_ref.name {
-                        routes.apply(route.gknn(), outbound_route.clone());
+                        routes.apply_http_route(route.gknn(), outbound_route.clone());
                     }
                 },
             );
 
             // Also add the route to the list of routes that target the
             // Service without specifying a port.
-            self.service_routes
+            self.service_http_routes
                 .entry(parent_ref.name.clone())
                 .or_default()
-                .insert(route.gknn(), outbound_route)
-                .map_err(|error| tracing::warn!(?error))
-                .transpose();
+                .insert(route.gknn(), outbound_route);
+        }
+    }
+
+    fn apply_grpc_route(
+        &mut self,
+        route: k8s_gateway_api::GrpcRoute,
+        parent_ref: &ParentReference,
+        cluster_info: &ClusterInfo,
+        service_info: &HashMap<ServiceRef, ServiceInfo>,
+    ) {
+        tracing::debug!(?route);
+
+        let outbound_route =
+            match grpc::convert_route(&self.namespace, route.clone(), cluster_info, service_info) {
+                Ok(route) => route,
+                Err(error) => {
+                    tracing::error!(%error, "failed to convert route");
+                    return;
+                }
+            };
+
+        tracing::debug!(?outbound_route);
+
+        let gknn = route
+            .gkn()
+            .namespaced(route.namespace().expect("Route must have namespace"));
+
+        let port = parent_ref.port.and_then(NonZeroU16::new);
+
+        if let Some(port) = port {
+            let service_port = ServicePort {
+                port,
+                service: parent_ref.name.clone(),
+            };
+
+            tracing::debug!(
+                ?service_port,
+                route = route.name_unchecked(),
+                "inserting grpcroute for service"
+            );
+
+            let service_routes =
+                self.service_routes_or_default(service_port, cluster_info, service_info);
+
+            service_routes.apply_grpc_route(gknn, outbound_route);
+        } else {
+            // If the parent_ref doesn't include a port, apply this route
+            // to all ServiceRoutes which match the Service name.
+            self.service_port_routes.iter_mut().for_each(
+                |(ServicePort { service, port: _ }, routes)| {
+                    if service == &parent_ref.name {
+                        routes.apply_grpc_route(gknn.clone(), outbound_route.clone());
+                    }
+                },
+            );
+
+            // Also add the route to the list of routes that target the
+            // Service without specifying a port.
+            self.service_grpc_routes
+                .entry(parent_ref.name.clone())
+                .or_default()
+                .insert(gknn, outbound_route);
         }
     }
 
@@ -352,16 +468,18 @@ impl Namespace {
 
         for routes in self.service_port_routes.values_mut() {
             for watch in routes.watches_by_ns.values_mut() {
-                match &mut watch.routes {
-                    OutboundRouteCollection::Empty => {}
-                    OutboundRouteCollection::Http(routes) => {
-                        routes
-                            .values_mut()
-                            .flat_map(|route| route.rules.iter_mut())
-                            .flat_map(|rule| rule.backends.iter_mut())
-                            .for_each(update_service);
-                    }
-                }
+                let http_backends = watch
+                    .http_routes
+                    .values_mut()
+                    .flat_map(|route| route.rules.iter_mut())
+                    .flat_map(|rule| rule.backends.iter_mut());
+                let grpc_backends = watch
+                    .grpc_routes
+                    .values_mut()
+                    .flat_map(|route| route.rules.iter_mut())
+                    .flat_map(|rule| rule.backends.iter_mut());
+
+                http_backends.chain(grpc_backends).for_each(update_service);
                 watch.send_if_modified();
             }
         }
@@ -381,12 +499,23 @@ impl Namespace {
         }
     }
 
-    fn delete(&mut self, gknn: &GroupKindNamespaceName) {
+    fn delete_http_route(&mut self, gknn: &GroupKindNamespaceName) {
         for service in self.service_port_routes.values_mut() {
-            service.delete(gknn);
+            service.delete_http_route(gknn);
         }
 
-        self.service_routes.retain(|_, routes| {
+        self.service_http_routes.retain(|_, routes| {
+            routes.remove(gknn);
+            !routes.is_empty()
+        });
+    }
+
+    fn delete_grpc_route(&mut self, gknn: &GroupKindNamespaceName) {
+        for service in self.service_port_routes.values_mut() {
+            service.delete_grpc_route(gknn);
+        }
+
+        self.service_grpc_routes.retain(|_, routes| {
             routes.remove(gknn);
             !routes.is_empty()
         });
@@ -416,7 +545,16 @@ impl Namespace {
 
                 // The routes which target this Service but don't specify
                 // a port apply to all ports. Therefore, we include them.
-                let routes = self.service_routes.get(&sp.service).cloned();
+                let http_routes = self
+                    .service_http_routes
+                    .get(&sp.service)
+                    .cloned()
+                    .unwrap_or_default();
+                let grpc_routes = self
+                    .service_grpc_routes
+                    .get(&sp.service)
+                    .cloned()
+                    .unwrap_or_default();
 
                 let mut service_routes = ServiceRoutes {
                     opaque,
@@ -428,303 +566,85 @@ impl Namespace {
                     watches_by_ns: Default::default(),
                 };
 
-                match routes {
-                    None | Some(OutboundRouteCollection::Empty) => {}
-                    Some(OutboundRouteCollection::Http(routes)) => {
-                        service_routes
-                            .insert_producer_and_consumer_routes(routes, self.namespace.as_str());
-                    }
-                };
-
-                service_routes
-            })
-    }
-
-    fn convert_route(
-        &self,
-        route: RouteResource,
-        cluster: &ClusterInfo,
-        service_info: &HashMap<ServiceRef, ServiceInfo>,
-    ) -> Result<TypedOutboundRoute> {
-        match route {
-            RouteResource::LinkerdHttp(route) => {
-                let hostnames = route
-                    .spec
-                    .hostnames
-                    .into_iter()
-                    .flatten()
-                    .map(routes::http::host_match)
-                    .collect();
-
-                let rules = route
-                    .spec
-                    .rules
-                    .into_iter()
-                    .flatten()
-                    .map(|r| self.convert_linkerd_rule(r, cluster, service_info))
-                    .collect::<Result<_>>()?;
-
-                let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);
-
-                Ok(TypedOutboundRoute::Http(OutboundRoute {
-                    hostnames,
-                    rules,
-                    creation_timestamp,
-                }))
-            }
-            RouteResource::GatewayHttp(route) => {
-                let hostnames = route
-                    .spec
-                    .hostnames
+                // Producer routes are routes in the same namespace as
+                // their parent service. Consumer routes are routes in
+                // other namespaces.
+                let (producer_http_routes, consumer_http_routes): (Vec<_>, Vec<_>) = http_routes
                     .into_iter()
-                    .flatten()
-                    .map(routes::http::host_match)
-                    .collect();
-
-                let rules = route
-                    .spec
-                    .rules
+                    .partition(|(gknn, _)| gknn.namespace == *self.namespace);
+                let (producer_grpc_routes, consumer_grpc_routes): (Vec<_>, Vec<_>) = grpc_routes
                     .into_iter()
-                    .flatten()
-                    .map(|r| self.convert_gateway_http_rule(r, cluster, service_info))
-                    .collect::<Result<_>>()?;
+                    .partition(|(gknn, _)| gknn.namespace == *self.namespace);
 
-                let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);
+                for (consumer_gknn, consumer_route) in consumer_http_routes {
+                    // Consumer routes should only apply to watches from the
+                    // consumer namespace.
+                    let consumer_watch =
+                        service_routes.watch_for_ns_or_default(consumer_gknn.namespace.to_string());
 
-                Ok(TypedOutboundRoute::Http(OutboundRoute {
-                    hostnames,
-                    rules,
-                    creation_timestamp,
-                }))
-            }
-        }
-    }
-
-    fn convert_linkerd_rule(
-        &self,
-        rule: linkerd_k8s_api::httproute::HttpRouteRule,
-        cluster: &ClusterInfo,
-        service_info: &HashMap<ServiceRef, ServiceInfo>,
-    ) -> Result<OutboundRouteRule<HttpRouteMatch>> {
-        let matches = rule
-            .matches
-            .into_iter()
-            .flatten()
-            .map(routes::http::try_match)
-            .collect::<Result<_>>()?;
-
-        let backends = rule
-            .backend_refs
-            .into_iter()
-            .flatten()
-            .filter_map(|b| convert_backend(&self.namespace, b, cluster, service_info))
-            .collect();
-
-        let filters = rule
-            .filters
-            .into_iter()
-            .flatten()
-            .map(convert_linkerd_filter)
-            .collect::<Result<_>>()?;
-
-        let request_timeout = rule.timeouts.as_ref().and_then(|timeouts| {
-            let timeout = time::Duration::from(timeouts.request?);
-
-            // zero means "no timeout", per GEP-1742
-            if timeout == time::Duration::from_nanos(0) {
-                return None;
-            }
-
-            Some(timeout)
-        });
-
-        let backend_request_timeout = rule.timeouts.as_ref().and_then(
-            |timeouts: &linkerd_k8s_api::httproute::HttpRouteTimeouts| {
-                let timeout = time::Duration::from(timeouts.backend_request?);
-
-                // zero means "no timeout", per GEP-1742
-                if timeout == time::Duration::from_nanos(0) {
-                    return None;
+                    consumer_watch.insert_http_route(consumer_gknn.clone(), consumer_route.clone());
                 }
+                for (consumer_gknn, consumer_route) in consumer_grpc_routes {
+                    // Consumer routes should only apply to watches from the
+                    // consumer namespace.
+                    let consumer_watch =
+                        service_routes.watch_for_ns_or_default(consumer_gknn.namespace.to_string());
 
-                Some(timeout)
-            },
-        );
-
-        Ok(OutboundRouteRule {
-            matches,
-            backends,
-            request_timeout,
-            backend_request_timeout,
-            filters,
-        })
-    }
+                    consumer_watch.insert_grpc_route(consumer_gknn.clone(), consumer_route.clone());
+                }
 
-    fn convert_gateway_http_rule(
-        &self,
-        rule: k8s_gateway_api::HttpRouteRule,
-        cluster: &ClusterInfo,
-        service_info: &HashMap<ServiceRef, ServiceInfo>,
-    ) -> Result<OutboundRouteRule<HttpRouteMatch>> {
-        let matches = rule
-            .matches
-            .into_iter()
-            .flatten()
-            .map(routes::http::try_match)
-            .collect::<Result<_>>()?;
-
-        let backends = rule
-            .backend_refs
-            .into_iter()
-            .flatten()
-            .filter_map(|b| convert_backend(&self.namespace, b, cluster, service_info))
-            .collect();
-
-        let filters = rule
-            .filters
-            .into_iter()
-            .flatten()
-            .map(convert_gateway_filter)
-            .collect::<Result<_>>()?;
-
-        Ok(OutboundRouteRule {
-            matches,
-            backends,
-            request_timeout: None,
-            backend_request_timeout: None,
-            filters,
-        })
-    }
-}
+                for (producer_gknn, producer_route) in producer_http_routes {
+                    // Insert the route into the producer namespace.
+                    let producer_watch =
+                        service_routes.watch_for_ns_or_default(producer_gknn.namespace.to_string());
+
+                    producer_watch.insert_http_route(producer_gknn.clone(), producer_route.clone());
+
+                    // Producer routes apply to clients in all namespaces, so
+                    // apply it to watches for all other namespaces too.
+                    service_routes
+                        .watches_by_ns
+                        .iter_mut()
+                        .filter(|(namespace, _)| {
+                            namespace.as_str() != producer_gknn.namespace.as_ref()
+                        })
+                        .for_each(|(_, watch)| {
+                            watch.insert_http_route(producer_gknn.clone(), producer_route.clone())
+                        });
+                }
 
-fn convert_backend<BackendRef: Into<HttpBackendRef>>(
-    ns: &str,
-    backend: BackendRef,
-    cluster: &ClusterInfo,
-    services: &HashMap<ServiceRef, ServiceInfo>,
-) -> Option<Backend> {
-    let backend = backend.into();
-    let filters = backend.filters;
-    let backend = backend.backend_ref?;
-    if !is_backend_service(&backend.inner) {
-        return Some(Backend::Invalid {
-            weight: backend.weight.unwrap_or(1).into(),
-            message: format!(
-                "unsupported backend type {group} {kind}",
-                group = backend.inner.group.as_deref().unwrap_or("core"),
-                kind = backend.inner.kind.as_deref().unwrap_or("<empty>"),
-            ),
-        });
-    }
+                for (producer_gknn, producer_route) in producer_grpc_routes {
+                    // Insert the route into the producer namespace.
+                    let producer_watch =
+                        service_routes.watch_for_ns_or_default(producer_gknn.namespace.to_string());
+
+                    producer_watch.insert_grpc_route(producer_gknn.clone(), producer_route.clone());
+
+                    // Producer routes apply to clients in all namespaces, so
+                    // apply it to watches for all other namespaces too.
+                    service_routes
+                        .watches_by_ns
+                        .iter_mut()
+                        .filter(|(namespace, _)| {
+                            namespace.as_str() != producer_gknn.namespace.as_ref()
+                        })
+                        .for_each(|(_, watch)| {
+                            watch.insert_grpc_route(producer_gknn.clone(), producer_route.clone())
+                        });
+                }
 
-    let name = backend.inner.name;
-    let weight = backend.weight.unwrap_or(1);
-
-    // The gateway API dictates:
-    //
-    // Port is required when the referent is a Kubernetes Service.
-    let port = match backend
-        .inner
-        .port
-        .and_then(|p| NonZeroU16::try_from(p).ok())
-    {
-        Some(port) => port,
-        None => {
-            return Some(Backend::Invalid {
-                weight: weight.into(),
-                message: format!("missing port for backend Service {name}"),
+                service_routes
             })
-        }
-    };
-    let service_ref = ServiceRef {
-        name: name.clone(),
-        namespace: backend.inner.namespace.unwrap_or_else(|| ns.to_string()),
-    };
-
-    let filters = match filters
-        .into_iter()
-        .flatten()
-        .map(convert_gateway_filter)
-        .collect::<Result<_>>()
-    {
-        Ok(filters) => filters,
-        Err(error) => {
-            return Some(Backend::Invalid {
-                weight: backend.weight.unwrap_or(1).into(),
-                message: format!("unsupported backend filter: {error}", error = error),
-            });
-        }
-    };
-
-    Some(Backend::Service(WeightedService {
-        weight: weight.into(),
-        authority: cluster.service_dns_authority(&service_ref.namespace, &name, port),
-        name,
-        namespace: service_ref.namespace.to_string(),
-        port,
-        filters,
-        exists: services.contains_key(&service_ref),
-    }))
-}
-
-fn convert_linkerd_filter(filter: linkerd_k8s_api::httproute::HttpRouteFilter) -> Result<Filter> {
-    let filter = match filter {
-        linkerd_k8s_api::httproute::HttpRouteFilter::RequestHeaderModifier {
-            request_header_modifier,
-        } => {
-            let filter = routes::http::header_modifier(request_header_modifier)?;
-            Filter::RequestHeaderModifier(filter)
-        }
-
-        linkerd_k8s_api::httproute::HttpRouteFilter::ResponseHeaderModifier {
-            response_header_modifier,
-        } => {
-            let filter = routes::http::header_modifier(response_header_modifier)?;
-            Filter::RequestHeaderModifier(filter)
-        }
-
-        linkerd_k8s_api::httproute::HttpRouteFilter::RequestRedirect { request_redirect } => {
-            let filter = routes::http::req_redirect(request_redirect)?;
-            Filter::RequestRedirect(filter)
-        }
-    };
-    Ok(filter)
+    }
 }
 
-fn convert_gateway_filter<RouteFilter: Into<k8s_gateway_api::HttpRouteFilter>>(
-    filter: RouteFilter,
-) -> Result<Filter> {
-    let filter = filter.into();
-    let filter = match filter {
-        k8s_gateway_api::HttpRouteFilter::RequestHeaderModifier {
-            request_header_modifier,
-        } => {
-            let filter = routes::http::header_modifier(request_header_modifier)?;
-            Filter::RequestHeaderModifier(filter)
-        }
-
-        k8s_gateway_api::HttpRouteFilter::ResponseHeaderModifier {
-            response_header_modifier,
-        } => {
-            let filter = routes::http::header_modifier(response_header_modifier)?;
-            Filter::ResponseHeaderModifier(filter)
-        }
-
-        k8s_gateway_api::HttpRouteFilter::RequestRedirect { request_redirect } => {
-            let filter = routes::http::req_redirect(request_redirect)?;
-            Filter::RequestRedirect(filter)
-        }
-        k8s_gateway_api::HttpRouteFilter::RequestMirror { .. } => {
-            bail!("RequestMirror filter is not supported")
-        }
-        k8s_gateway_api::HttpRouteFilter::URLRewrite { .. } => {
-            bail!("URLRewrite filter is not supported")
-        }
-        k8s_gateway_api::HttpRouteFilter::ExtensionRef { .. } => {
-            bail!("ExtensionRef filter is not supported")
-        }
-    };
-    Ok(filter)
+#[inline]
+fn is_service(group: Option<&str>, kind: &str) -> bool {
+    // If the group is not specified or empty, assume it's 'core'.
+    group
+        .map(|g| g.eq_ignore_ascii_case("core") || g.is_empty())
+        .unwrap_or(true)
+        && kind.eq_ignore_ascii_case("Service")
 }
 
 #[inline]
@@ -756,32 +676,19 @@ fn route_accepted_by_service(
         })
 }
 
-#[inline]
-fn is_backend_service(backend: &BackendObjectReference) -> bool {
-    is_service(
-        backend.group.as_deref(),
-        // Backends default to `Service` if no kind is specified.
-        backend.kind.as_deref().unwrap_or("Service"),
-    )
-}
-
-#[inline]
-fn is_service(group: Option<&str>, kind: &str) -> bool {
-    // If the group is not specified or empty, assume it's 'core'.
-    group
-        .map(|g| g.eq_ignore_ascii_case("core") || g.is_empty())
-        .unwrap_or(true)
-        && kind.eq_ignore_ascii_case("Service")
-}
-
 impl ServiceRoutes {
     fn watch_for_ns_or_default(&mut self, namespace: String) -> &mut RoutesWatch {
         // The routes from the producer namespace apply to watches in all
         // namespaces, so we copy them.
-        let routes = self
+        let http_routes = self
+            .watches_by_ns
+            .get(self.namespace.as_ref())
+            .map(|watch| watch.http_routes.clone())
+            .unwrap_or_default();
+        let grpc_routes = self
             .watches_by_ns
             .get(self.namespace.as_ref())
-            .map(|watch| watch.routes.clone())
+            .map(|watch| watch.grpc_routes.clone())
             .unwrap_or_default();
 
         self.watches_by_ns.entry(namespace).or_insert_with(|| {
@@ -789,14 +696,16 @@ impl ServiceRoutes {
                 port: self.port,
                 opaque: self.opaque,
                 accrual: self.accrual,
-                routes: routes.clone(),
+                http_routes: http_routes.clone(),
+                grpc_routes: grpc_routes.clone(),
                 name: self.name.to_string(),
                 authority: self.authority.clone(),
                 namespace: self.namespace.to_string(),
             });
 
             RoutesWatch {
-                routes,
+                http_routes,
+                grpc_routes,
                 watch: sender,
                 opaque: self.opaque,
                 accrual: self.accrual,
@@ -804,25 +713,55 @@ impl ServiceRoutes {
         })
     }
 
-    fn apply(&mut self, gknn: GroupKindNamespaceName, route: TypedOutboundRoute) {
+    fn apply_http_route(
+        &mut self,
+        gknn: GroupKindNamespaceName,
+        route: OutboundRoute<HttpRouteMatch>,
+    ) {
+        if *gknn.namespace == *self.namespace {
+            // This is a producer namespace route.
+            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());
+
+            watch.insert_http_route(gknn.clone(), route.clone());
+
+            // Producer routes apply to clients in all namespaces, so
+            // apply it to watches for all other namespaces too.
+            for (ns, ns_watch) in self.watches_by_ns.iter_mut() {
+                if ns != &gknn.namespace {
+                    ns_watch.insert_http_route(gknn.clone(), route.clone());
+                }
+            }
+        } else {
+            // This is a consumer namespace route and should only apply to
+            // watches from that namespace.
+            let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());
+            watch.insert_http_route(gknn, route);
+        }
+    }
+
+    fn apply_grpc_route(
+        &mut self,
+        gknn: GroupKindNamespaceName,
+        route: OutboundRoute<GrpcRouteMatch>,
+    ) {
         if *gknn.namespace == *self.namespace {
             // This is a producer namespace route.
             let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());
 
-            watch.insert_route(gknn.clone(), route.clone());
+            watch.insert_grpc_route(gknn.clone(), route.clone());
 
             // Producer routes apply to clients in all namespaces, so
             // apply it to watches for all other namespaces too.
             for (ns, ns_watch) in self.watches_by_ns.iter_mut() {
                 if ns != &gknn.namespace {
-                    ns_watch.insert_route(gknn.clone(), route.clone());
+                    ns_watch.insert_grpc_route(gknn.clone(), route.clone());
                 }
             }
         } else {
             // This is a consumer namespace route and should only apply to
             // watches from that namespace.
             let watch = self.watch_for_ns_or_default(gknn.namespace.to_string());
-            watch.insert_route(gknn, route);
+            watch.insert_grpc_route(gknn, route);
         }
     }
 
@@ -836,46 +775,15 @@ impl ServiceRoutes {
         }
     }
 
-    fn delete(&mut self, gknn: &GroupKindNamespaceName) {
+    fn delete_http_route(&mut self, gknn: &GroupKindNamespaceName) {
         for watch in self.watches_by_ns.values_mut() {
-            watch.remove_route(gknn);
+            watch.remove_http_route(gknn);
         }
     }
 
-    fn insert_producer_and_consumer_routes<Route: Clone + Into<TypedOutboundRoute>>(
-        &mut self,
-        routes: HashMap<GroupKindNamespaceName, Route>,
-        namespace: &str,
-    ) {
-        // Producer routes are routes in the same namespace as
-        // their parent service. Consumer routes are routes in
-        // other namespaces.
-        let (producer_routes, consumer_routes): (Vec<_>, Vec<_>) = routes
-            .into_iter()
-            .partition(|(gknn, _)| gknn.namespace.as_ref() == namespace);
-
-        for (consumer_gknn, consumer_route) in consumer_routes {
-            // Consumer routes should only apply to watches from the
-            // consumer namespace.
-            let consumer_watch = self.watch_for_ns_or_default(consumer_gknn.namespace.to_string());
-
-            consumer_watch.insert_route(consumer_gknn.clone(), consumer_route.clone());
-        }
-
-        for (producer_gknn, producer_route) in producer_routes {
-            // Insert the route into the producer namespace.
-            let producer_watch = self.watch_for_ns_or_default(producer_gknn.namespace.to_string());
-
-            producer_watch.insert_route(producer_gknn.clone(), producer_route.clone());
-
-            // Producer routes apply to clients in all namespaces, so
-            // apply it to watches for all other namespaces too.
-            self.watches_by_ns
-                .iter_mut()
-                .filter(|(namespace, _)| namespace.as_str() != producer_gknn.namespace.as_ref())
-                .for_each(|(_, watch)| {
-                    watch.insert_route(producer_gknn.clone(), producer_route.clone())
-                });
+    fn delete_grpc_route(&mut self, gknn: &GroupKindNamespaceName) {
+        for watch in self.watches_by_ns.values_mut() {
+            watch.remove_grpc_route(gknn);
         }
     }
 }
@@ -885,8 +793,13 @@ impl RoutesWatch {
         self.watch.send_if_modified(|policy| {
             let mut modified = false;
 
-            if self.routes != policy.routes {
-                policy.routes = self.routes.clone();
+            if self.http_routes != policy.http_routes {
+                policy.http_routes = self.http_routes.clone();
+                modified = true;
+            }
+
+            if self.grpc_routes != policy.grpc_routes {
+                policy.grpc_routes = self.grpc_routes.clone();
                 modified = true;
             }
 
@@ -904,21 +817,33 @@ impl RoutesWatch {
         });
     }
 
-    fn insert_route<Route: Into<TypedOutboundRoute>>(
+    fn insert_http_route(
+        &mut self,
+        gknn: GroupKindNamespaceName,
+        route: OutboundRoute<HttpRouteMatch>,
+    ) {
+        self.http_routes.insert(gknn, route);
+
+        self.send_if_modified();
+    }
+
+    fn insert_grpc_route(
         &mut self,
         gknn: GroupKindNamespaceName,
-        route: Route,
+        route: OutboundRoute<GrpcRouteMatch>,
     ) {
-        self.routes
-            .insert(gknn, route)
-            .map_err(|error| tracing::warn!(?error))
-            .transpose();
+        self.grpc_routes.insert(gknn, route);
+
+        self.send_if_modified();
+    }
 
+    fn remove_http_route(&mut self, gknn: &GroupKindNamespaceName) {
+        self.http_routes.remove(gknn);
         self.send_if_modified();
     }
 
-    fn remove_route(&mut self, gknn: &GroupKindNamespaceName) {
-        self.routes.remove(gknn);
+    fn remove_grpc_route(&mut self, gknn: &GroupKindNamespaceName) {
+        self.grpc_routes.remove(gknn);
         self.send_if_modified();
     }
 }
diff --git a/policy-controller/k8s/index/src/outbound/index/grpc.rs b/policy-controller/k8s/index/src/outbound/index/grpc.rs
new file mode 100644
index 000000000..6962e9f82
--- /dev/null
+++ b/policy-controller/k8s/index/src/outbound/index/grpc.rs
@@ -0,0 +1,75 @@
+use super::http::{convert_backend, convert_gateway_filter};
+use super::{ServiceInfo, ServiceRef};
+use crate::{routes, ClusterInfo};
+use ahash::AHashMap as HashMap;
+use anyhow::Result;
+use linkerd_policy_controller_core::outbound::OutboundRoute;
+use linkerd_policy_controller_core::{outbound::OutboundRouteRule, routes::GrpcRouteMatch};
+use linkerd_policy_controller_k8s_api::{gateway, Time};
+
+pub(crate) fn convert_route(
+    ns: &str,
+    route: gateway::GrpcRoute,
+    cluster: &ClusterInfo,
+    service_info: &HashMap<ServiceRef, ServiceInfo>,
+) -> Result<OutboundRoute<GrpcRouteMatch>> {
+    let hostnames = route
+        .spec
+        .hostnames
+        .into_iter()
+        .flatten()
+        .map(routes::http::host_match)
+        .collect();
+
+    let rules = route
+        .spec
+        .rules
+        .into_iter()
+        .flatten()
+        .map(|rule| convert_rule(ns, rule, cluster, service_info))
+        .collect::<Result<_>>()?;
+
+    let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);
+
+    Ok(OutboundRoute {
+        hostnames,
+        rules,
+        creation_timestamp,
+    })
+}
+
+fn convert_rule(
+    ns: &str,
+    rule: gateway::GrpcRouteRule,
+    cluster: &ClusterInfo,
+    service_info: &HashMap<ServiceRef, ServiceInfo>,
+) -> Result<OutboundRouteRule<GrpcRouteMatch>> {
+    let matches = rule
+        .matches
+        .into_iter()
+        .flatten()
+        .map(routes::grpc::try_match)
+        .collect::<Result<_>>()?;
+
+    let backends = rule
+        .backend_refs
+        .into_iter()
+        .flatten()
+        .filter_map(|b| convert_backend(ns, b, cluster, service_info))
+        .collect();
+
+    let filters = rule
+        .filters
+        .into_iter()
+        .flatten()
+        .map(convert_gateway_filter)
+        .collect::<Result<_>>()?;
+
+    Ok(OutboundRouteRule {
+        matches,
+        backends,
+        request_timeout: None,
+        backend_request_timeout: None,
+        filters,
+    })
+}
diff --git a/policy-controller/k8s/index/src/outbound/index/http.rs b/policy-controller/k8s/index/src/outbound/index/http.rs
new file mode 100644
index 000000000..3a4242c91
--- /dev/null
+++ b/policy-controller/k8s/index/src/outbound/index/http.rs
@@ -0,0 +1,310 @@
+use std::{num::NonZeroU16, time};
+
+use super::{is_service, ServiceInfo, ServiceRef};
+use crate::{
+    routes::{self, HttpRouteResource},
+    ClusterInfo,
+};
+use ahash::AHashMap as HashMap;
+use anyhow::{bail, Result};
+use linkerd_policy_controller_core::{
+    outbound::{Backend, Filter, OutboundRoute, OutboundRouteRule, WeightedService},
+    routes::HttpRouteMatch,
+};
+use linkerd_policy_controller_k8s_api::{gateway, policy, Time};
+
+pub(crate) fn convert_route(
+    ns: &str,
+    route: HttpRouteResource,
+    cluster: &ClusterInfo,
+    service_info: &HashMap<ServiceRef, ServiceInfo>,
+) -> Result<OutboundRoute<HttpRouteMatch>> {
+    match route {
+        HttpRouteResource::LinkerdHttp(route) => {
+            let hostnames = route
+                .spec
+                .hostnames
+                .into_iter()
+                .flatten()
+                .map(routes::http::host_match)
+                .collect();
+
+            let rules = route
+                .spec
+                .rules
+                .into_iter()
+                .flatten()
+                .map(|r| convert_linkerd_rule(ns, r, cluster, service_info))
+                .collect::<Result<_>>()?;
+
+            let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);
+
+            Ok(OutboundRoute {
+                hostnames,
+                rules,
+                creation_timestamp,
+            })
+        }
+        HttpRouteResource::GatewayHttp(route) => {
+            let hostnames = route
+                .spec
+                .hostnames
+                .into_iter()
+                .flatten()
+                .map(routes::http::host_match)
+                .collect();
+
+            let rules = route
+                .spec
+                .rules
+                .into_iter()
+                .flatten()
+                .map(|r| convert_gateway_rule(ns, r, cluster, service_info))
+                .collect::<Result<_>>()?;
+
+            let creation_timestamp = route.metadata.creation_timestamp.map(|Time(t)| t);
+
+            Ok(OutboundRoute {
+                hostnames,
+                rules,
+                creation_timestamp,
+            })
+        }
+    }
+}
+
+fn convert_linkerd_rule(
+    ns: &str,
+    rule: policy::httproute::HttpRouteRule,
+    cluster: &ClusterInfo,
+    service_info: &HashMap<ServiceRef, ServiceInfo>,
+) -> Result<OutboundRouteRule<HttpRouteMatch>> {
+    let matches = rule
+        .matches
+        .into_iter()
+        .flatten()
+        .map(routes::http::try_match)
+        .collect::<Result<_>>()?;
+
+    let backends = rule
+        .backend_refs
+        .into_iter()
+        .flatten()
+        .filter_map(|b| convert_backend(ns, b, cluster, service_info))
+        .collect();
+
+    let filters = rule
+        .filters
+        .into_iter()
+        .flatten()
+        .map(convert_linkerd_filter)
+        .collect::<Result<_>>()?;
+
+    let request_timeout = rule.timeouts.as_ref().and_then(|timeouts| {
+        let timeout = time::Duration::from(timeouts.request?);
+
+        // zero means "no timeout", per GEP-1742
+        if timeout == time::Duration::from_nanos(0) {
+            return None;
+        }
+
+        Some(timeout)
+    });
+
+    let backend_request_timeout =
+        rule.timeouts
+            .as_ref()
+            .and_then(|timeouts: &policy::httproute::HttpRouteTimeouts| {
+                let timeout = time::Duration::from(timeouts.backend_request?);
+
+                // zero means "no timeout", per GEP-1742
+                if timeout == time::Duration::from_nanos(0) {
+                    return None;
+                }
+
+                Some(timeout)
+            });
+
+    Ok(OutboundRouteRule {
+        matches,
+        backends,
+        request_timeout,
+        backend_request_timeout,
+        filters,
+    })
+}
+
+fn convert_gateway_rule(
+    ns: &str,
+    rule: gateway::HttpRouteRule,
+    cluster: &ClusterInfo,
+    service_info: &HashMap<ServiceRef, ServiceInfo>,
+) -> Result<OutboundRouteRule<HttpRouteMatch>> {
+    let matches = rule
+        .matches
+        .into_iter()
+        .flatten()
+        .map(routes::http::try_match)
+        .collect::<Result<_>>()?;
+
+    let backends = rule
+        .backend_refs
+        .into_iter()
+        .flatten()
+        .filter_map(|b| convert_backend(ns, b, cluster, service_info))
+        .collect();
+
+    let filters = rule
+        .filters
+        .into_iter()
+        .flatten()
+        .map(convert_gateway_filter)
+        .collect::<Result<_>>()?;
+
+    Ok(OutboundRouteRule {
+        matches,
+        backends,
+        request_timeout: None,
+        backend_request_timeout: None,
+        filters,
+    })
+}
+
+pub(crate) fn convert_backend<BackendRef: Into<gateway::HttpBackendRef>>(
+    ns: &str,
+    backend: BackendRef,
+    cluster: &ClusterInfo,
+    services: &HashMap<ServiceRef, ServiceInfo>,
+) -> Option<Backend> {
+    let backend = backend.into();
+    let filters = backend.filters;
+    let backend = backend.backend_ref?;
+    if !is_backend_service(&backend.inner) {
+        return Some(Backend::Invalid {
+            weight: backend.weight.unwrap_or(1).into(),
+            message: format!(
+                "unsupported backend type {group} {kind}",
+                group = backend.inner.group.as_deref().unwrap_or("core"),
+                kind = backend.inner.kind.as_deref().unwrap_or("<empty>"),
+            ),
+        });
+    }
+
+    let name = backend.inner.name;
+    let weight = backend.weight.unwrap_or(1);
+
+    // The gateway API dictates:
+    //
+    // Port is required when the referent is a Kubernetes Service.
+    let port = match backend
+        .inner
+        .port
+        .and_then(|p| NonZeroU16::try_from(p).ok())
+    {
+        Some(port) => port,
+        None => {
+            return Some(Backend::Invalid {
+                weight: weight.into(),
+                message: format!("missing port for backend Service {name}"),
+            })
+        }
+    };
+    let service_ref = ServiceRef {
+        name: name.clone(),
+        namespace: backend.inner.namespace.unwrap_or_else(|| ns.to_string()),
+    };
+
+    let filters = match filters
+        .into_iter()
+        .flatten()
+        .map(convert_gateway_filter)
+        .collect::<Result<_>>()
+    {
+        Ok(filters) => filters,
+        Err(error) => {
+            return Some(Backend::Invalid {
+                weight: backend.weight.unwrap_or(1).into(),
+                message: format!("unsupported backend filter: {error}"),
+            });
+        }
+    };
+
+    Some(Backend::Service(WeightedService {
+        weight: weight.into(),
+        authority: cluster.service_dns_authority(&service_ref.namespace, &name, port),
+        name,
+        namespace: service_ref.namespace.to_string(),
+        port,
+        filters,
+        exists: services.contains_key(&service_ref),
+    }))
+}
+
+fn convert_linkerd_filter(filter: policy::httproute::HttpRouteFilter) -> Result<Filter> {
+    let filter = match filter {
+        policy::httproute::HttpRouteFilter::RequestHeaderModifier {
+            request_header_modifier,
+        } => {
+            let filter = routes::http::header_modifier(request_header_modifier)?;
+            Filter::RequestHeaderModifier(filter)
+        }
+
+        policy::httproute::HttpRouteFilter::ResponseHeaderModifier {
+            response_header_modifier,
+        } => {
+            let filter = routes::http::header_modifier(response_header_modifier)?;
+            Filter::RequestHeaderModifier(filter)
+        }
+
+        policy::httproute::HttpRouteFilter::RequestRedirect { request_redirect } => {
+            let filter = routes::http::req_redirect(request_redirect)?;
+            Filter::RequestRedirect(filter)
+        }
+    };
+    Ok(filter)
+}
+
+pub(crate) fn convert_gateway_filter<RouteFilter: Into<gateway::HttpRouteFilter>>(
+    filter: RouteFilter,
+) -> Result<Filter> {
+    let filter = filter.into();
+    let filter = match filter {
+        gateway::HttpRouteFilter::RequestHeaderModifier {
+            request_header_modifier,
+        } => {
+            let filter = routes::http::header_modifier(request_header_modifier)?;
+            Filter::RequestHeaderModifier(filter)
+        }
+
+        gateway::HttpRouteFilter::ResponseHeaderModifier {
+            response_header_modifier,
+        } => {
+            let filter = routes::http::header_modifier(response_header_modifier)?;
+            Filter::ResponseHeaderModifier(filter)
+        }
+
+        gateway::HttpRouteFilter::RequestRedirect { request_redirect } => {
+            let filter = routes::http::req_redirect(request_redirect)?;
+            Filter::RequestRedirect(filter)
+        }
+        gateway::HttpRouteFilter::RequestMirror { .. } => {
+            bail!("RequestMirror filter is not supported")
+        }
+        gateway::HttpRouteFilter::URLRewrite { .. } => {
+            bail!("URLRewrite filter is not supported")
+        }
+        gateway::HttpRouteFilter::ExtensionRef { .. } => {
+            bail!("ExtensionRef filter is not supported")
+        }
+    };
+    Ok(filter)
+}
+
+#[inline]
+fn is_backend_service(backend: &gateway::BackendObjectReference) -> bool {
+    is_service(
+        backend.group.as_deref(),
+        // Backends default to `Service` if no kind is specified.
+        backend.kind.as_deref().unwrap_or("Service"),
+    )
+}
diff --git a/policy-controller/k8s/index/src/outbound/index/metrics.rs b/policy-controller/k8s/index/src/outbound/index/metrics.rs
index bbde96ee7..d7f698a78 100644
--- a/policy-controller/k8s/index/src/outbound/index/metrics.rs
+++ b/policy-controller/k8s/index/src/outbound/index/metrics.rs
@@ -44,7 +44,7 @@ impl Collector for Instrumented {
         )?;
         for (ns, index) in &this.namespaces.by_ns {
             let labels = vec![("namespace", ns.as_str())];
-            let service_routes = ConstGauge::new(index.service_routes.len() as u32);
+            let service_routes = ConstGauge::new(index.service_http_routes.len() as u32);
             let service_route_encoder = service_route_encoder.encode_family(&labels)?;
             service_routes.encode(service_route_encoder)?;
         }
diff --git a/policy-controller/k8s/index/src/routes.rs b/policy-controller/k8s/index/src/routes.rs
index 1c9d07ad7..538d2bf4e 100644
--- a/policy-controller/k8s/index/src/routes.rs
+++ b/policy-controller/k8s/index/src/routes.rs
@@ -1,53 +1,50 @@
 use linkerd_policy_controller_core::routes::{GroupKindName, GroupKindNamespaceName};
 use linkerd_policy_controller_k8s_api::{gateway as api, policy, Resource, ResourceExt};
 
+pub mod grpc;
 pub mod http;
 
 #[derive(Debug, Clone)]
-pub(crate) enum RouteResource {
+pub(crate) enum HttpRouteResource {
     LinkerdHttp(policy::HttpRoute),
     GatewayHttp(api::HttpRoute),
 }
 
-impl RouteResource {
+impl HttpRouteResource {
     pub(crate) fn name(&self) -> String {
         match self {
-            RouteResource::LinkerdHttp(route) => route.name_unchecked(),
-            RouteResource::GatewayHttp(route) => route.name_unchecked(),
+            Self::LinkerdHttp(route) => route.name_unchecked(),
+            Self::GatewayHttp(route) => route.name_unchecked(),
         }
     }
 
     pub(crate) fn namespace(&self) -> String {
         match self {
-            RouteResource::LinkerdHttp(route) => {
-                route.namespace().expect("HttpRoute must have a namespace")
-            }
-            RouteResource::GatewayHttp(route) => {
-                route.namespace().expect("HttpRoute must have a namespace")
-            }
+            Self::LinkerdHttp(route) => route.namespace().expect("HttpRoute must have a namespace"),
+            Self::GatewayHttp(route) => route.namespace().expect("HttpRoute must have a namespace"),
         }
     }
 
     pub(crate) fn inner(&self) -> &api::CommonRouteSpec {
         match self {
-            RouteResource::LinkerdHttp(route) => &route.spec.inner,
-            RouteResource::GatewayHttp(route) => &route.spec.inner,
+            Self::LinkerdHttp(route) => &route.spec.inner,
+            Self::GatewayHttp(route) => &route.spec.inner,
         }
     }
 
     pub(crate) fn status(&self) -> Option<&api::RouteStatus> {
         match self {
-            RouteResource::LinkerdHttp(route) => route.status.as_ref().map(|status| &status.inner),
-            RouteResource::GatewayHttp(route) => route.status.as_ref().map(|status| &status.inner),
+            Self::LinkerdHttp(route) => route.status.as_ref().map(|status| &status.inner),
+            Self::GatewayHttp(route) => route.status.as_ref().map(|status| &status.inner),
         }
     }
 
     pub(crate) fn gknn(&self) -> GroupKindNamespaceName {
         match self {
-            RouteResource::LinkerdHttp(route) => route
+            Self::LinkerdHttp(route) => route
                 .gkn()
                 .namespaced(route.namespace().expect("Route must have namespace")),
-            RouteResource::GatewayHttp(route) => route
+            Self::GatewayHttp(route) => route
                 .gkn()
                 .namespaced(route.namespace().expect("Route must have namespace")),
         }
diff --git a/policy-controller/k8s/index/src/routes/grpc.rs b/policy-controller/k8s/index/src/routes/grpc.rs
new file mode 100644
index 000000000..1bb6e9756
--- /dev/null
+++ b/policy-controller/k8s/index/src/routes/grpc.rs
@@ -0,0 +1,22 @@
+use anyhow::Result;
+use linkerd_policy_controller_core::routes;
+use linkerd_policy_controller_k8s_api::gateway as k8s_gateway_api;
+
+pub fn try_match(
+    k8s_gateway_api::GrpcRouteMatch { headers, method }: k8s_gateway_api::GrpcRouteMatch,
+) -> Result<routes::GrpcRouteMatch> {
+    let headers = headers
+        .into_iter()
+        .flatten()
+        .map(super::http::header_match)
+        .collect::<Result<_>>()?;
+
+    let method = method.map(|value| match value {
+        k8s_gateway_api::GrpcMethodMatch::Exact { method, service }
+        | k8s_gateway_api::GrpcMethodMatch::RegularExpression { method, service } => {
+            routes::GrpcMethodMatch { method, service }
+        }
+    });
+
+    Ok(routes::GrpcRouteMatch { headers, method })
+}
diff --git a/policy-controller/k8s/status/Cargo.toml b/policy-controller/k8s/status/Cargo.toml
index 5299589fc..605ad246e 100644
--- a/policy-controller/k8s/status/Cargo.toml
+++ b/policy-controller/k8s/status/Cargo.toml
@@ -19,7 +19,7 @@ linkerd-policy-controller-k8s-api = { path = "../api" }
 parking_lot = "0.12"
 prometheus-client = { version = "0.22.0", default-features = false }
 serde = "1"
-serde_json = "1.0.117"
+serde_json = "1.0.118"
 thiserror = "1"
 tokio = { version = "1", features = ["macros"] }
 tracing = "0.1.40"
diff --git a/policy-controller/k8s/status/src/index.rs b/policy-controller/k8s/status/src/index.rs
index 1e714bc1a..eb6f656f7 100644
--- a/policy-controller/k8s/status/src/index.rs
+++ b/policy-controller/k8s/status/src/index.rs
@@ -30,12 +30,12 @@ mod conditions {
     pub const RESOLVED_REFS: &str = "ResolvedRefs";
     pub const ACCEPTED: &str = "Accepted";
 }
-
 mod reasons {
     pub const RESOLVED_REFS: &str = "ResolvedRefs";
     pub const BACKEND_NOT_FOUND: &str = "BackendNotFound";
     pub const INVALID_KIND: &str = "InvalidKind";
     pub const NO_MATCHING_PARENT: &str = "NoMatchingParent";
+    pub const ROUTE_REASON_CONFLICTED: &str = "RouteReasonConflicted";
 }
 
 mod cond_statuses {
@@ -360,13 +360,24 @@ impl Index {
 
     fn parent_status(
         &self,
+        id: &NamespaceGroupKindName,
         parent_ref: &routes::ParentReference,
         backend_condition: k8s_core_api::Condition,
     ) -> Option<k8s_gateway_api::RouteParentStatus> {
         match parent_ref {
             routes::ParentReference::Server(server) => {
                 let condition = if self.servers.contains(server) {
-                    accepted()
+                    // If this route is an HTTPRoute and there exists a GRPCRoute
+                    // with the same parent, the HTTPRoute should not be accepted
+                    // because it is less specific.
+                    // https://gateway-api.sigs.k8s.io/geps/gep-1426/#route-types
+                    if id.gkn.kind == k8s_gateway_api::HttpRoute::kind(&())
+                        && self.parent_has_grpcroute_children(parent_ref)
+                    {
+                        route_conflicted()
+                    } else {
+                        accepted()
+                    }
                 } else {
                     no_matching_parent()
                 };
@@ -391,7 +402,17 @@ impl Index {
                     .get(service)
                     .map_or(false, |svc| svc.valid_parent_service())
                 {
-                    accepted()
+                    // If this route is an HTTPRoute and there exists a GRPCRoute
+                    // with the same parent, the HTTPRoute should not be accepted
+                    // because it is less specific.
+                    // https://gateway-api.sigs.k8s.io/geps/gep-1426/#route-types
+                    if id.gkn.kind == k8s_gateway_api::HttpRoute::kind(&())
+                        && self.parent_has_grpcroute_children(parent_ref)
+                    {
+                        route_conflicted()
+                    } else {
+                        accepted()
+                    }
                 } else {
                     no_matching_parent()
                 };
@@ -413,6 +434,13 @@ impl Index {
         }
     }
 
+    fn parent_has_grpcroute_children(&self, parent_ref: &routes::ParentReference) -> bool {
+        self.route_refs.iter().any(|(id, route)| {
+            id.gkn.kind == k8s_gateway_api::GrpcRoute::kind(&())
+                && route.parents.contains(parent_ref)
+        })
+    }
+
     fn backend_condition(
         &self,
         backend_refs: &[routes::BackendReference],
@@ -456,7 +484,7 @@ impl Index {
         let parent_statuses = route
             .parents
             .iter()
-            .filter_map(|parent_ref| self.parent_status(parent_ref, backend_condition.clone()));
+            .filter_map(|parent_ref| self.parent_status(id, parent_ref, backend_condition.clone()));
 
         let all_statuses = unowned_statuses.chain(parent_statuses).collect::<Vec<_>>();
 
@@ -785,22 +813,7 @@ impl Index {
             return;
         }
 
-        // Create a patch for the route and send it to the Controller so
-        // that it is applied.
-        if let Some(patch) = self.make_route_patch(&id, &route) {
-            match self.updates.try_send(Update {
-                id: id.clone(),
-                patch,
-            }) {
-                Ok(()) => {
-                    self.metrics.patch_enqueues.inc();
-                }
-                Err(error) => {
-                    self.metrics.patch_channel_full.inc();
-                    tracing::error!(%id.namespace, route = ?id.gkn, %error, "Failed to send route patch");
-                }
-            }
-        }
+        self.reconcile()
     }
 }
 
@@ -848,6 +861,17 @@ fn no_matching_parent() -> k8s_core_api::Condition {
     }
 }
 
+fn route_conflicted() -> k8s_core_api::Condition {
+    k8s_core_api::Condition {
+        last_transition_time: k8s_core_api::Time(now()),
+        message: "".to_string(),
+        observed_generation: None,
+        reason: reasons::ROUTE_REASON_CONFLICTED.to_string(),
+        status: cond_statuses::STATUS_FALSE.to_string(),
+        type_: conditions::ACCEPTED.to_string(),
+    }
+}
+
 fn accepted() -> k8s_core_api::Condition {
     k8s_core_api::Condition {
         last_transition_time: k8s_core_api::Time(now()),
diff --git a/policy-controller/src/main.rs b/policy-controller/src/main.rs
index 552509921..594bf2b46 100644
--- a/policy-controller/src/main.rs
+++ b/policy-controller/src/main.rs
@@ -262,8 +262,11 @@ async fn main() -> Result<()> {
 
     let gateway_grpc_routes =
         runtime.watch_all::<k8s_gateway_api::GrpcRoute>(watcher::Config::default());
+    let gateway_grpc_routes_indexes = IndexList::new(outbound_index.clone())
+        .push(status_index.clone())
+        .shared();
     tokio::spawn(
-        kubert::index::namespaced(status_index.clone(), gateway_grpc_routes)
+        kubert::index::namespaced(gateway_grpc_routes_indexes.clone(), gateway_grpc_routes)
             .instrument(info_span!("grpcroutes.gateway.networking.k8s.io")),
     );
 
diff --git a/viz/charts/linkerd-viz/README.md b/viz/charts/linkerd-viz/README.md
index 6baf8e4c7..8ed126c47 100644
--- a/viz/charts/linkerd-viz/README.md
+++ b/viz/charts/linkerd-viz/README.md
@@ -128,7 +128,7 @@ Kubernetes: `>=1.22.0-0`
 | namespaceMetadata.image.name | string | `"extension-init"` | Docker image name for the namespace-metadata instance |
 | namespaceMetadata.image.pullPolicy | string | defaultImagePullPolicy | Pull policy for the namespace-metadata instance |
 | namespaceMetadata.image.registry | string | defaultRegistry | Docker registry for the namespace-metadata instance |
-| namespaceMetadata.image.tag | string | `"v0.1.0"` | Docker image tag for the namespace-metadata instance |
+| namespaceMetadata.image.tag | string | `"v0.1.1"` | Docker image tag for the namespace-metadata instance |
 | namespaceMetadata.nodeSelector | object | `{"kubernetes.io/os":"linux"}` | NodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
 | namespaceMetadata.tolerations | string | `nil` | Tolerations section, See the [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for more information |
 | nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Default nodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
diff --git a/viz/charts/linkerd-viz/values.yaml b/viz/charts/linkerd-viz/values.yaml
index 3e4acfe9f..c3ebc6af5 100644
--- a/viz/charts/linkerd-viz/values.yaml
+++ b/viz/charts/linkerd-viz/values.yaml
@@ -407,7 +407,7 @@ namespaceMetadata:
     # -- Docker image name for the namespace-metadata instance
     name: extension-init
     # -- Docker image tag for the namespace-metadata instance
-    tag: v0.1.0
+    tag: v0.1.1
     # -- Pull policy for the namespace-metadata instance
     # @default -- defaultImagePullPolicy
     pullPolicy: ""
diff --git a/web/app/package.json b/web/app/package.json
index 4000ed679..980378d7d 100644
--- a/web/app/package.json
+++ b/web/app/package.json
@@ -62,9 +62,9 @@
     "eslint": "^8.57.0",
     "eslint-config-airbnb": "^19.0.4",
     "eslint-plugin-import": "^2.29.1",
-    "eslint-plugin-jsx-a11y": "^6.7.1",
+    "eslint-plugin-jsx-a11y": "^6.9.0",
     "eslint-plugin-promise": "^6.2.0",
-    "eslint-plugin-react": "^7.34.2",
+    "eslint-plugin-react": "^7.34.3",
     "eslint-plugin-react-hooks": "^4.6.2",
     "eslint-webpack-plugin": "^4.2.0",
     "file-loader": "^6.2.0",
diff --git a/web/app/yarn.lock b/web/app/yarn.lock
index abe918997..07963dd9e 100644
--- a/web/app/yarn.lock
+++ b/web/app/yarn.lock
@@ -1099,7 +1099,7 @@
   resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
   integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
 
-"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.24.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.24.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
   version "7.24.7"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
   integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
@@ -2515,7 +2515,7 @@ argparse@^2.0.1:
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
   integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
 
-aria-query@^5.1.3:
+aria-query@~5.1.3:
   version "5.1.3"
   resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e"
   integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
@@ -2543,7 +2543,7 @@ array-flatten@1.1.1:
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
 
-array-includes@^3.1.5, array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8:
+array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8:
   version "3.1.8"
   resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d"
   integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==
@@ -2609,7 +2609,7 @@ array.prototype.findlastindex@^1.2.3:
     es-shim-unscopables "^1.0.0"
     get-intrinsic "^1.2.1"
 
-array.prototype.flat@^1.2.3, array.prototype.flat@^1.3.2:
+array.prototype.flat@^1.2.3, array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18"
   integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==
@@ -2619,7 +2619,7 @@ array.prototype.flat@^1.2.3, array.prototype.flat@^1.3.2:
     es-abstract "^1.22.1"
     es-shim-unscopables "^1.0.0"
 
-array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2:
+array.prototype.flatmap@^1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527"
   integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==
@@ -2639,15 +2639,15 @@ array.prototype.toreversed@^1.1.2:
     es-abstract "^1.22.1"
     es-shim-unscopables "^1.0.0"
 
-array.prototype.tosorted@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz#c8c89348337e51b8a3c48a9227f9ce93ceedcba8"
-  integrity sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==
+array.prototype.tosorted@^1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc"
+  integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==
   dependencies:
-    call-bind "^1.0.5"
+    call-bind "^1.0.7"
     define-properties "^1.2.1"
-    es-abstract "^1.22.3"
-    es-errors "^1.1.0"
+    es-abstract "^1.23.3"
+    es-errors "^1.3.0"
     es-shim-unscopables "^1.0.2"
 
 arraybuffer.prototype.slice@^1.0.2:
@@ -2682,10 +2682,10 @@ assertion-error@^1.1.0:
   resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
   integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
 
-ast-types-flow@^0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
-  integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
+ast-types-flow@^0.0.8:
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6"
+  integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==
 
 asynckit@^0.4.0:
   version "0.4.0"
@@ -2709,12 +2709,12 @@ available-typed-arrays@^1.0.6, available-typed-arrays@^1.0.7:
   dependencies:
     possible-typed-array-names "^1.0.0"
 
-axe-core@^4.6.2:
-  version "4.6.2"
-  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.2.tgz#6e566ab2a3d29e415f5115bc0fd2597a5eb3e5e3"
-  integrity sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==
+axe-core@^4.9.1:
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae"
+  integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==
 
-axobject-query@^3.1.1:
+axobject-query@~3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1"
   integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==
@@ -4090,6 +4090,58 @@ es-abstract@^1.17.4, es-abstract@^1.18.0, es-abstract@^1.18.0-next.2:
     string.prototype.trimstart "^1.0.4"
     unbox-primitive "^1.0.1"
 
+es-abstract@^1.17.5, es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.3:
+  version "1.23.3"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0"
+  integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==
+  dependencies:
+    array-buffer-byte-length "^1.0.1"
+    arraybuffer.prototype.slice "^1.0.3"
+    available-typed-arrays "^1.0.7"
+    call-bind "^1.0.7"
+    data-view-buffer "^1.0.1"
+    data-view-byte-length "^1.0.1"
+    data-view-byte-offset "^1.0.0"
+    es-define-property "^1.0.0"
+    es-errors "^1.3.0"
+    es-object-atoms "^1.0.0"
+    es-set-tostringtag "^2.0.3"
+    es-to-primitive "^1.2.1"
+    function.prototype.name "^1.1.6"
+    get-intrinsic "^1.2.4"
+    get-symbol-description "^1.0.2"
+    globalthis "^1.0.3"
+    gopd "^1.0.1"
+    has-property-descriptors "^1.0.2"
+    has-proto "^1.0.3"
+    has-symbols "^1.0.3"
+    hasown "^2.0.2"
+    internal-slot "^1.0.7"
+    is-array-buffer "^3.0.4"
+    is-callable "^1.2.7"
+    is-data-view "^1.0.1"
+    is-negative-zero "^2.0.3"
+    is-regex "^1.1.4"
+    is-shared-array-buffer "^1.0.3"
+    is-string "^1.0.7"
+    is-typed-array "^1.1.13"
+    is-weakref "^1.0.2"
+    object-inspect "^1.13.1"
+    object-keys "^1.1.1"
+    object.assign "^4.1.5"
+    regexp.prototype.flags "^1.5.2"
+    safe-array-concat "^1.1.2"
+    safe-regex-test "^1.0.3"
+    string.prototype.trim "^1.2.9"
+    string.prototype.trimend "^1.0.8"
+    string.prototype.trimstart "^1.0.8"
+    typed-array-buffer "^1.0.2"
+    typed-array-byte-length "^1.0.1"
+    typed-array-byte-offset "^1.0.2"
+    typed-array-length "^1.0.6"
+    unbox-primitive "^1.0.2"
+    which-typed-array "^1.1.15"
+
 es-abstract@^1.19.0:
   version "1.19.1"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
@@ -4208,58 +4260,6 @@ es-abstract@^1.22.3:
     unbox-primitive "^1.0.2"
     which-typed-array "^1.1.14"
 
-es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.3:
-  version "1.23.3"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0"
-  integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==
-  dependencies:
-    array-buffer-byte-length "^1.0.1"
-    arraybuffer.prototype.slice "^1.0.3"
-    available-typed-arrays "^1.0.7"
-    call-bind "^1.0.7"
-    data-view-buffer "^1.0.1"
-    data-view-byte-length "^1.0.1"
-    data-view-byte-offset "^1.0.0"
-    es-define-property "^1.0.0"
-    es-errors "^1.3.0"
-    es-object-atoms "^1.0.0"
-    es-set-tostringtag "^2.0.3"
-    es-to-primitive "^1.2.1"
-    function.prototype.name "^1.1.6"
-    get-intrinsic "^1.2.4"
-    get-symbol-description "^1.0.2"
-    globalthis "^1.0.3"
-    gopd "^1.0.1"
-    has-property-descriptors "^1.0.2"
-    has-proto "^1.0.3"
-    has-symbols "^1.0.3"
-    hasown "^2.0.2"
-    internal-slot "^1.0.7"
-    is-array-buffer "^3.0.4"
-    is-callable "^1.2.7"
-    is-data-view "^1.0.1"
-    is-negative-zero "^2.0.3"
-    is-regex "^1.1.4"
-    is-shared-array-buffer "^1.0.3"
-    is-string "^1.0.7"
-    is-typed-array "^1.1.13"
-    is-weakref "^1.0.2"
-    object-inspect "^1.13.1"
-    object-keys "^1.1.1"
-    object.assign "^4.1.5"
-    regexp.prototype.flags "^1.5.2"
-    safe-array-concat "^1.1.2"
-    safe-regex-test "^1.0.3"
-    string.prototype.trim "^1.2.9"
-    string.prototype.trimend "^1.0.8"
-    string.prototype.trimstart "^1.0.8"
-    typed-array-buffer "^1.0.2"
-    typed-array-byte-length "^1.0.1"
-    typed-array-byte-offset "^1.0.2"
-    typed-array-length "^1.0.6"
-    unbox-primitive "^1.0.2"
-    which-typed-array "^1.1.15"
-
 es-array-method-boxes-properly@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e"
@@ -4272,7 +4272,7 @@ es-define-property@^1.0.0:
   dependencies:
     get-intrinsic "^1.2.4"
 
-es-errors@^1.0.0, es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0:
+es-errors@^1.0.0, es-errors@^1.2.1, es-errors@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
   integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
@@ -4460,27 +4460,27 @@ eslint-plugin-import@^2.29.1:
     semver "^6.3.1"
     tsconfig-paths "^3.15.0"
 
-eslint-plugin-jsx-a11y@^6.7.1:
-  version "6.7.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976"
-  integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==
+eslint-plugin-jsx-a11y@^6.9.0:
+  version "6.9.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz#67ab8ff460d4d3d6a0b4a570e9c1670a0a8245c8"
+  integrity sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==
   dependencies:
-    "@babel/runtime" "^7.20.7"
-    aria-query "^5.1.3"
-    array-includes "^3.1.6"
-    array.prototype.flatmap "^1.3.1"
-    ast-types-flow "^0.0.7"
-    axe-core "^4.6.2"
-    axobject-query "^3.1.1"
+    aria-query "~5.1.3"
+    array-includes "^3.1.8"
+    array.prototype.flatmap "^1.3.2"
+    ast-types-flow "^0.0.8"
+    axe-core "^4.9.1"
+    axobject-query "~3.1.1"
     damerau-levenshtein "^1.0.8"
     emoji-regex "^9.2.2"
-    has "^1.0.3"
-    jsx-ast-utils "^3.3.3"
-    language-tags "=1.0.5"
+    es-iterator-helpers "^1.0.19"
+    hasown "^2.0.2"
+    jsx-ast-utils "^3.3.5"
+    language-tags "^1.0.9"
     minimatch "^3.1.2"
-    object.entries "^1.1.6"
-    object.fromentries "^2.0.6"
-    semver "^6.3.0"
+    object.fromentries "^2.0.8"
+    safe-regex-test "^1.0.3"
+    string.prototype.includes "^2.0.0"
 
 eslint-plugin-promise@^6.2.0:
   version "6.2.0"
@@ -4492,16 +4492,16 @@ eslint-plugin-react-hooks@^4.6.2:
   resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596"
   integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
 
-eslint-plugin-react@^7.34.2:
-  version "7.34.2"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.2.tgz#2780a1a35a51aca379d86d29b9a72adc6bfe6b66"
-  integrity sha512-2HCmrU+/JNigDN6tg55cRDKCQWicYAPB38JGSFDQt95jDm8rrvSUo7YPkOIm5l6ts1j1zCvysNcasvfTMQzUOw==
+eslint-plugin-react@^7.34.3:
+  version "7.34.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz#9965f27bd1250a787b5d4cfcc765e5a5d58dcb7b"
+  integrity sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==
   dependencies:
     array-includes "^3.1.8"
     array.prototype.findlast "^1.2.5"
     array.prototype.flatmap "^1.3.2"
     array.prototype.toreversed "^1.1.2"
-    array.prototype.tosorted "^1.1.3"
+    array.prototype.tosorted "^1.1.4"
     doctrine "^2.1.0"
     es-iterator-helpers "^1.0.19"
     estraverse "^5.3.0"
@@ -5263,21 +5263,7 @@ has@^1.0.3:
   dependencies:
     function-bind "^1.1.1"
 
-hasown@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
-  integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
-  dependencies:
-    function-bind "^1.1.2"
-
-hasown@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa"
-  integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==
-  dependencies:
-    function-bind "^1.1.2"
-
-hasown@^2.0.2:
+hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
   integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
@@ -6657,13 +6643,15 @@ jss@10.8.0, jss@^10.5.1:
     is-in-browser "^1.1.3"
     tiny-warning "^1.0.2"
 
-"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3:
-  version "3.3.3"
-  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea"
-  integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==
+"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5:
+  version "3.3.5"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
+  integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==
   dependencies:
-    array-includes "^3.1.5"
-    object.assign "^4.1.3"
+    array-includes "^3.1.6"
+    array.prototype.flat "^1.3.1"
+    object.assign "^4.1.4"
+    object.values "^1.1.6"
 
 just-extend@^6.2.0:
   version "6.2.0"
@@ -6680,17 +6668,17 @@ kleur@^3.0.3:
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
   integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
 
-language-subtag-registry@~0.3.2:
-  version "0.3.21"
-  resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
-  integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==
+language-subtag-registry@^0.3.20:
+  version "0.3.23"
+  resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7"
+  integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==
 
-language-tags@=1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a"
-  integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==
+language-tags@^1.0.9:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777"
+  integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==
   dependencies:
-    language-subtag-registry "~0.3.2"
+    language-subtag-registry "^0.3.20"
 
 launch-editor@^2.6.1:
   version "2.6.1"
@@ -7178,7 +7166,7 @@ object-keys@^1.0.12, object-keys@^1.1.1:
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
 
-object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.3, object.assign@^4.1.4, object.assign@^4.1.5:
+object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.4, object.assign@^4.1.5:
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
   integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
@@ -7188,7 +7176,7 @@ object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.3, object.assign@
     has-symbols "^1.0.3"
     object-keys "^1.1.1"
 
-object.entries@^1.1.1, object.entries@^1.1.2, object.entries@^1.1.5, object.entries@^1.1.6, object.entries@^1.1.8:
+object.entries@^1.1.1, object.entries@^1.1.2, object.entries@^1.1.5, object.entries@^1.1.8:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41"
   integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==
@@ -7197,7 +7185,7 @@ object.entries@^1.1.1, object.entries@^1.1.2, object.entries@^1.1.5, object.entr
     define-properties "^1.2.1"
     es-object-atoms "^1.0.0"
 
-object.fromentries@^2.0.6, object.fromentries@^2.0.7, object.fromentries@^2.0.8:
+object.fromentries@^2.0.7, object.fromentries@^2.0.8:
   version "2.0.8"
   resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65"
   integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==
@@ -7226,7 +7214,7 @@ object.hasown@^1.1.4:
     es-abstract "^1.23.2"
     es-object-atoms "^1.0.0"
 
-object.values@^1.1.1, object.values@^1.1.7, object.values@^1.2.0:
+object.values@^1.1.1, object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b"
   integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==
@@ -8312,16 +8300,7 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
-safe-regex-test@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
-  integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
-  dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.1.3"
-    is-regex "^1.1.4"
-
-safe-regex-test@^1.0.3:
+safe-regex-test@^1.0.0, safe-regex-test@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
   integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==
@@ -8744,6 +8723,14 @@ string-width@^5.0.1, string-width@^5.1.2:
     emoji-regex "^9.2.2"
     strip-ansi "^7.0.1"
 
+string.prototype.includes@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz#8986d57aee66d5460c144620a6d873778ad7289f"
+  integrity sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==
+  dependencies:
+    define-properties "^1.1.3"
+    es-abstract "^1.17.5"
+
 string.prototype.matchall@^4.0.11:
   version "4.0.11"
   resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a"


Test patch:
diff --git a/cli/cmd/install_test.go b/cli/cmd/install_test.go
index 020040ffa..1be81a738 100644
--- a/cli/cmd/install_test.go
+++ b/cli/cmd/install_test.go
@@ -129,16 +129,6 @@ func TestRender(t *testing.T) {
 				Version:    "ProxyInitVersion",
 			},
 			IgnoreOutboundPorts: "443",
-			Resources: &charts.Resources{
-				CPU: charts.Constraints{
-					Limit:   "100m",
-					Request: "10m",
-				},
-				Memory: charts.Constraints{
-					Limit:   "50Mi",
-					Request: "10Mi",
-				},
-			},
 			XTMountPath: &charts.VolumeMountPath{
 				MountPath: "/run",
 				Name:      "linkerd-proxy-init-xtables-lock",
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
index 7512beced..f66223d56 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
@@ -197,13 +197,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
index 2cef3d0cb..b77c4f358 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
@@ -197,13 +197,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -434,13 +427,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
index 84d60d3eb..ca8abe01f 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
@@ -197,13 +197,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_contour.golden.yml b/cli/cmd/testdata/inject_contour.golden.yml
index 1eadf9825..73bf0b478 100644
--- a/cli/cmd/testdata/inject_contour.golden.yml
+++ b/cli/cmd/testdata/inject_contour.golden.yml
@@ -237,13 +237,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml b/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
index 601c0c69c..a789384e7 100644
--- a/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
@@ -208,13 +208,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -456,13 +449,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -704,13 +690,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -952,13 +931,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
index dc73ff638..2a24f9194 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
@@ -208,13 +208,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_access_log.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_access_log.golden.yml
index 173f2c388..90cd71bf1 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_access_log.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_access_log.golden.yml
@@ -211,13 +211,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.golden.yml
index c9fe281ce..0b2c4304a 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_automountServiceAccountToken_false.golden.yml
@@ -200,13 +200,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
index abb4dcb2c..599e06af7 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
@@ -222,13 +222,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
index 9a0f93a64..ae2381dc7 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
@@ -227,11 +227,11 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: 100m
-            memory: 20Mi
+            cpu: "1"
+            memory: 256Mi
           requests:
-            cpu: 100m
-            memory: 20Mi
+            cpu: 500m
+            memory: 64Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
index 77de4b77e..99133a8ce 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
@@ -208,13 +208,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -456,13 +449,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
index 65a195bea..aded7673a 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
@@ -221,13 +221,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
index 9ee818825..e2dcbb38d 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
@@ -208,13 +208,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
index 6fb718c6d..2ec373cc0 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
@@ -209,13 +209,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_native_sidecar.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_native_sidecar.golden.yml
index 0e9b29186..c23cdf613 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_native_sidecar.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_native_sidecar.golden.yml
@@ -52,13 +52,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
index 8ad9d8c0e..93c5ed510 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
@@ -209,13 +209,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         name: linkerd-network-validator
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_opaque_ports.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_opaque_ports.golden.yml
index 9b4443cd2..def762260 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_opaque_ports.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_opaque_ports.golden.yml
@@ -209,13 +209,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
index 3bfdba0ed..50586f0e5 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
@@ -209,13 +209,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_params.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_params.golden.yml
index 10d709425..e8339aae2 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_params.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_params.golden.yml
@@ -208,13 +208,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
index 0a490eee1..59eb1f50c 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
@@ -210,13 +210,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
index 9b8446390..7a3d99bba 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
@@ -210,13 +210,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_list.golden.yml b/cli/cmd/testdata/inject_emojivoto_list.golden.yml
index 6903e9625..af2512218 100644
--- a/cli/cmd/testdata/inject_emojivoto_list.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_list.golden.yml
@@ -210,13 +210,6 @@ items:
           image: cr.l5d.io/linkerd/proxy-init:v2.4.1
           imagePullPolicy: IfNotPresent
           name: linkerd-init
-          resources:
-            limits:
-              cpu: 100m
-              memory: 20Mi
-            requests:
-              cpu: 100m
-              memory: 20Mi
           securityContext:
             allowPrivilegeEscalation: false
             capabilities:
@@ -452,13 +445,6 @@ items:
           image: cr.l5d.io/linkerd/proxy-init:v2.4.1
           imagePullPolicy: IfNotPresent
           name: linkerd-init
-          resources:
-            limits:
-              cpu: 100m
-              memory: 20Mi
-            requests:
-              cpu: 100m
-              memory: 20Mi
           securityContext:
             allowPrivilegeEscalation: false
             capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml b/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
index 54b3c6512..f2971bd21 100644
--- a/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
@@ -210,13 +210,6 @@ items:
           image: cr.l5d.io/linkerd/proxy-init:v2.4.1
           imagePullPolicy: IfNotPresent
           name: linkerd-init
-          resources:
-            limits:
-              cpu: 100m
-              memory: 20Mi
-            requests:
-              cpu: 100m
-              memory: 20Mi
           securityContext:
             allowPrivilegeEscalation: false
             capabilities:
@@ -452,13 +445,6 @@ items:
           image: cr.l5d.io/linkerd/proxy-init:v2.4.1
           imagePullPolicy: IfNotPresent
           name: linkerd-init
-          resources:
-            limits:
-              cpu: 100m
-              memory: 20Mi
-            requests:
-              cpu: 100m
-              memory: 20Mi
           securityContext:
             allowPrivilegeEscalation: false
             capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
index 68d3b574c..64dbd8c98 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
@@ -192,13 +192,6 @@ spec:
     image: cr.l5d.io/linkerd/proxy-init:v2.4.1
     imagePullPolicy: IfNotPresent
     name: linkerd-init
-    resources:
-      limits:
-        cpu: 100m
-        memory: 20Mi
-      requests:
-        cpu: 100m
-        memory: 20Mi
     securityContext:
       allowPrivilegeEscalation: false
       capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod_ingress.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod_ingress.golden.yml
index df73a73f9..15ecfe922 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod_ingress.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod_ingress.golden.yml
@@ -195,13 +195,6 @@ spec:
     image: cr.l5d.io/linkerd/proxy-init:v2.4.1
     imagePullPolicy: IfNotPresent
     name: linkerd-init
-    resources:
-      limits:
-        cpu: 100m
-        memory: 20Mi
-      requests:
-        cpu: 100m
-        memory: 20Mi
     securityContext:
       allowPrivilegeEscalation: false
       capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
index 1def79ac1..e11a95c24 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
@@ -194,13 +194,6 @@ spec:
     image: cr.l5d.io/linkerd/proxy-init:v2.4.1
     imagePullPolicy: IfNotPresent
     name: linkerd-init
-    resources:
-      limits:
-        cpu: 100m
-        memory: 20Mi
-      requests:
-        cpu: 100m
-        memory: 20Mi
     securityContext:
       allowPrivilegeEscalation: false
       capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
index 4ac568027..7f33b3f9e 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
@@ -205,11 +205,11 @@ spec:
     name: linkerd-init
     resources:
       limits:
-        cpu: 100m
-        memory: 20Mi
+        cpu: 160m
+        memory: 150Mi
       requests:
-        cpu: 100m
-        memory: 20Mi
+        cpu: 110m
+        memory: 100Mi
     securityContext:
       allowPrivilegeEscalation: false
       capabilities:
diff --git a/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml b/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
index d408c9c84..7a2597803 100644
--- a/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
@@ -209,13 +209,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml b/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
index 217ff2e57..53707d1c0 100644
--- a/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
+++ b/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
@@ -210,13 +210,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -460,13 +453,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml b/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
index 24aa752b1..58ab6a5d6 100644
--- a/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
+++ b/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
@@ -271,13 +271,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy-init:v2.4.1
         imagePullPolicy: IfNotPresent
         name: linkerd-init
-        resources:
-          limits:
-            cpu: 100m
-            memory: 20Mi
-          requests:
-            cpu: 100m
-            memory: 20Mi
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_controlplane_tracing_output.golden b/cli/cmd/testdata/install_controlplane_tracing_output.golden
index a948dfcd1..4c521bb16 100644
--- a/cli/cmd/testdata/install_controlplane_tracing_output.golden
+++ b/cli/cmd/testdata/install_controlplane_tracing_output.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1140,12 +1131,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1285,6 +1270,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1628,12 +1614,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1739,6 +1719,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1993,12 +1974,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_custom_domain.golden b/cli/cmd/testdata/install_custom_domain.golden
index 4fecab99e..e7de829b5 100644
--- a/cli/cmd/testdata/install_custom_domain.golden
+++ b/cli/cmd/testdata/install_custom_domain.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1139,12 +1130,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1284,6 +1269,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1626,12 +1612,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1737,6 +1717,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1991,12 +1972,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index 8ff609cc0..285e8a64d 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1139,12 +1130,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1284,6 +1269,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1626,12 +1612,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1737,6 +1717,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1991,12 +1972,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden
index 4fecab99e..e7de829b5 100644
--- a/cli/cmd/testdata/install_default.golden
+++ b/cli/cmd/testdata/install_default.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1139,12 +1130,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1284,6 +1269,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1626,12 +1612,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1737,6 +1717,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1991,12 +1972,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_default_override_dst_get_nets.golden b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
index 879966f3b..bd4474cb8 100644
--- a/cli/cmd/testdata/install_default_override_dst_get_nets.golden
+++ b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1139,12 +1130,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1284,6 +1269,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1626,12 +1612,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1737,6 +1717,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1991,12 +1972,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_default_token.golden b/cli/cmd/testdata/install_default_token.golden
index faacabc09..dec581f37 100644
--- a/cli/cmd/testdata/install_default_token.golden
+++ b/cli/cmd/testdata/install_default_token.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1137,12 +1128,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1275,6 +1260,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1615,12 +1601,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1719,6 +1699,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1971,12 +1952,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_gid_output.golden b/cli/cmd/testdata/install_gid_output.golden
index a379191a9..e276828d5 100755
--- a/cli/cmd/testdata/install_gid_output.golden
+++ b/cli/cmd/testdata/install_gid_output.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1143,12 +1134,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1288,6 +1273,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1636,12 +1622,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1748,6 +1728,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -2006,12 +1987,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden
index 6b7b6b6a4..b3c5100dc 100644
--- a/cli/cmd/testdata/install_ha_output.golden
+++ b/cli/cmd/testdata/install_ha_output.golden
@@ -770,16 +770,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1222,8 +1213,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1382,6 +1372,7 @@ spec:
     matchLabels:
       linkerd.io/control-plane-component: destination
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1755,8 +1746,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1871,6 +1861,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -2156,8 +2147,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
diff --git a/cli/cmd/testdata/install_ha_with_overrides_output.golden b/cli/cmd/testdata/install_ha_with_overrides_output.golden
index 47419154f..1c3ca44ad 100644
--- a/cli/cmd/testdata/install_ha_with_overrides_output.golden
+++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden
@@ -770,16 +770,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1222,11 +1213,10 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
-            cpu: "100m"
-            memory: "20Mi"
+            cpu: "400m"
+            memory: "300Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1382,6 +1372,7 @@ spec:
     matchLabels:
       linkerd.io/control-plane-component: destination
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1755,11 +1746,10 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
-            cpu: "100m"
-            memory: "20Mi"
+            cpu: "400m"
+            memory: "300Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1871,6 +1861,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -2156,11 +2147,10 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
-            cpu: "100m"
-            memory: "20Mi"
+            cpu: "400m"
+            memory: "300Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_heartbeat_disabled_output.golden b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
index 505e51e6e..b6e8d7cf7 100644
--- a/cli/cmd/testdata/install_heartbeat_disabled_output.golden
+++ b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
@@ -674,16 +674,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1070,12 +1061,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1215,6 +1200,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1557,12 +1543,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1608,6 +1588,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1862,12 +1843,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_helm_control_plane_output.golden b/cli/cmd/testdata/install_helm_control_plane_output.golden
index bb04189aa..7349c82dd 100644
--- a/cli/cmd/testdata/install_helm_control_plane_output.golden
+++ b/cli/cmd/testdata/install_helm_control_plane_output.golden
@@ -720,16 +720,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1112,12 +1103,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1259,6 +1244,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1601,12 +1587,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1716,6 +1696,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1970,12 +1951,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_helm_control_plane_output_ha.golden b/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
index 38d4b2cac..41a3b93d3 100644
--- a/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_control_plane_output_ha.golden
@@ -747,16 +747,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1195,8 +1186,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1357,6 +1347,7 @@ spec:
     matchLabels:
       linkerd.io/control-plane-component: destination
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1730,8 +1721,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1850,6 +1840,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -2135,8 +2126,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
diff --git a/cli/cmd/testdata/install_helm_control_plane_output_ha_with_gid.golden b/cli/cmd/testdata/install_helm_control_plane_output_ha_with_gid.golden
index ac92e9443..7b8412dcd 100755
--- a/cli/cmd/testdata/install_helm_control_plane_output_ha_with_gid.golden
+++ b/cli/cmd/testdata/install_helm_control_plane_output_ha_with_gid.golden
@@ -747,16 +747,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1199,8 +1190,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1361,6 +1351,7 @@ spec:
     matchLabels:
       linkerd.io/control-plane-component: destination
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1740,8 +1731,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1861,6 +1851,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -2150,8 +2141,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
diff --git a/cli/cmd/testdata/install_helm_output_ha_labels.golden b/cli/cmd/testdata/install_helm_output_ha_labels.golden
index d3b1d38a7..4cef7c269 100644
--- a/cli/cmd/testdata/install_helm_output_ha_labels.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_labels.golden
@@ -751,16 +751,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1203,8 +1194,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1365,6 +1355,7 @@ spec:
     matchLabels:
       linkerd.io/control-plane-component: destination
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1742,8 +1733,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1866,6 +1856,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -2155,8 +2146,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
diff --git a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
index 535b7ba64..db933e292 100644
--- a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
@@ -742,16 +742,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1185,8 +1176,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1347,6 +1337,7 @@ spec:
     matchLabels:
       linkerd.io/control-plane-component: destination
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1720,8 +1711,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
@@ -1840,6 +1830,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -2125,8 +2116,7 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "20Mi"
+            memory: "250Mi"
           requests:
             cpu: "100m"
             memory: "20Mi"
diff --git a/cli/cmd/testdata/install_no_init_container.golden b/cli/cmd/testdata/install_no_init_container.golden
index 3159a5348..b758a8dd0 100644
--- a/cli/cmd/testdata/install_no_init_container.golden
+++ b/cli/cmd/testdata/install_no_init_container.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1127,12 +1118,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1277,6 +1262,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1607,12 +1593,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1723,6 +1703,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1965,12 +1946,6 @@ spec:
         image: cr.l5d.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden
index 133dc1bc9..bc14d8384 100644
--- a/cli/cmd/testdata/install_output.golden
+++ b/cli/cmd/testdata/install_output.golden
@@ -696,16 +696,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 10m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 50Mi
-          request: 10Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1084,11 +1075,11 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "50Mi"
+            cpu: "cpu-limit"
+            memory: "memory-limit"
           requests:
-            cpu: "10m"
-            memory: "10Mi"
+            cpu: "cpu-request"
+            memory: "memory-request"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1229,6 +1220,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1570,11 +1562,11 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "50Mi"
+            cpu: "cpu-limit"
+            memory: "memory-limit"
           requests:
-            cpu: "10m"
-            memory: "10Mi"
+            cpu: "cpu-request"
+            memory: "memory-request"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1683,6 +1675,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1929,11 +1922,11 @@ spec:
         name: linkerd-init
         resources:
           limits:
-            cpu: "100m"
-            memory: "50Mi"
+            cpu: "cpu-limit"
+            memory: "memory-limit"
           requests:
-            cpu: "10m"
-            memory: "10Mi"
+            cpu: "cpu-request"
+            memory: "memory-request"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -2001,7 +1994,7 @@ spec:
 ---
 apiVersion: v1
 data:
-  linkerd-config-overrides: Y2xpVmVyc2lvbjogQ2xpVmVyc2lvbgpjbHVzdGVyTmV0d29ya3M6IENsdXN0ZXJOZXR3b3Jrcwpjb250cm9sUGxhbmVUcmFjaW5nTmFtZXNwYWNlOiAiIgpjb250cm9sbGVyOiBudWxsCmNvbnRyb2xsZXJHSUQ6IDIxMDMKY29udHJvbGxlckltYWdlOiBDb250cm9sbGVySW1hZ2UKY29udHJvbGxlckxvZ0Zvcm1hdDogQ29udHJvbGxlckxvZ0Zvcm1hdApjb250cm9sbGVyTG9nTGV2ZWw6IENvbnRyb2xsZXJMb2dMZXZlbApkZWJ1Z0NvbnRhaW5lcjoKICBpbWFnZToKICAgIG5hbWU6IERlYnVnSW1hZ2VOYW1lCiAgICBwdWxsUG9saWN5OiBEZWJ1Z0ltYWdlUHVsbFBvbGljeQogICAgdmVyc2lvbjogRGVidWdWZXJzaW9uCmRlc3RpbmF0aW9uQ29udHJvbGxlcjogbnVsbApkaXNhYmxlSVB2NjogZmFsc2UKZW5hYmxlRW5kcG9pbnRTbGljZXM6IGZhbHNlCmhlYXJ0YmVhdFNjaGVkdWxlOiAxIDIgMyA0IDUKaWRlbnRpdHk6CiAgaXNzdWVyOgogICAgdGxzOgogICAgICBjcnRQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICAgICAgICBNSUlCd0RDQ0FXZWdBd0lCQWdJUkFKUklnWjhSdE84RXdnMVhlcGY4VDQ0d0NnWUlLb1pJemowRUF3SXdLVEVuCiAgICAgICAgTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQogICAgICAgIE9EQTNNVE0wTjFvWERUTXdNRGd5TmpBM01UTTBOMW93S1RFbk1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHUKICAgICAgICBhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxL0ZwCiAgICAgICAgZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQogICAgICAgIDIvSnh5aVNneEtXUmRvYXkrYU53TUc0d0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUIKICAgICAgICBBZjhDQVFBd0hRWURWUjBPQkJZRUZJMVducnFNWUthSEhPbyt6cHlpaURxMnBPMEtNQ2tHQTFVZEVRUWlNQ0NDCiAgICAgICAgSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQogICAgICAgIEFpQXR1b0k1WHVDdHJHVlJ6U21SVGwycmEyOGFWOU15VFU3ZDVxblRBRkhLU2dJZ1JLQ3ZsdU9TZ0E1TzIxcDUKICAgICAgICA1MXRkcm1rSEVaUnIwcWxMU0pkSFlnRWZNems9CiAgICAgICAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQogICAgICBrZXlQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KICAgICAgICBNSGNDQVFFRUlBQWU4bmZielp1OWMvT0IyKzh4Sk0wRno3TlV3VFFhenVsa0ZOczRUSTUrb0FvR0NDcUdTTTQ5CiAgICAgICAgQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgogICAgICAgIGRRdlJhWWFudXhEMzZEdDEyL0p4eWlTZ3hLV1Jkb2F5K1E9PQogICAgICAgIC0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KaWRlbnRpdHlUcnVzdEFuY2hvcnNQRU06IHwKICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICBNSUlCd1RDQ0FXYWdBd0lCQWdJUWVEWnA1bERhSXlnUTVVZk1LWnJGQVRBS0JnZ3Foa2pPUFFRREFqQXBNU2N3CiAgSlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1clpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d0hoY05NakF3T0RJNAogIE1EY3hNalEzV2hjTk16QXdPREkyTURjeE1qUTNXakFwTVNjd0pRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXIKICBaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnFjNzBaCiAgbDF2Z3c3OXJqQjV1U0lUSUNVQTZHeWZ2U0ZmY3VJaXM3Qi9YRlNra3dBSFU1Uy9zMUFBUCtSMFRYN0hCV1VDNAogIHVhRzRXV3Npd0pLTm43bWdvM0F3YmpBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIKICAvd0lCQVRBZEJnTlZIUTRFRmdRVTVZdGpWVlBmZDdJN05MSHNuMkMyNkVCeUdWMHdLUVlEVlIwUkJDSXdJSUllCiAgYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQwogIElRQ043bEJGTEREdmp4NlYwK1hranBLRVJSc0pZZjVhZE12bmxvRmw0OGlsSmdJaEFOdHhobmRjcitRSlB1QzgKICB2Z1VDMGQyLzlGTXVlSVZNYis0NldUQ09qc3FyCiAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQppbWFnZVB1bGxQb2xpY3k6IEltYWdlUHVsbFBvbGljeQppbWFnZVB1bGxTZWNyZXRzOiBudWxsCmxpbmtlcmRWZXJzaW9uOiBMaW5rZXJkVmVyc2lvbgpuZXR3b3JrVmFsaWRhdG9yOgogIGVuYWJsZVNlY3VyaXR5Q29udGV4dDogZmFsc2UKcG9kTW9uaXRvcjogbnVsbApwb2xpY3lDb250cm9sbGVyOgogIGltYWdlOgogICAgbmFtZTogUG9saWN5Q29udHJvbGxlckltYWdlTmFtZQogICAgcHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CiAgICB2ZXJzaW9uOiBQb2xpY3lDb250cm9sbGVyVmVyc2lvbgogIGxvZ0xldmVsOiBsb2ctbGV2ZWwKICByZXNvdXJjZXM6CiAgICBjcHU6CiAgICAgIGxpbWl0OiBjcHUtbGltaXQKICAgICAgcmVxdWVzdDogY3B1LXJlcXVlc3QKICAgIG1lbW9yeToKICAgICAgbGltaXQ6IG1lbW9yeS1saW1pdAogICAgICByZXF1ZXN0OiBtZW1vcnktcmVxdWVzdApwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJpb3JpdHlDbGFzc05hbWU6IFByaW9yaXR5Q2xhc3NOYW1lCnByb2ZpbGVWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHByb2ZpbGUgdmFsaWRhdG9yIENBIGJ1bmRsZQogIGV4dGVybmFsU2VjcmV0OiB0cnVlCnByb3h5OgogIGNvbnRyb2w6IG51bGwKICBkZWZhdWx0SW5ib3VuZFBvbGljeTogZGVmYXVsdC1hbGxvdy1wb2xpY3kKICBnaWQ6IDIxMDIKICBpbWFnZToKICAgIG5hbWU6IFByb3h5SW1hZ2VOYW1lCiAgICBwdWxsUG9saWN5OiBJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IFByb3h5VmVyc2lvbgogIGluYm91bmRDb25uZWN0VGltZW91dDogIiIKICBpbmJvdW5kRGlzY292ZXJ5Q2FjaGVVbnVzZWRUaW1lb3V0OiAiIgogIGxvZ0xldmVsOiB3YXJuLGxpbmtlcmQ9aW5mbwogIG9wYXF1ZVBvcnRzOiAyNSw0NDMsNTg3LDMzMDYsNTQzMiwxMTIxMQogIG91dGJvdW5kQ29ubmVjdFRpbWVvdXQ6ICIiCiAgb3V0Ym91bmREaXNjb3ZlcnlDYWNoZVVudXNlZFRpbWVvdXQ6ICIiCiAgcmVzb3VyY2VzOgogICAgY3B1OgogICAgICBsaW1pdDogY3B1LWxpbWl0CiAgICAgIHJlcXVlc3Q6IGNwdS1yZXF1ZXN0CiAgICBtZW1vcnk6CiAgICAgIGxpbWl0OiBtZW1vcnktbGltaXQKICAgICAgcmVxdWVzdDogbWVtb3J5LXJlcXVlc3QKICBzdGFydHVwUHJvYmU6IG51bGwKcHJveHlDb250YWluZXJOYW1lOiBQcm94eUNvbnRhaW5lck5hbWUKcHJveHlJbml0OgogIGlnbm9yZUluYm91bmRQb3J0czogIiIKICBpZ25vcmVPdXRib3VuZFBvcnRzOiAiNDQzIgogIGltYWdlOgogICAgbmFtZTogUHJveHlJbml0SW1hZ2VOYW1lCiAgICBwdWxsUG9saWN5OiBJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IFByb3h5SW5pdFZlcnNpb24KICBrdWJlQVBJU2VydmVyUG9ydHM6ICIiCiAgcmVzb3VyY2VzOgogICAgY3B1OgogICAgICByZXF1ZXN0OiAxMG0KICAgIG1lbW9yeToKICAgICAgbGltaXQ6IDUwTWkKICAgICAgcmVxdWVzdDogMTBNaQpwcm94eUluamVjdG9yOgogIGNhQnVuZGxlOiBwcm94eSBpbmplY3RvciBDQSBidW5kbGUKICBleHRlcm5hbFNlY3JldDogdHJ1ZQp3ZWJob29rRmFpbHVyZVBvbGljeTogV2ViaG9va0ZhaWx1cmVQb2xpY3kK
+  linkerd-config-overrides: Y2xpVmVyc2lvbjogQ2xpVmVyc2lvbgpjbHVzdGVyTmV0d29ya3M6IENsdXN0ZXJOZXR3b3Jrcwpjb250cm9sUGxhbmVUcmFjaW5nTmFtZXNwYWNlOiAiIgpjb250cm9sbGVyOiBudWxsCmNvbnRyb2xsZXJHSUQ6IDIxMDMKY29udHJvbGxlckltYWdlOiBDb250cm9sbGVySW1hZ2UKY29udHJvbGxlckxvZ0Zvcm1hdDogQ29udHJvbGxlckxvZ0Zvcm1hdApjb250cm9sbGVyTG9nTGV2ZWw6IENvbnRyb2xsZXJMb2dMZXZlbApkZWJ1Z0NvbnRhaW5lcjoKICBpbWFnZToKICAgIG5hbWU6IERlYnVnSW1hZ2VOYW1lCiAgICBwdWxsUG9saWN5OiBEZWJ1Z0ltYWdlUHVsbFBvbGljeQogICAgdmVyc2lvbjogRGVidWdWZXJzaW9uCmRlc3RpbmF0aW9uQ29udHJvbGxlcjogbnVsbApkaXNhYmxlSVB2NjogZmFsc2UKZW5hYmxlRW5kcG9pbnRTbGljZXM6IGZhbHNlCmhlYXJ0YmVhdFNjaGVkdWxlOiAxIDIgMyA0IDUKaWRlbnRpdHk6CiAgaXNzdWVyOgogICAgdGxzOgogICAgICBjcnRQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICAgICAgICBNSUlCd0RDQ0FXZWdBd0lCQWdJUkFKUklnWjhSdE84RXdnMVhlcGY4VDQ0d0NnWUlLb1pJemowRUF3SXdLVEVuCiAgICAgICAgTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01CNFhEVEl3TURneQogICAgICAgIE9EQTNNVE0wTjFvWERUTXdNRGd5TmpBM01UTTBOMW93S1RFbk1DVUdBMVVFQXhNZWFXUmxiblJwZEhrdWJHbHUKICAgICAgICBhMlZ5WkM1amJIVnpkR1Z5TG14dlkyRnNNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxL0ZwCiAgICAgICAgZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyZFF2UmFZYW51eEQzNkR0MQogICAgICAgIDIvSnh5aVNneEtXUmRvYXkrYU53TUc0d0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUIKICAgICAgICBBZjhDQVFBd0hRWURWUjBPQkJZRUZJMVducnFNWUthSEhPbyt6cHlpaURxMnBPMEtNQ2tHQTFVZEVRUWlNQ0NDCiAgICAgICAgSG1sa1pXNTBhWFI1TG14cGJtdGxjbVF1WTJ4MWMzUmxjaTVzYjJOaGJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQogICAgICAgIEFpQXR1b0k1WHVDdHJHVlJ6U21SVGwycmEyOGFWOU15VFU3ZDVxblRBRkhLU2dJZ1JLQ3ZsdU9TZ0E1TzIxcDUKICAgICAgICA1MXRkcm1rSEVaUnIwcWxMU0pkSFlnRWZNems9CiAgICAgICAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQogICAgICBrZXlQRU06IHwKICAgICAgICAtLS0tLUJFR0lOIEVDIFBSSVZBVEUgS0VZLS0tLS0KICAgICAgICBNSGNDQVFFRUlBQWU4bmZielp1OWMvT0IyKzh4Sk0wRno3TlV3VFFhenVsa0ZOczRUSTUrb0FvR0NDcUdTTTQ5CiAgICAgICAgQXdFSG9VUURRZ0FFMS9GcGZjUm5EY2VkTDZBalVhWFlQdjRESU1CYUp1Zk9JNU5XdHkrWFNYN0pqWGdadE03MgogICAgICAgIGRRdlJhWWFudXhEMzZEdDEyL0p4eWlTZ3hLV1Jkb2F5K1E9PQogICAgICAgIC0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KaWRlbnRpdHlUcnVzdEFuY2hvcnNQRU06IHwKICAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KICBNSUlCd1RDQ0FXYWdBd0lCQWdJUWVEWnA1bERhSXlnUTVVZk1LWnJGQVRBS0JnZ3Foa2pPUFFRREFqQXBNU2N3CiAgSlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1clpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d0hoY05NakF3T0RJNAogIE1EY3hNalEzV2hjTk16QXdPREkyTURjeE1qUTNXakFwTVNjd0pRWURWUVFERXg1cFpHVnVkR2wwZVM1c2FXNXIKICBaWEprTG1Oc2RYTjBaWEl1Ykc5allXd3dXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnFjNzBaCiAgbDF2Z3c3OXJqQjV1U0lUSUNVQTZHeWZ2U0ZmY3VJaXM3Qi9YRlNra3dBSFU1Uy9zMUFBUCtSMFRYN0hCV1VDNAogIHVhRzRXV3Npd0pLTm43bWdvM0F3YmpBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIKICAvd0lCQVRBZEJnTlZIUTRFRmdRVTVZdGpWVlBmZDdJN05MSHNuMkMyNkVCeUdWMHdLUVlEVlIwUkJDSXdJSUllCiAgYVdSbGJuUnBkSGt1YkdsdWEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQwogIElRQ043bEJGTEREdmp4NlYwK1hranBLRVJSc0pZZjVhZE12bmxvRmw0OGlsSmdJaEFOdHhobmRjcitRSlB1QzgKICB2Z1VDMGQyLzlGTXVlSVZNYis0NldUQ09qc3FyCiAgLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQppbWFnZVB1bGxQb2xpY3k6IEltYWdlUHVsbFBvbGljeQppbWFnZVB1bGxTZWNyZXRzOiBudWxsCmxpbmtlcmRWZXJzaW9uOiBMaW5rZXJkVmVyc2lvbgpuZXR3b3JrVmFsaWRhdG9yOgogIGVuYWJsZVNlY3VyaXR5Q29udGV4dDogZmFsc2UKcG9kTW9uaXRvcjogbnVsbApwb2xpY3lDb250cm9sbGVyOgogIGltYWdlOgogICAgbmFtZTogUG9saWN5Q29udHJvbGxlckltYWdlTmFtZQogICAgcHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CiAgICB2ZXJzaW9uOiBQb2xpY3lDb250cm9sbGVyVmVyc2lvbgogIGxvZ0xldmVsOiBsb2ctbGV2ZWwKICByZXNvdXJjZXM6CiAgICBjcHU6CiAgICAgIGxpbWl0OiBjcHUtbGltaXQKICAgICAgcmVxdWVzdDogY3B1LXJlcXVlc3QKICAgIG1lbW9yeToKICAgICAgbGltaXQ6IG1lbW9yeS1saW1pdAogICAgICByZXF1ZXN0OiBtZW1vcnktcmVxdWVzdApwb2xpY3lWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHBvbGljeSB2YWxpZGF0b3IgQ0EgYnVuZGxlCiAgZXh0ZXJuYWxTZWNyZXQ6IHRydWUKcHJpb3JpdHlDbGFzc05hbWU6IFByaW9yaXR5Q2xhc3NOYW1lCnByb2ZpbGVWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHByb2ZpbGUgdmFsaWRhdG9yIENBIGJ1bmRsZQogIGV4dGVybmFsU2VjcmV0OiB0cnVlCnByb3h5OgogIGNvbnRyb2w6IG51bGwKICBkZWZhdWx0SW5ib3VuZFBvbGljeTogZGVmYXVsdC1hbGxvdy1wb2xpY3kKICBnaWQ6IDIxMDIKICBpbWFnZToKICAgIG5hbWU6IFByb3h5SW1hZ2VOYW1lCiAgICBwdWxsUG9saWN5OiBJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IFByb3h5VmVyc2lvbgogIGluYm91bmRDb25uZWN0VGltZW91dDogIiIKICBpbmJvdW5kRGlzY292ZXJ5Q2FjaGVVbnVzZWRUaW1lb3V0OiAiIgogIGxvZ0xldmVsOiB3YXJuLGxpbmtlcmQ9aW5mbwogIG9wYXF1ZVBvcnRzOiAyNSw0NDMsNTg3LDMzMDYsNTQzMiwxMTIxMQogIG91dGJvdW5kQ29ubmVjdFRpbWVvdXQ6ICIiCiAgb3V0Ym91bmREaXNjb3ZlcnlDYWNoZVVudXNlZFRpbWVvdXQ6ICIiCiAgcmVzb3VyY2VzOgogICAgY3B1OgogICAgICBsaW1pdDogY3B1LWxpbWl0CiAgICAgIHJlcXVlc3Q6IGNwdS1yZXF1ZXN0CiAgICBtZW1vcnk6CiAgICAgIGxpbWl0OiBtZW1vcnktbGltaXQKICAgICAgcmVxdWVzdDogbWVtb3J5LXJlcXVlc3QKICBzdGFydHVwUHJvYmU6IG51bGwKcHJveHlDb250YWluZXJOYW1lOiBQcm94eUNvbnRhaW5lck5hbWUKcHJveHlJbml0OgogIGlnbm9yZUluYm91bmRQb3J0czogIiIKICBpZ25vcmVPdXRib3VuZFBvcnRzOiAiNDQzIgogIGltYWdlOgogICAgbmFtZTogUHJveHlJbml0SW1hZ2VOYW1lCiAgICBwdWxsUG9saWN5OiBJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IFByb3h5SW5pdFZlcnNpb24KICBrdWJlQVBJU2VydmVyUG9ydHM6ICIiCnByb3h5SW5qZWN0b3I6CiAgY2FCdW5kbGU6IHByb3h5IGluamVjdG9yIENBIGJ1bmRsZQogIGV4dGVybmFsU2VjcmV0OiB0cnVlCndlYmhvb2tGYWlsdXJlUG9saWN5OiBXZWJob29rRmFpbHVyZVBvbGljeQo=
 kind: Secret
 metadata:
   creationTimestamp: null
diff --git a/cli/cmd/testdata/install_proxy_ignores.golden b/cli/cmd/testdata/install_proxy_ignores.golden
index 2973f1b0c..7359ae0a3 100644
--- a/cli/cmd/testdata/install_proxy_ignores.golden
+++ b/cli/cmd/testdata/install_proxy_ignores.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1139,12 +1130,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1284,6 +1269,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1626,12 +1612,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1737,6 +1717,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1991,12 +1972,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/cli/cmd/testdata/install_values_file.golden b/cli/cmd/testdata/install_values_file.golden
index 60b479156..c690c3923 100644
--- a/cli/cmd/testdata/install_values_file.golden
+++ b/cli/cmd/testdata/install_values_file.golden
@@ -743,16 +743,7 @@ data:
       logFormat: ""
       logLevel: ""
       privileged: false
-      resources:
-        cpu:
-          limit: 100m
-          request: 100m
-        ephemeral-storage:
-          limit: ""
-          request: ""
-        memory:
-          limit: 20Mi
-          request: 20Mi
+      resources: null
       runAsGroup: 65534
       runAsRoot: false
       runAsUser: 65534
@@ -1139,12 +1130,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1284,6 +1269,7 @@ spec:
     port: 443
     targetPort: policy-https
 ---
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1626,12 +1612,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
@@ -1737,6 +1717,7 @@ spec:
 ###
 ### Proxy Injector
 ###
+
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -1991,12 +1972,6 @@ spec:
         imagePullPolicy: IfNotPresent
         name: linkerd-init
         resources:
-          limits:
-            cpu: "100m"
-            memory: "20Mi"
-          requests:
-            cpu: "100m"
-            memory: "20Mi"
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
diff --git a/pkg/charts/linkerd2/values_test.go b/pkg/charts/linkerd2/values_test.go
index 22c1893e7..95f1860f2 100644
--- a/pkg/charts/linkerd2/values_test.go
+++ b/pkg/charts/linkerd2/values_test.go
@@ -201,16 +201,6 @@ func TestNewValues(t *testing.T) {
 				Name:    "cr.l5d.io/linkerd/proxy-init",
 				Version: testVersion,
 			},
-			Resources: &Resources{
-				CPU: Constraints{
-					Limit:   "100m",
-					Request: "100m",
-				},
-				Memory: Constraints{
-					Limit:   "20Mi",
-					Request: "20Mi",
-				},
-			},
 			XTMountPath: &VolumeMountPath{
 				Name:      "linkerd-proxy-init-xtables-lock",
 				MountPath: "/run",
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index f93916205..a83b85119 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -1802,13 +1802,7 @@ data:
         name: ProxyInitImageName
         pullPolicy: ImagePullPolicy
         version: ProxyInitVersion
-      resources:
-        cpu:
-          limit: 100m
-          request: 10m
-        memory:
-          limit: 50Mi
-          request: 10Mi
+      resources: null
       saMountPath: null
       xtMountPath:
         mountPath: /run
@@ -1865,16 +1859,6 @@ data:
 						PullPolicy: "ImagePullPolicy",
 						Version:    "ProxyInitVersion",
 					},
-					Resources: &linkerd2.Resources{
-						CPU: linkerd2.Constraints{
-							Limit:   "100m",
-							Request: "10m",
-						},
-						Memory: linkerd2.Constraints{
-							Limit:   "50Mi",
-							Request: "10Mi",
-						},
-					},
 					XTMountPath: &linkerd2.VolumeMountPath{
 						MountPath: "/run",
 						Name:      "linkerd-proxy-init-xtables-lock",
@@ -1960,12 +1944,6 @@ data:
           pullPolicy: ImagePullPolicy
           version: ProxyInitVersion
         resources:
-          cpu:
-            limit: 100m
-            request: 10m
-          memory:
-            limit: 50Mi
-            request: 10Mi
         saMountPath: null
         xtMountPath:
           mountPath: /run
@@ -2022,16 +2000,6 @@ data:
 						PullPolicy: "ImagePullPolicy",
 						Version:    "ProxyInitVersion",
 					},
-					Resources: &linkerd2.Resources{
-						CPU: linkerd2.Constraints{
-							Limit:   "100m",
-							Request: "10m",
-						},
-						Memory: linkerd2.Constraints{
-							Limit:   "50Mi",
-							Request: "10Mi",
-						},
-					},
 					XTMountPath: &linkerd2.VolumeMountPath{
 						MountPath: "/run",
 						Name:      "linkerd-proxy-init-xtables-lock",
diff --git a/policy-controller/k8s/index/src/outbound/tests/routes.rs b/policy-controller/k8s/index/src/outbound/tests/routes.rs
index 8074a0f70..f4e23ff7c 100644
--- a/policy-controller/k8s/index/src/outbound/tests/routes.rs
+++ b/policy-controller/k8s/index/src/outbound/tests/routes.rs
@@ -1,1 +1,2 @@
+mod grpc;
 mod http;
diff --git a/policy-controller/k8s/index/src/outbound/tests/routes/grpc.rs b/policy-controller/k8s/index/src/outbound/tests/routes/grpc.rs
new file mode 100644
index 000000000..d8245bdb1
--- /dev/null
+++ b/policy-controller/k8s/index/src/outbound/tests/routes/grpc.rs
@@ -0,0 +1,174 @@
+use kube::Resource;
+use linkerd_policy_controller_core::{
+    outbound::{Backend, WeightedService},
+    routes::GroupKindNamespaceName,
+    POLICY_CONTROLLER_NAME,
+};
+use linkerd_policy_controller_k8s_api::gateway as k8s_gateway_api;
+use tracing::Level;
+
+use super::super::*;
+
+#[test]
+fn backend_service() {
+    tracing_subscriber::fmt()
+        .with_max_level(Level::TRACE)
+        .try_init()
+        .ok();
+
+    let test = TestConfig::default();
+    // Create apex service.
+    let apex = mk_service("ns", "apex", 8080);
+    test.index.write().apply(apex);
+
+    // Create httproute.
+    let route = mk_route("ns", "route", 8080, "apex", "backend");
+    test.index.write().apply(route);
+
+    let mut rx = test
+        .index
+        .write()
+        .outbound_policy_rx(
+            "apex".to_string(),
+            "ns".to_string(),
+            8080.try_into().unwrap(),
+            "ns".to_string(),
+        )
+        .expect("apex.ns should exist");
+
+    {
+        let policy = rx.borrow_and_update();
+        let backend = policy
+            .grpc_routes
+            .get(&GroupKindNamespaceName {
+                group: k8s_gateway_api::GrpcRoute::group(&()),
+                kind: k8s_gateway_api::GrpcRoute::kind(&()),
+                namespace: "ns".into(),
+                name: "route".into(),
+            })
+            .expect("route should exist")
+            .rules
+            .first()
+            .expect("rule should exist")
+            .backends
+            .first()
+            .expect("backend should exist");
+
+        let exists = match backend {
+            Backend::Service(WeightedService { exists, .. }) => exists,
+            _ => panic!("backend should be a service"),
+        };
+
+        // Backend should not exist.
+        assert!(!exists);
+    }
+
+    // Create backend service.
+    let backend = mk_service("ns", "backend", 8080);
+    test.index.write().apply(backend);
+    assert!(rx.has_changed().unwrap());
+
+    {
+        let policy = rx.borrow_and_update();
+        let backend = policy
+            .grpc_routes
+            .get(&GroupKindNamespaceName {
+                group: k8s_gateway_api::GrpcRoute::group(&()),
+                kind: k8s_gateway_api::GrpcRoute::kind(&()),
+                namespace: "ns".into(),
+                name: "route".into(),
+            })
+            .expect("route should exist")
+            .rules
+            .first()
+            .expect("rule should exist")
+            .backends
+            .first()
+            .expect("backend should exist");
+
+        let exists = match backend {
+            Backend::Service(WeightedService { exists, .. }) => exists,
+            _ => panic!("backend should be a service"),
+        };
+
+        // Backend should exist.
+        assert!(exists);
+    }
+}
+
+fn mk_route(
+    ns: impl ToString,
+    name: impl ToString,
+    port: u16,
+    parent: impl ToString,
+    backend: impl ToString,
+) -> k8s_gateway_api::GrpcRoute {
+    use chrono::Utc;
+    use k8s::{policy::httproute::*, Time};
+
+    k8s_gateway_api::GrpcRoute {
+        metadata: k8s::ObjectMeta {
+            namespace: Some(ns.to_string()),
+            name: Some(name.to_string()),
+            creation_timestamp: Some(Time(Utc::now())),
+            ..Default::default()
+        },
+        spec: k8s_gateway_api::GrpcRouteSpec {
+            inner: CommonRouteSpec {
+                parent_refs: Some(vec![ParentReference {
+                    group: Some("core".to_string()),
+                    kind: Some("Service".to_string()),
+                    namespace: Some(ns.to_string()),
+                    name: parent.to_string(),
+                    section_name: None,
+                    port: Some(port),
+                }]),
+            },
+            hostnames: None,
+            rules: Some(vec![k8s_gateway_api::GrpcRouteRule {
+                matches: Some(vec![k8s_gateway_api::GrpcRouteMatch {
+                    headers: None,
+                    method: Some(k8s_gateway_api::GrpcMethodMatch::Exact {
+                        method: Some("Test".to_string()),
+                        service: Some("io.linkerd.Testing".to_string()),
+                    }),
+                }]),
+                filters: None,
+                backend_refs: Some(vec![k8s_gateway_api::GrpcRouteBackendRef {
+                    filters: None,
+                    weight: None,
+                    inner: BackendObjectReference {
+                        group: Some("core".to_string()),
+                        kind: Some("Service".to_string()),
+                        namespace: Some(ns.to_string()),
+                        name: backend.to_string(),
+                        port: Some(port),
+                    },
+                }]),
+            }]),
+        },
+        status: Some(k8s_gateway_api::GrpcRouteStatus {
+            inner: RouteStatus {
+                parents: vec![k8s::gateway::RouteParentStatus {
+                    parent_ref: ParentReference {
+                        group: Some("core".to_string()),
+                        kind: Some("Service".to_string()),
+                        namespace: Some(ns.to_string()),
+                        name: parent.to_string(),
+                        section_name: None,
+                        port: Some(port),
+                    },
+                    controller_name: POLICY_CONTROLLER_NAME.to_string(),
+                    conditions: vec![k8s::Condition {
+                        last_transition_time: Time(chrono::DateTime::<Utc>::MIN_UTC),
+                        message: "".to_string(),
+                        observed_generation: None,
+                        reason: "Accepted".to_string(),
+                        status: "True".to_string(),
+                        type_: "Accepted".to_string(),
+                    }],
+                }],
+            },
+        }),
+    }
+}
diff --git a/policy-controller/k8s/index/src/outbound/tests/routes/http.rs b/policy-controller/k8s/index/src/outbound/tests/routes/http.rs
index 6ad80286f..dc672b339 100644
--- a/policy-controller/k8s/index/src/outbound/tests/routes/http.rs
+++ b/policy-controller/k8s/index/src/outbound/tests/routes/http.rs
@@ -1,6 +1,6 @@
 use kube::Resource;
 use linkerd_policy_controller_core::{
-    outbound::{Backend, OutboundRouteCollection, WeightedService},
+    outbound::{Backend, WeightedService},
     routes::GroupKindNamespaceName,
     POLICY_CONTROLLER_NAME,
 };
@@ -39,12 +39,8 @@ fn backend_service() {
 
     {
         let policy = rx.borrow_and_update();
-        let backend = Some(&policy.routes)
-            .map(|routes| match routes {
-                OutboundRouteCollection::Http(routes) => routes,
-                _ => panic!("expected http route collection"),
-            })
-            .unwrap()
+        let backend = policy
+            .http_routes
             .get(&GroupKindNamespaceName {
                 group: k8s::policy::HttpRoute::group(&()),
                 kind: k8s::policy::HttpRoute::kind(&()),
@@ -76,12 +72,8 @@ fn backend_service() {
 
     {
         let policy = rx.borrow_and_update();
-        let backend = Some(&policy.routes)
-            .map(|routes| match routes {
-                OutboundRouteCollection::Http(routes) => routes,
-                _ => panic!("expected http route collection"),
-            })
-            .unwrap()
+        let backend = policy
+            .http_routes
             .get(&GroupKindNamespaceName {
                 group: k8s::policy::HttpRoute::group(&()),
                 kind: k8s::policy::HttpRoute::kind(&()),
diff --git a/policy-controller/k8s/status/src/tests/routes/grpc.rs b/policy-controller/k8s/status/src/tests/routes/grpc.rs
index 006e1862d..af578ab41 100644
--- a/policy-controller/k8s/status/src/tests/routes/grpc.rs
+++ b/policy-controller/k8s/status/src/tests/routes/grpc.rs
@@ -457,6 +457,139 @@ fn route_rejected_after_server_delete() {
     assert!(updates_rx.try_recv().is_err());
 }
 
+#[test]
+fn service_route_type_conflict() {
+    let hostname = "test";
+    let claim = kubert::lease::Claim {
+        holder: "test".to_string(),
+        expiry: DateTime::<Utc>::MAX_UTC,
+    };
+    let (_claims_tx, claims_rx) = watch::channel(Arc::new(claim));
+    let (updates_tx, mut updates_rx) = mpsc::channel(10000);
+    let index = Index::shared(
+        hostname,
+        claims_rx,
+        updates_tx,
+        IndexMetrics::register(&mut Default::default()),
+    );
+
+    // Apply the parent service
+    let parent = super::make_service("ns-0", "svc");
+    index.write().apply(parent.clone());
+
+    let parent = k8s_gateway_api::ParentReference {
+        group: Some("core".to_string()),
+        kind: Some("Service".to_string()),
+        namespace: parent.namespace(),
+        name: parent.name_unchecked(),
+        section_name: None,
+        port: Some(8080),
+    };
+
+    // Apply the HTTP route.
+    let http_id = NamespaceGroupKindName {
+        namespace: parent.namespace.as_deref().unwrap().to_string(),
+        gkn: GroupKindName {
+            group: k8s_gateway_api::HttpRoute::group(&()),
+            kind: k8s_gateway_api::HttpRoute::kind(&()),
+            name: "httproute-foo".into(),
+        },
+    };
+    let http_route = k8s_gateway_api::HttpRoute {
+        status: None,
+        metadata: k8s_core_api::ObjectMeta {
+            name: Some(http_id.gkn.name.to_string()),
+            namespace: Some(http_id.namespace.clone()),
+            creation_timestamp: Some(k8s_core_api::Time(Utc::now())),
+            ..Default::default()
+        },
+        spec: k8s_gateway_api::HttpRouteSpec {
+            inner: k8s_gateway_api::CommonRouteSpec {
+                parent_refs: Some(vec![parent.clone()]),
+            },
+            hostnames: None,
+            rules: Some(vec![]),
+        },
+    };
+    index.write().apply(http_route);
+
+    // Create the expected update -- HTTPRoute should be accepted
+    let accepted_condition = k8s_core_api::Condition {
+        last_transition_time: k8s_core_api::Time(DateTime::<Utc>::MIN_UTC),
+        message: "".to_string(),
+        observed_generation: None,
+        reason: "Accepted".to_string(),
+        status: "True".to_string(),
+        type_: "Accepted".to_string(),
+    };
+    // No backends were specified, so we have vacuously resolved them all.
+    let backend_condition = k8s_core_api::Condition {
+        last_transition_time: k8s_core_api::Time(DateTime::<Utc>::MIN_UTC),
+        message: "".to_string(),
+        observed_generation: None,
+        reason: "ResolvedRefs".to_string(),
+        status: "True".to_string(),
+        type_: "ResolvedRefs".to_string(),
+    };
+    let parent_status = k8s_gateway_api::RouteParentStatus {
+        parent_ref: parent.clone(),
+        controller_name: POLICY_CONTROLLER_NAME.to_string(),
+        conditions: vec![accepted_condition.clone(), backend_condition.clone()],
+    };
+    let status = make_status(vec![parent_status]);
+    let patch = crate::index::make_patch(&http_id, status).unwrap();
+    let update = updates_rx.try_recv().unwrap();
+    assert_eq!(http_id, update.id);
+    assert_eq!(patch, update.patch);
+
+    // Apply the GRPC route.
+    let grpc_id = NamespaceGroupKindName {
+        namespace: parent.namespace.as_deref().unwrap().to_string(),
+        gkn: GroupKindName {
+            group: k8s_gateway_api::GrpcRoute::group(&()),
+            kind: k8s_gateway_api::GrpcRoute::kind(&()),
+            name: "grpcroute-foo".into(),
+        },
+    };
+    let route = make_route(&grpc_id, parent.clone(), None);
+    index.write().apply(route);
+
+    // Two expected updates: HTTPRoute should be rejected and GRPCRoute should be accepted
+    for _ in 0..2 {
+        let update = updates_rx.try_recv().unwrap();
+        if update.id.gkn.kind == k8s_gateway_api::HttpRoute::kind(&()) {
+            let conflict_condition = k8s_core_api::Condition {
+                last_transition_time: k8s_core_api::Time(DateTime::<Utc>::MIN_UTC),
+                message: "".to_string(),
+                observed_generation: None,
+                reason: "RouteReasonConflicted".to_string(),
+                status: "False".to_string(),
+                type_: "Accepted".to_string(),
+            };
+            let parent_status = k8s_gateway_api::RouteParentStatus {
+                parent_ref: parent.clone(),
+                controller_name: POLICY_CONTROLLER_NAME.to_string(),
+                conditions: vec![conflict_condition, backend_condition.clone()],
+            };
+            let status = make_status(vec![parent_status]);
+            let patch = crate::index::make_patch(&http_id, status).unwrap();
+            assert_eq!(patch, update.patch);
+        } else {
+            let parent_status = k8s_gateway_api::RouteParentStatus {
+                parent_ref: parent.clone(),
+                controller_name: POLICY_CONTROLLER_NAME.to_string(),
+                conditions: vec![accepted_condition.clone(), backend_condition.clone()],
+            };
+            let status = make_status(vec![parent_status]);
+            let patch = crate::index::make_patch(&grpc_id, status).unwrap();
+            assert_eq!(patch, update.patch);
+        }
+    }
+
+    // No more updates.
+    assert!(updates_rx.try_recv().is_err())
+}
+
 fn make_route(
     id: &NamespaceGroupKindName,
     parent: k8s_gateway_api::ParentReference,
diff --git a/test/integration/install/inject/inject_test.go b/test/integration/install/inject/inject_test.go
index 317decbc5..e58d67841 100644
--- a/test/integration/install/inject/inject_test.go
+++ b/test/integration/install/inject/inject_test.go
@@ -405,16 +405,6 @@ func TestInjectAutoPod(t *testing.T) {
 			"--inbound-ports-to-ignore", "4190,4191,1234,5678",
 			"--outbound-ports-to-ignore", "4567,4568",
 		},
-		Resources: v1.ResourceRequirements{
-			Limits: v1.ResourceList{
-				v1.ResourceName("cpu"):    resource.MustParse("100m"),
-				v1.ResourceName("memory"): resource.MustParse("20Mi"),
-			},
-			Requests: v1.ResourceList{
-				v1.ResourceName("cpu"):    resource.MustParse("100m"),
-				v1.ResourceName("memory"): resource.MustParse("20Mi"),
-			},
-		},
 		VolumeMounts: []v1.VolumeMount{
 			{
 				Name:      "linkerd-proxy-init-xtables-lock",