Congratulations!

[Valid Atom 1.0] This is a valid Atom 1.0 feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://feeds.feedburner.com/OctopusDeploy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <feed xmlns="http://www.w3.org/2005/Atom">
  3.  <title>Octopus blog</title>
  4.  <subtitle>Site description.</subtitle>
  5.  <link href="https://octopus.com/blog/feed.xml" rel="self" />
  6.  <link href="https://octopus.com" />
  7.  <id>https://octopus.com/blog/feed.xml</id>
  8.  <updated>2025-10-17T00:00:00.000Z</updated>
  9.  
  10.    <entry>
  11.      <title>Octopus partners with Arm to enable software delivery at scale</title>
  12.      <link href="https://octopus.com/blog/arm-partnership" />
  13.      <id>https://octopus.com/blog/arm-partnership</id>
  14.      <published>2025-10-17T00:00:00.000Z</published>
  15.      <updated>2025-10-17T00:00:00.000Z</updated>
  16.      <summary>Our partnership with Arm brings centralized, secure, and repeatable software delivery to Arm-powered systems.</summary>
  17.      <author>
  18.        <name>Madalina Iosif, Octopus Deploy</name>
  19.      </author>
  20.      <content type="html"><![CDATA[<p>Octopus sets the standard for Continuous Delivery (CD) at scale, helping thousands of customers orchestrate deployments across various environments, regardless of whether the target hosts are on-prem, hybrid, or multi-cloud. It supports complex application deployments of any flavour, from heritage monoliths to containerized microservices.</p>
  21. <p>With the rise of AI, technology is becoming increasingly prevalent in business and daily life, requiring computing platforms to keep pace with rapid innovation and the high volume of data being processed.</p>
  22. <p>This is where Arm steps in to provide the industry’s most efficient and highest-performing compute platform. More than 325 billion Arm-based devices have been shipped to date. Arm powers innovation acceleration across sectors such as high-tech, automotive, healthcare, telco, and much more, with a focus on cloud computing and IoT.</p>
  23. <p>We partnered with Arm to make software deployments on Arm-powered infrastructure secure, repeatable, and scalable.</p>
  24. <h2 id="why-is-a-robust-cd-solution-necessary-when-deploying-at-scale">Why is a robust CD solution necessary when deploying at scale?</h2>
  25. <p>Deploying one application to one host is an easy task. However, managing this at scale is challenging without an enterprise-grade solution.</p>
  26. <p>Risks such as human error or DIY scripts with minimal security can lead to major incidents, especially in highly regulated or critical industries like automotive or healthcare. Plus, they have a high impact on the speed of deployment, leaving companies lagging behind their peers in terms of innovation.</p>
  27. <p>A centralized CD solution with built-in security and the capability to deploy to thousands of targets using a repeatable process ensures efficiency, quick rollback in case of an incident, and proper governance and compliance. These are all imperative to ensuring technology is not only being built, but also used.</p>
  28. <h2 id="the-octopus-deploy-and-arm-partnership">The Octopus Deploy and Arm partnership</h2>
  29. <p>Octopus has been supporting deployments to Arm through the Tentacle agent since 2021. If you are curious about how this works, check out <a href="https://octopus.com/blog/tentacle-on-arm">our blog post on the topic</a>.</p>
  30. <p>With the evolution during the past years of both Octopus and Arm, the partnership brings benefits for our joint customers in two main areas:</p>
  31. <h3 id="continuous-delivery-at-scale-from-x86-to-arm-servers-to-reduce-infrastructure-cost">Continuous Delivery at scale from x86 to Arm servers, to reduce infrastructure cost</h3>
  32. <p>Octopus can target both x86 and Arm-based servers, supporting the same deployment process regardless of the target host. This way, organizations can migrate or extend workloads from x86 to Arm cloud instances (AWS Graviton, Azure Arm VMs, or Axion-based Google Cloud instances) to optimize cost while keeping a single pipeline. Deployments are fully automated, repeatable, and can scale to thousands of targets.</p>
  33. <p>For example, Octopus Deploy can deploy the same application to x86 EC2 instances and AWS Graviton. You reduce your compute costs and improve performance while the transition is seamless to your users.</p>
  34. <h3 id="compliant--secure-cd-for-kubernetes-edge-deployments-to-reduce-risk">Compliant &#x26; secure CD for Kubernetes edge deployments, to reduce risk</h3>
  35. <p>Running Octopus Kubernetes Agent natively on Arm-based Kubernetes clusters at the edge creates a secure connection and eliminates the need for an inbound connection. As for any other deployments with Octopus, you can use encrypted communication, role-based access control, and auditable approvals to ensure compliance.</p>
  36. <p>Moreover, using Runbooks in Octopus, you can automate maintenance tasks such as patching, certificate rotation, and secure updates across distributed Arm-powered Kubernetes clusters at the edge.</p>
  37. <p>A practical example is a retail chain that uses Octopus to securely roll out point-of-sale software updates to thousands of Arm-powered edge devices across stores, with a single deployment process and full auditability for compliance teams.</p>
  38. <h2 id="curious-to-learn-more">Curious to learn more?</h2>
  39. <p>If you’re an Arm customer and would like to test Octopus for your Continuous Delivery, you can <a href="https://octopus.com/start">sign up for a free Octopus trial</a> or <a href="https://octopus.com/lp/schedule-a-demo">request a demo</a>.</p>
  40. <p>You can also see how Octopus supports deployments to Arm devices by visiting us at GitHub Universe in San Francisco, October 28–29, 2025.</p>
  41. <p>Happy deployments!</p>]]></content>
  42.    </entry>
  43.    <entry>
  44.      <title>Using Platform Hub to increase Supply Chain Security</title>
  45.      <link href="https://octopus.com/blog/supply-chain-security-with-platform-hub" />
  46.      <id>https://octopus.com/blog/supply-chain-security-with-platform-hub</id>
  47.      <published>2025-10-15T00:00:00.000Z</published>
  48.      <updated>2025-10-15T00:00:00.000Z</updated>
  49.      <summary>Learn how Platform Hub can help supply chain security in Octopus Deploy.</summary>
  50.      <author>
  51.        <name>Bob Walker, Octopus Deploy</name>
  52.      </author>
  53.      <content type="html"><![CDATA[<p>Imagine this scenario: An application is built on Monday afternoon. That version is deployed to a test environment on Tuesday morning for verification. Testing is successful, and the production deployment occurs on Wednesday morning. But no one knew that early Tuesday morning a third-party package issued an update that closes a critical <a href="https://www.cve.org/">CVE</a>. Ideally, a process exists to inform application teams about the vulnerability before testing starts on Tuesday and include it as part of the production deployment on Wednesday.</p>
  54. <p>With Platform Hub, solving that problem at scale is much easier than ever before. In this post, I will walk you through the steps to increase supply chain security in Octopus Deploy using the <a href="https://octopus.com/docs/platform-hub/process-templates">Process Templates</a> and <a href="https://octopus.com/docs/platform-hub/Policies">Policies</a> included in Platform Hub.</p>
  55. <h2 id="nomenclature">Nomenclature</h2>
  56. <p>If you read <a href="https://octopus.com/blog/supply-chain-security-with-github-and-octopus-deploy">my previous post on supply chain security</a>, you are familiar with SBOMs, Provenance, and Attestations. For those who haven’t read that post, I’ve included the definitions below to make it easier to follow along.</p>
  57. <ul>
  58. <li><strong>SBOM</strong> - Software Bill of Materials - a list of all the third-party libraries (and their third-party libraries) used to create the build artifact (container, .zip files, jar files, etc.).</li>
  59. <li><strong>Provenance</strong> - the record of who created the software change, how it was modified and built, and what inputs went into it. It shows how the build artifact was built.</li>
  60. <li><strong>Attestation</strong> - A cryptographically verifiable statement that asserts something about an artifact, specifically its Provenance. It is similar to the notary seal on a document. Doesn’t show the whole process, but certifies its validity.</li>
  61. </ul>
  62. <p>SBOMs, Provenance, and Attestations are intertwined. Think of it like a cake.</p>
  63. <ul>
  64. <li>SBOMs are the ingredient list.</li>
  65. <li>Provenance is the recipe and kitchen log (who cooked it, when, and with which tools).</li>
  66. <li>Attestation is a signed certificate that proves the ingredient list, recipe, and cooking process are trustworthy.</li>
  67. </ul>
  68. <h2 id="responsibilities-differences-between-build-servers-and-octopus-deploy">Responsibilities differences between build servers and Octopus Deploy</h2>
  69. <p>This post focuses on using Process Templates and Policies within Octopus Deploy. But they will rely on artifacts created by the build server. For example, the build server will create the SBOM, while Octopus Deploy will scan the SBOM for package references with known vulnerabilities. Below is a table showing the differences in responsibility between build servers and Octopus Deploy.</p>
  70. <div class="table-wrap">
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81.  
  82.  
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92.  
  93.  
  94.  
  95.  
  96.  
  97.  
  98.  
  99. <table><thead><tr><th>Build Server                                                                      </th><th>Octopus Deploy                                                                    </th></tr></thead><tbody><tr><td>Generating SBOMs                                                                 </td><td>Attaching the SBOM to the release or forwarding it onto a third-party tool like <a href="https://ortelius.io/">Ortelius</a></td></tr><tr><td>Scanning source code referenced third-party packages for known vulnerabilities    </td><td>Scanning for known vulnerabilities within package versions listed in the SBOM     </td></tr><tr><td>Generating Attestations                                                           </td><td>Verifying attestations                                                           </td></tr><tr><td>Scanning recently built containers for known vulnerabilities                      </td><td>Scanning containers about to be deployed for known vulnerabilities                </td></tr><tr><td>Create build information file for Octopus Deploy to consume                       </td><td>Use the build information file to update third-party issue trackers like JIRA     </td></tr></tbody></table></div>
  100. <h2 id="tooling-used">Tooling Used</h2>
  101. <p>For my build server, I’m using GitHub Actions because it includes built-in Attestation generation. I’ll use <a href="https://trivy.dev/">Trivy</a> for vulnerability scanning and SBOM generation.</p>
  102. <ul>
  103. <li><strong>SBOM generation</strong> - GitHub Actions will use Trivy to create an SBOM in the <code>spdx-json</code> format via the <a href="https://github.com/aquasecurity/trivy-action">provided step by AquaSecurity/trivy-action</a> step.</li>
  104. <li><strong>SBOM scanning</strong> - Octopus Deploy will use Trivy to scan the SBOM for known fixed vulnerabilities.</li>
  105. <li><strong>Attestation generation</strong> - GitHub Actions will use the built in step <a href="https://github.com/actions/attest-build-provenance">actions/attest-build-provenance</a> to create the attestation.</li>
  106. <li><strong>Attestation verification</strong> - Octopus Deploy will use the command <code>gh attestation verify</code> provided by <a href="https://cli.github.com/">GitHub CLI</a> to verify the attestation.</li>
  107. <li><strong>Container scanning Post-build</strong> - GitHub Actions will use Trivy to scan the container for known fixed vulnerabilities after the container is built.</li>
  108. <li><strong>Container scanning Pre-deployment</strong> - Octopus Deploy will use Trivy to scan the container for known fixed vulnerabilities before a deployment.</li>
  109. </ul>
  110. <p>In Octopus Deploy, I opted for <a href="https://octopus.com/docs/projects/steps/execution-containers-for-workers">execution containers</a> instead of installing Trivy directly on my workers. There are two execution containers with Trivy installed you can use today:</p>
  111. <ul>
  112. <li><a href="https://hub.docker.com/r/octopuslabs/trivy-workertools">octopuslabs/trivy-workertools</a> includes Trivy, PowerShell, Python, and Octopus CLI.</li>
  113. <li><a href="https://hub.docker.com/r/octopuslabs/github-workertools">octopuslabs/github-workertools</a> includes the Git, GitHub CLI, Trivy, PowerShell, Python, and Octopus CLI.
  114.  
  115. The <code>DOCKERFILE</code> for both execution containers is in the <a href="https://github.com/OctopusDeployLabs/workertools">WorkerTools</a> GitHub repository.</li>
  116. </ul>
  117. <h2 id="process-templates">Process Templates</h2>
  118. <p>My Process Template will contain all the necessary logic to attach the SBOM to the release, scan the SBOM for known vulnerabilities, verify the attestations from GitHub, and scan containers for known third-party vulnerabilities.</p>
  119. <p>The <a href="https://octopus.com/docs/platform-hub/process-templates">Process Template documentation</a> provides a step-by-step guide to creating Process Templates. Instead of a step-by-step guide, I’ll walk you through the specific configuration for my Process Template.</p>
  120. <h3 id="process-template-configuration">Process Template Configuration</h3>
  121. <p>Following our <a href="https://octopus.com/docs/platform-hub/process-templates/best-practices">best practices</a>, I created a Process Template focused on supply chain security, <code>Deploy Process - Attach SBOM and Verify Build Artifacts</code>.</p>
  122. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/list-of-process-templates.png" alt="List of Process Templates on an instance with an arrow pointing to the Process Template to attach SBOM and build artifacts">.</p></figure>
  123. <p>The Process Template currently has two steps.</p>
  124. <ol>
  125. <li>The first step will extract the SBOM from a package, attach it as a deployment artifact, and then run Trivy to scan for known fixed vulnerabilities.</li>
  126. <li>The second step will loop through a list of packages and containers, run <code>gh attestation verify</code> on each item, and run Trivy on any containers for any known fixed vulnerabilities.</li>
  127. </ol>
  128. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/process-template-steps.png" alt="List of steps in the example Process Template"></p></figure>
  129. <h3 id="process-template-parameters">Process Template parameters</h3>
  130. <p>For my Process Template, there are four Process Template parameters.</p>
  131. <ol>
  132. <li><code>Template.SBOM.Artifact</code> - a zip file containing the SBOM for a specific application.</li>
  133. <li><code>Template.Git.AuthToken</code> - the GitHub PAT used for <code>gh attestation verify</code> to function properly.</li>
  134. <li><code>Template.Verify.Workerpool</code> - the worker pool to run all the steps.</li>
  135. <li><code>Template.Verify.ExecutionContainerFeed</code> - the container feed for the execution containers.</li>
  136. </ol>
  137. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/process-template-parameters.png" alt="List of parameters for the Process Template"></p></figure>
  138. <h3 id="processing-the-sbom">Processing the SBOM</h3>
  139. <p>The first step will use the package from the <code>Template.SBOM.Artifact</code>, <code>Template.Verify.Workerpool</code>, and <code>Template.Verify.ExecutionContainer</code> parameters. As a producer of this step, I opted to hardcode the execution container instead of passing it in as a parameter. The consumer shouldn’t need to worry about that configuration.</p>
  140. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/attach-sbom-and-run-trivy-step.png" alt="Step to attach the SBOM and run Trivy"></p></figure>
  141. <p>I opted for inline scripts because Octopus Deploy stores Process Templates in git. Referencing a third-party repo felt redundant. Below is the script I used.</p>
  142. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="powershell"><code><span class="line"><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#000000"> = </span><span style="color:#001080">$OctopusParameters</span><span style="color:#000000">[</span><span style="color:#A31515">"Octopus.Environment.Name"</span><span style="color:#000000">]</span></span>
  143. <span class="line"><span style="color:#001080">$extractedPath</span><span style="color:#000000"> = </span><span style="color:#001080">$OctopusParameters</span><span style="color:#000000">[</span><span style="color:#A31515">"Octopus.Action.Package[Template.SBOM.Artifact].ExtractedPath"</span><span style="color:#000000">]</span></span>
  144. <span class="line"></span>
  145. <span class="line"><span style="color:#795E26">Write-Host</span><span style="color:#A31515"> "The SBOM extracted file path is this value </span><span style="color:#001080">$extractedPath</span><span style="color:#A31515">"</span></span>
  146. <span class="line"><span style="color:#000000">      </span></span>
  147. <span class="line"><span style="color:#001080">$sbomFiles</span><span style="color:#000000"> = </span><span style="color:#795E26">Get-ChildItem</span><span style="color:#000000"> -Path </span><span style="color:#001080">$extractedPath</span><span style="color:#000000"> -Filter </span><span style="color:#A31515">"*.json"</span><span style="color:#000000"> -Recurse</span></span>
  148. <span class="line"></span>
  149. <span class="line"><span style="color:#AF00DB">foreach</span><span style="color:#000000"> (</span><span style="color:#001080">$sbom</span><span style="color:#AF00DB"> in</span><span style="color:#001080"> $sbomFiles</span><span style="color:#000000">)</span></span>
  150. <span class="line"><span style="color:#000000">{</span></span>
  151. <span class="line"><span style="color:#795E26">  Write-Host</span><span style="color:#A31515"> "Attaching </span><span style="color:#0000FF">$(</span><span style="color:#001080">$sbom</span><span style="color:#795E26">.FullName</span><span style="color:#0000FF">)</span><span style="color:#A31515"> as an artifacts"</span></span>
  152. <span class="line"><span style="color:#795E26">  New-OctopusArtifact</span><span style="color:#000000"> -Path </span><span style="color:#001080">$sbom</span><span style="color:#795E26">.FullName</span><span style="color:#000000"> -Name </span><span style="color:#A31515">"</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.SBOM.JSON"</span><span style="color:#000000">  </span></span>
  153. <span class="line"></span>
  154. <span class="line"><span style="color:#795E26">  Write-Host</span><span style="color:#A31515"> "Running trivy to scan the SBOM for any new vulnerabilities since the build was run"</span></span>
  155. <span class="line"><span style="color:#000000">  trivy sbom </span><span style="color:#001080">$sbom</span><span style="color:#795E26">.FullName</span><span style="color:#000000"> --severity </span><span style="color:#A31515">"MEDIUM,HIGH,CRITICAL"</span><span style="color:#000000"> --ignore-unfixed --quiet</span></span>
  156. <span class="line"></span>
  157. <span class="line"><span style="color:#AF00DB">  if</span><span style="color:#000000"> (</span><span style="color:#001080">$LASTEXITCODE</span><span style="color:#000000"> -eq </span><span style="color:#098658">0</span><span style="color:#000000">)</span></span>
  158. <span class="line"><span style="color:#000000">  {</span></span>
  159. <span class="line"><span style="color:#795E26">    Write-Highlight</span><span style="color:#A31515"> "Trivy successfully scanned the SBOM and no new vulnerabilities were found in the referenced third-party libraries."</span></span>
  160. <span class="line"><span style="color:#000000">  }</span></span>
  161. <span class="line"><span style="color:#AF00DB">  else</span></span>
  162. <span class="line"><span style="color:#000000">  {</span></span>
  163. <span class="line"><span style="color:#795E26">     Write-Error</span><span style="color:#A31515"> "Trivy found vulnerabilities that must be fixed before this application version can proceed. Please update the package references and rebuild the application."</span></span>
  164. <span class="line"><span style="color:#000000">  }</span></span>
  165. <span class="line"><span style="color:#000000">} </span></span></code></pre>
  166. <h3 id="verifying-attestations">Verifying attestations</h3>
  167. <p>The second step in the Process Template is much more complex. The initial configuration is similar to the first step.  It uses the <code>Template.Git.AuthToken</code>, <code>Template.Verify.Workerpool</code>, and <code>Template.Verify.ExecutionContainer</code> parameters. Just like with the first step, the execution container is hardcoded.</p>
  168. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/step-to-verify-attestations-and-run-trivy.png" alt="Step to verify attestations and run Trivy"></p></figure>
  169. <p>The complexity stems from decisions I made as the producer of the step to make it easier for consumers to use the template.</p>
  170. <ul>
  171. <li>Maximum re-use: the Process Template must support 1 to N packages/containers.  The consumer shouldn’t need to provide a list of those packages/containers either.  That information is stored in the variable manifest.</li>
  172. <li>The consumer shouldn’t have to hardcode information that is already included in the variable manifest.  For example the GitHub repo is stored in the build information variables.</li>
  173. </ul>
  174. <p>The <code>gh attestation verify</code> command presented two challenges.  That command needs a hash to lookup the attestation.</p>
  175. <ol>
  176. <li>For zip / JAR / WAR / NuGet files the hash is created from the file itself.  <code>gh attestation verify</code> requires the path to the folder.</li>
  177. <li>For containers the digest hash is used.  For private container repositories, verification must occur prior to invoking <code>gh attestation verify</code>.</li>
  178. </ol>
  179. <p>For my applications, I have the following benefits:</p>
  180. <ul>
  181. <li>All services and websites are hosted on Kubernetes clusters.</li>
  182. <li>All containers publicly accessible on <a href="https://hub.docker.com">hub.docker.com</a>.</li>
  183. <li>All deployments to database backends or other services occur via a <a href="https://octopus.com/docs/infrastructure/workers/kubernetes-worker">Kubernetes worker</a> running on the same cluster.</li>
  184. </ul>
  185. <p>That allowed me to perform a couple of shortcuts unique to my configuration.</p>
  186. <ul>
  187. <li>Octopus will download all containers/packages to the same Kubernetes cluster at the start of the deployment.</li>
  188. <li>Any package (including the SBOM package) needed for the deployment is stored in the <code>octopus/files</code> directory.</li>
  189. <li>Being public containers on DockerHub, I didn’t have to worry about authentication when pulling the digest for the container.</li>
  190. </ul>
  191. <p>Below is the PowerShell that works <em>for my configuration.</em> It will require some modifications if you wish to include it in your instance.  I’m providing it for example use only.</p>
  192. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="powershell"><code><span class="line"><span style="color:#001080">$gitHubToken</span><span style="color:#000000"> = </span><span style="color:#001080">$OctopusParameters</span><span style="color:#000000">[</span><span style="color:#A31515">"Template.Git.AuthToken"</span><span style="color:#000000">]</span></span>
  193. <span class="line"></span>
  194. <span class="line"><span style="color:#001080">$buildInformation</span><span style="color:#000000"> = </span><span style="color:#001080">$OctopusParameters</span><span style="color:#000000">[</span><span style="color:#A31515">"Octopus.Deployment.PackageBuildInformation"</span><span style="color:#000000">]</span></span>
  195. <span class="line"><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#000000"> = </span><span style="color:#001080">$OctopusParameters</span><span style="color:#000000">[</span><span style="color:#A31515">"Octopus.Environment.Name"</span><span style="color:#000000">]</span></span>
  196. <span class="line"></span>
  197. <span class="line"><span style="color:#795E26">Write-Host</span><span style="color:#A31515"> "Getting a list of packages and containers to attest to from the variable manifest"</span></span>
  198. <span class="line"><span style="color:#001080">$objectArray</span><span style="color:#000000"> = </span><span style="color:#0000FF">@</span><span style="color:#000000">()</span></span>
  199. <span class="line"><span style="color:#AF00DB">foreach</span><span style="color:#000000"> (</span><span style="color:#001080">$key</span><span style="color:#AF00DB"> in</span><span style="color:#001080"> $OctopusParameters</span><span style="color:#795E26">.Keys</span><span style="color:#000000">)</span></span>
  200. <span class="line"><span style="color:#000000">{</span></span>
  201. <span class="line"><span style="color:#AF00DB">  if</span><span style="color:#000000"> (</span><span style="color:#001080">$key</span><span style="color:#000000"> -like </span><span style="color:#A31515">"*.PackageId"</span><span style="color:#000000">)</span></span>
  202. <span class="line"><span style="color:#000000">  {</span></span>
  203. <span class="line"><span style="color:#795E26">    Write-Host</span><span style="color:#A31515"> "Found a package Id parameter: </span><span style="color:#001080">$key</span><span style="color:#A31515"> - checking to see if it already is in the packages to verify"</span></span>
  204. <span class="line"><span style="color:#000000">    </span></span>
  205. <span class="line"><span style="color:#001080">    $packageId</span><span style="color:#000000"> = </span><span style="color:#001080">$OctopusParameters</span><span style="color:#000000">[</span><span style="color:#001080">$key</span><span style="color:#000000">]</span></span>
  206. <span class="line"><span style="color:#795E26">    Write-Host</span><span style="color:#A31515"> "The package ID to check for is </span><span style="color:#001080">$packageId</span><span style="color:#A31515">"</span></span>
  207. <span class="line"></span>
  208. <span class="line"><span style="color:#001080">    $packageVersionKey</span><span style="color:#000000"> = </span><span style="color:#001080">$key</span><span style="color:#000000"> -replace </span><span style="color:#A31515">".PackageId"</span><span style="color:#000000">, </span><span style="color:#A31515">".PackageVersion"</span></span>
  209. <span class="line"><span style="color:#795E26">    Write-Host</span><span style="color:#A31515"> "The package version key is </span><span style="color:#001080">$packageVersionKey</span><span style="color:#A31515">"</span></span>
  210. <span class="line"><span style="color:#001080">    $packageVersion</span><span style="color:#000000"> = </span><span style="color:#001080">$OctopusParameters</span><span style="color:#000000">[</span><span style="color:#001080">$packageVersionKey</span><span style="color:#000000">]</span></span>
  211. <span class="line"><span style="color:#795E26">    Write-Host</span><span style="color:#A31515"> "The package version is </span><span style="color:#001080">$packageVersion</span><span style="color:#A31515">"</span></span>
  212. <span class="line"></span>
  213. <span class="line"><span style="color:#001080">    $packageVersionToVerify</span><span style="color:#000000"> = </span><span style="color:#A31515">"</span><span style="color:#0000FF">$(</span><span style="color:#001080">$packageId</span><span style="color:#0000FF">)</span><span style="color:#A31515">:</span><span style="color:#0000FF">$(</span><span style="color:#001080">$packageVersion</span><span style="color:#0000FF">)</span><span style="color:#A31515">"</span></span>
  214. <span class="line"></span>
  215. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> (</span><span style="color:#001080">$objectArray</span><span style="color:#000000"> -contains </span><span style="color:#A31515">"</span><span style="color:#001080">$packageVersionToVerify</span><span style="color:#A31515">"</span><span style="color:#000000">)</span></span>
  216. <span class="line"><span style="color:#000000">    {</span></span>
  217. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "</span><span style="color:#001080">$packageVersionToVerify</span><span style="color:#A31515"> already exists in the array"</span></span>
  218. <span class="line"><span style="color:#000000">    }</span></span>
  219. <span class="line"><span style="color:#AF00DB">    else</span></span>
  220. <span class="line"><span style="color:#000000">    {</span></span>
  221. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "</span><span style="color:#001080">$packageVersionToVerify</span><span style="color:#A31515"> does not exist - adding it"</span></span>
  222. <span class="line"><span style="color:#001080">      $objectArray</span><span style="color:#000000"> += </span><span style="color:#001080">$packageVersionToVerify</span></span>
  223. <span class="line"><span style="color:#000000">    }    </span></span>
  224. <span class="line"><span style="color:#000000">  }  </span></span>
  225. <span class="line"><span style="color:#000000">}</span></span>
  226. <span class="line"></span>
  227. <span class="line"><span style="color:#795E26">Write-Host</span><span style="color:#A31515"> "Getting the GitHub repository from the build information"</span></span>
  228. <span class="line"><span style="color:#001080">$buildInfoObject</span><span style="color:#000000"> = </span><span style="color:#795E26">ConvertFrom-Json</span><span style="color:#001080"> $buildInformation</span></span>
  229. <span class="line"><span style="color:#001080">$vcsRoot</span><span style="color:#000000"> = </span><span style="color:#0000FF">$null</span></span>
  230. <span class="line"></span>
  231. <span class="line"><span style="color:#795E26">Write-Host</span><span style="color:#A31515"> "Getting the repo name from build information"</span></span>
  232. <span class="line"><span style="color:#AF00DB">foreach</span><span style="color:#000000"> (</span><span style="color:#001080">$packageItem</span><span style="color:#AF00DB"> in</span><span style="color:#001080"> $objectArray</span><span style="color:#000000">)</span></span>
  233. <span class="line"><span style="color:#000000">{</span></span>
  234. <span class="line"><span style="color:#001080">  $artifactToCompare</span><span style="color:#000000"> = </span><span style="color:#001080">$packageItem</span><span style="color:#795E26">.Trim</span><span style="color:#000000">().Split(</span><span style="color:#A31515">':'</span><span style="color:#000000">)</span></span>
  235. <span class="line"><span style="color:#001080">  $packageVersion</span><span style="color:#000000"> = </span><span style="color:#001080">$artifactToCompare</span><span style="color:#000000">[</span><span style="color:#098658">1</span><span style="color:#000000">]</span></span>
  236. <span class="line"><span style="color:#000000">  </span></span>
  237. <span class="line"><span style="color:#795E26">  Write-Host</span><span style="color:#A31515"> "The version to look for is: </span><span style="color:#001080">$packageVersion</span><span style="color:#A31515">"</span></span>
  238. <span class="line"><span style="color:#000000">  </span></span>
  239. <span class="line"><span style="color:#AF00DB">  foreach</span><span style="color:#000000"> (</span><span style="color:#001080">$package</span><span style="color:#AF00DB"> in</span><span style="color:#001080"> $buildInfoObject</span><span style="color:#000000">)</span></span>
  240. <span class="line"><span style="color:#000000">  {</span></span>
  241. <span class="line"><span style="color:#795E26">    Write-Host</span><span style="color:#A31515"> "Comparing </span><span style="color:#0000FF">$(</span><span style="color:#001080">$package</span><span style="color:#795E26">.Version</span><span style="color:#0000FF">)</span><span style="color:#A31515"> with </span><span style="color:#0000FF">$(</span><span style="color:#001080">$packageVersion</span><span style="color:#0000FF">)</span><span style="color:#A31515">"</span></span>
  242. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> (</span><span style="color:#001080">$packageVersion</span><span style="color:#000000"> -eq </span><span style="color:#001080">$package</span><span style="color:#795E26">.Version</span><span style="color:#000000">)</span></span>
  243. <span class="line"><span style="color:#000000">    {</span></span>
  244. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "Versions match, getting the build URL"</span><span style="color:#000000">    </span></span>
  245. <span class="line"><span style="color:#001080">      $vcsRoot</span><span style="color:#000000"> = </span><span style="color:#001080">$package</span><span style="color:#795E26">.VcsRoot</span></span>
  246. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "The vcsRoot is </span><span style="color:#001080">$vcsRoot</span><span style="color:#A31515">"</span><span style="color:#000000">    </span></span>
  247. <span class="line"><span style="color:#000000">    }</span></span>
  248. <span class="line"><span style="color:#000000">  }</span></span>
  249. <span class="line"><span style="color:#000000">}</span></span>
  250. <span class="line"></span>
  251. <span class="line"><span style="color:#AF00DB">if</span><span style="color:#000000"> (</span><span style="color:#0000FF">$null</span><span style="color:#000000"> -eq </span><span style="color:#001080">$vcsRoot</span><span style="color:#000000">)</span></span>
  252. <span class="line"><span style="color:#000000">{</span></span>
  253. <span class="line"><span style="color:#795E26">  Write-Error</span><span style="color:#A31515"> "Unable to pull the build information URL from the Octopus Build information using supplied versions in </span><span style="color:#001080">$packageName</span><span style="color:#A31515">. Check that the build information has been supplied and try again."</span></span>
  254. <span class="line"><span style="color:#000000">}</span></span>
  255. <span class="line"></span>
  256. <span class="line"><span style="color:#001080">$githubLessUrl</span><span style="color:#000000"> = </span><span style="color:#001080">$vcsRoot</span><span style="color:#000000"> -Replace </span><span style="color:#A31515">"https://github.com/"</span><span style="color:#000000">, </span><span style="color:#A31515">""</span></span>
  257. <span class="line"></span>
  258. <span class="line"><span style="color:#001080">$env:GITHUB_TOKEN</span><span style="color:#000000"> = </span><span style="color:#001080">$gitHubToken</span></span>
  259. <span class="line"></span>
  260. <span class="line"><span style="color:#795E26">Write-Host</span><span style="color:#A31515"> "Verifying the attestation of all the found packages and containers."</span></span>
  261. <span class="line"><span style="color:#AF00DB">foreach</span><span style="color:#000000">(</span><span style="color:#001080">$packageItem</span><span style="color:#AF00DB"> in</span><span style="color:#001080"> $objectArray</span><span style="color:#000000">)</span></span>
  262. <span class="line"><span style="color:#000000">{    </span></span>
  263. <span class="line"><span style="color:#795E26">  Write-Host</span><span style="color:#A31515"> "Verifying </span><span style="color:#001080">$packageItem</span><span style="color:#A31515">"</span></span>
  264. <span class="line"><span style="color:#001080">  $artifactToCompare</span><span style="color:#000000"> = </span><span style="color:#001080">$packageItem</span><span style="color:#795E26">.Trim</span><span style="color:#000000">().Split(</span><span style="color:#A31515">':'</span><span style="color:#000000">)</span></span>
  265. <span class="line"><span style="color:#001080">  $packageName</span><span style="color:#000000"> = </span><span style="color:#001080">$artifactToCompare</span><span style="color:#000000">[</span><span style="color:#098658">0</span><span style="color:#000000">].Replace(</span><span style="color:#A31515">"/"</span><span style="color:#000000">, </span><span style="color:#A31515">""</span><span style="color:#000000">)</span></span>
  266. <span class="line"><span style="color:#000000">  </span></span>
  267. <span class="line"><span style="color:#AF00DB">  if</span><span style="color:#000000"> (</span><span style="color:#001080">$packageItem</span><span style="color:#795E26">.Contains</span><span style="color:#000000">(</span><span style="color:#A31515">"/"</span><span style="color:#000000">))</span></span>
  268. <span class="line"><span style="color:#000000">  {</span></span>
  269. <span class="line"><span style="color:#001080">      $imageToAttest</span><span style="color:#000000"> = </span><span style="color:#A31515">"oci://</span><span style="color:#001080">$packageItem</span><span style="color:#A31515">"</span></span>
  270. <span class="line"></span>
  271. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "Attesting to </span><span style="color:#001080">$imageToAttest</span><span style="color:#A31515"> in the repo </span><span style="color:#001080">$githubLessUrl</span><span style="color:#A31515">"</span></span>
  272. <span class="line"><span style="color:#001080">      $attestation</span><span style="color:#000000">=gh attestation verify </span><span style="color:#A31515">"</span><span style="color:#001080">$imageToAttest</span><span style="color:#A31515">"</span><span style="color:#000000"> --repo </span><span style="color:#001080">$githubLessUrl</span><span style="color:#000000"> --format json </span></span>
  273. <span class="line"></span>
  274. <span class="line"><span style="color:#AF00DB">      if</span><span style="color:#000000"> (</span><span style="color:#001080">$LASTEXITCODE</span><span style="color:#000000"> -ne </span><span style="color:#098658">0</span><span style="color:#000000">)</span></span>
  275. <span class="line"><span style="color:#000000">      {</span></span>
  276. <span class="line"><span style="color:#795E26">         Write-Error</span><span style="color:#A31515"> "The attestation for </span><span style="color:#001080">$packageItem</span><span style="color:#A31515"> could not be verified"</span></span>
  277. <span class="line"><span style="color:#000000">      }</span></span>
  278. <span class="line"></span>
  279. <span class="line"><span style="color:#795E26">      Write-Highlight</span><span style="color:#A31515"> "</span><span style="color:#001080">$packageItem</span><span style="color:#A31515"> successfully passed attestation verification"</span></span>
  280. <span class="line"><span style="color:#795E26">      Write-Verbose</span><span style="color:#001080"> $attestation</span></span>
  281. <span class="line"></span>
  282. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "Writing the attest output to </span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span></span>
  283. <span class="line"><span style="color:#795E26">      New-Item</span><span style="color:#000000"> -Name </span><span style="color:#A31515">"</span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span><span style="color:#000000"> -ItemType </span><span style="color:#A31515">"File"</span><span style="color:#000000"> -Value </span><span style="color:#001080">$attestation</span></span>
  284. <span class="line"><span style="color:#795E26">      New-OctopusArtifact</span><span style="color:#000000"> -Path </span><span style="color:#A31515">"</span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span><span style="color:#000000"> -Name  </span><span style="color:#A31515">"</span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span></span>
  285. <span class="line"></span>
  286. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "Running trivy to check the container for any known vulnerabilities that might have been discovered since the build."</span><span style="color:#000000">      </span></span>
  287. <span class="line"><span style="color:#000000">      trivy image --severity </span><span style="color:#A31515">"MEDIUM,HIGH,CRITICAL"</span><span style="color:#000000"> --ignore-unfixed --quiet </span><span style="color:#001080">$packageItem</span></span>
  288. <span class="line"><span style="color:#AF00DB">      if</span><span style="color:#000000"> (</span><span style="color:#001080">$LASTEXITCODE</span><span style="color:#000000"> -eq </span><span style="color:#098658">0</span><span style="color:#000000">)</span></span>
  289. <span class="line"><span style="color:#000000">      {</span></span>
  290. <span class="line"><span style="color:#795E26">          Write-Highlight</span><span style="color:#A31515"> "Trivy successfully scanned </span><span style="color:#001080">$packageItem</span><span style="color:#A31515"> and no new vulnerabilities have been found in the container or base containers since they were built."</span></span>
  291. <span class="line"><span style="color:#000000">      }</span></span>
  292. <span class="line"><span style="color:#AF00DB">      else</span></span>
  293. <span class="line"><span style="color:#000000">      {</span></span>
  294. <span class="line"><span style="color:#795E26">         Write-Error</span><span style="color:#A31515"> "Trivy found vulnerabilities in the build artifacts that must be fixed. You can no longer deploy this release. Please update the base container version, rebuild the application, and create a new release."</span></span>
  295. <span class="line"><span style="color:#000000">      }</span></span>
  296. <span class="line"><span style="color:#000000">  }</span></span>
  297. <span class="line"><span style="color:#AF00DB">  else</span></span>
  298. <span class="line"><span style="color:#000000">  {    </span></span>
  299. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> (</span><span style="color:#795E26">Test-Path</span><span style="color:#A31515"> "/octopus/Files/"</span><span style="color:#000000">)</span></span>
  300. <span class="line"><span style="color:#000000">    {</span></span>
  301. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "</span><span style="color:#001080">$artifactToCompare</span><span style="color:#A31515"> is a package from our local repo, getting the information from /octopus/Files/"</span></span>
  302. <span class="line"><span style="color:#001080">      $zipFiles</span><span style="color:#000000"> = </span><span style="color:#795E26">Get-ChildItem</span><span style="color:#000000"> -Path </span><span style="color:#A31515">"/octopus/Files/"</span><span style="color:#000000"> -Filter </span><span style="color:#A31515">"*</span><span style="color:#0000FF">$(</span><span style="color:#001080">$artifactToCompare</span><span style="color:#000000FF">[</span><span style="color:#098658">0</span><span style="color:#000000FF">]</span><span style="color:#0000FF">)</span><span style="color:#A31515">*</span><span style="color:#0000FF">$(</span><span style="color:#001080">$artifactToCompare</span><span style="color:#000000FF">[</span><span style="color:#098658">1</span><span style="color:#000000FF">]</span><span style="color:#0000FF">)</span><span style="color:#A31515">@*.zip"</span><span style="color:#000000"> -Recurse</span></span>
  303. <span class="line"><span style="color:#000000">    }</span></span>
  304. <span class="line"><span style="color:#AF00DB">    else</span></span>
  305. <span class="line"><span style="color:#000000">    {</span></span>
  306. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "</span><span style="color:#001080">$artifactToCompare</span><span style="color:#A31515"> is a package from our local repo, getting the information from /home/Octopus/Files"</span></span>
  307. <span class="line"><span style="color:#001080">      $zipFiles</span><span style="color:#000000"> = </span><span style="color:#795E26">Get-ChildItem</span><span style="color:#000000"> -Path </span><span style="color:#A31515">"/home/Octopus/Files"</span><span style="color:#000000"> -Filter </span><span style="color:#A31515">"*</span><span style="color:#0000FF">$(</span><span style="color:#001080">$artifactToCompare</span><span style="color:#000000FF">[</span><span style="color:#098658">0</span><span style="color:#000000FF">]</span><span style="color:#0000FF">)</span><span style="color:#A31515">*</span><span style="color:#0000FF">$(</span><span style="color:#001080">$artifactToCompare</span><span style="color:#000000FF">[</span><span style="color:#098658">1</span><span style="color:#000000FF">]</span><span style="color:#0000FF">)</span><span style="color:#A31515">@*.zip"</span><span style="color:#000000"> -Recurse</span></span>
  308. <span class="line"><span style="color:#000000">    }</span></span>
  309. <span class="line"></span>
  310. <span class="line"><span style="color:#001080">    $artifactVerified</span><span style="color:#000000"> = </span><span style="color:#0000FF">$false</span></span>
  311. <span class="line"><span style="color:#AF00DB">    foreach</span><span style="color:#000000"> (</span><span style="color:#001080">$file</span><span style="color:#AF00DB"> in</span><span style="color:#001080"> $zipFiles</span><span style="color:#000000">) </span></span>
  312. <span class="line"><span style="color:#000000">    {</span></span>
  313. <span class="line"><span style="color:#AF00DB">      if</span><span style="color:#000000"> (</span><span style="color:#795E26">test-path</span><span style="color:#A31515"> "</span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span><span style="color:#000000">)</span></span>
  314. <span class="line"><span style="color:#000000">      {</span></span>
  315. <span class="line"><span style="color:#AF00DB">        Continue</span></span>
  316. <span class="line"><span style="color:#000000">      }</span></span>
  317. <span class="line"><span style="color:#000000">      </span></span>
  318. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "Attesting to </span><span style="color:#0000FF">$(</span><span style="color:#001080">$file</span><span style="color:#795E26">.FullName</span><span style="color:#0000FF">)</span><span style="color:#A31515"> in the repo </span><span style="color:#001080">$githubLessUrl</span><span style="color:#A31515">"</span></span>
  319. <span class="line"><span style="color:#001080">      $attestation</span><span style="color:#000000">=gh attestation verify </span><span style="color:#A31515">"</span><span style="color:#0000FF">$(</span><span style="color:#001080">$file</span><span style="color:#795E26">.FullName</span><span style="color:#0000FF">)</span><span style="color:#A31515">"</span><span style="color:#000000"> --repo </span><span style="color:#001080">$githubLessUrl</span><span style="color:#000000"> --format json</span></span>
  320. <span class="line"></span>
  321. <span class="line"><span style="color:#AF00DB">      if</span><span style="color:#000000"> (</span><span style="color:#001080">$LASTEXITCODE</span><span style="color:#000000"> -ne </span><span style="color:#098658">0</span><span style="color:#000000">)</span></span>
  322. <span class="line"><span style="color:#000000">      {</span></span>
  323. <span class="line"><span style="color:#795E26">         Write-Error</span><span style="color:#A31515"> "The attestation for </span><span style="color:#001080">$packageItem</span><span style="color:#A31515"> could not be verified - this means no attestation was generated or the package has been tampered with since it was created - stopping the deployment to avoid a security incident."</span></span>
  324. <span class="line"><span style="color:#000000">      }</span></span>
  325. <span class="line"></span>
  326. <span class="line"><span style="color:#795E26">      Write-Highlight</span><span style="color:#A31515"> "</span><span style="color:#001080">$packageItem</span><span style="color:#A31515"> successfully passed attestation verification"</span></span>
  327. <span class="line"><span style="color:#795E26">      Write-Verbose</span><span style="color:#001080"> $attestation</span></span>
  328. <span class="line"><span style="color:#001080">      $artifactVerified</span><span style="color:#000000"> = </span><span style="color:#0000FF">$true</span></span>
  329. <span class="line"></span>
  330. <span class="line"><span style="color:#795E26">      Write-Host</span><span style="color:#A31515"> "Writing the attest output to </span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span></span>
  331. <span class="line"><span style="color:#795E26">      New-Item</span><span style="color:#000000"> -Name </span><span style="color:#A31515">"</span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span><span style="color:#000000"> -ItemType </span><span style="color:#A31515">"File"</span><span style="color:#000000"> -Value </span><span style="color:#001080">$attestation</span></span>
  332. <span class="line"><span style="color:#795E26">      New-OctopusArtifact</span><span style="color:#000000"> -Path </span><span style="color:#A31515">"</span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span><span style="color:#000000"> -Name  </span><span style="color:#A31515">"</span><span style="color:#001080">$packageName</span><span style="color:#A31515">.</span><span style="color:#001080">$OctopusEnvironmentName</span><span style="color:#A31515">.attestation.json"</span></span>
  333. <span class="line"><span style="color:#000000">    }</span></span>
  334. <span class="line"></span>
  335. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> (</span><span style="color:#001080">$artifactVerified</span><span style="color:#000000"> -eq </span><span style="color:#0000FF">$false</span><span style="color:#000000">)</span></span>
  336. <span class="line"><span style="color:#000000">    {</span></span>
  337. <span class="line"><span style="color:#795E26">      Write-Error</span><span style="color:#A31515"> "The attestation for </span><span style="color:#001080">$packageItem</span><span style="color:#A31515"> could not be verified - this means no attestation was generated or the package has been tampered with since it was created - stopping the deployment to avoid a security incident."</span></span>
  338. <span class="line"><span style="color:#000000">    }</span></span>
  339. <span class="line"><span style="color:#000000">  }  </span></span>
  340. <span class="line"><span style="color:#000000">}</span></span></code></pre>
  341. <h2 id="policies">Policies</h2>
  342. <p>Platform Hub’s Process templates are half of the solution. The other half are <a href="https://octopus.com/docs/platform-hub/Policies">Policies</a>. The Policies in Octopus Deploy can fail deployments if specific steps (or Process Templates) are not present. That same logic can be applied to runbook runs, but obviously, they’d require different steps.</p>
  343. <h3 id="how-policies-work">How Policies work</h3>
  344. <p>I want to explain Policies because they are a new concept in Octopus Deploy. Our policy engine uses Rego to query Octopus Deploy. The end goal of the policy engine is to provide “hooks” into various actions within Octopus Deploy. The first “hook” we provide is executing Deployments or Runbook Runs.</p>
  345. <p>When a Deployment or Runbook Run occurs, Octopus Deploy will send <code>input</code> information to the policy engine that looks similar to the example below.</p>
  346. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#0000FF">Input:</span></span>
  347. <span class="line"><span style="color:#000000">{</span></span>
  348. <span class="line"><span style="color:#A31515">  "Environment"</span><span style="color:#000000">: {</span></span>
  349. <span class="line"><span style="color:#A31515">    "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"Environments-42"</span><span style="color:#000000">,</span></span>
  350. <span class="line"><span style="color:#A31515">    "Name"</span><span style="color:#000000">: </span><span style="color:#A31515">"Test"</span><span style="color:#000000">,</span></span>
  351. <span class="line"><span style="color:#A31515">    "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"test"</span></span>
  352. <span class="line"><span style="color:#000000">  },</span></span>
  353. <span class="line"><span style="color:#A31515">  "Project"</span><span style="color:#000000">: {</span></span>
  354. <span class="line"><span style="color:#A31515">    "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"Projects-541"</span><span style="color:#000000">,</span></span>
  355. <span class="line"><span style="color:#A31515">    "Name"</span><span style="color:#000000">: </span><span style="color:#A31515">"Trident"</span><span style="color:#000000">,</span></span>
  356. <span class="line"><span style="color:#A31515">    "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"trident-aks"</span></span>
  357. <span class="line"><span style="color:#000000">  },</span></span>
  358. <span class="line"><span style="color:#A31515">  "ProjectGroup"</span><span style="color:#000000">: {</span></span>
  359. <span class="line"><span style="color:#A31515">    "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"ProjectGroups-483"</span><span style="color:#000000">,</span></span>
  360. <span class="line"><span style="color:#A31515">    "Name"</span><span style="color:#000000">: </span><span style="color:#A31515">"Kubernetes"</span><span style="color:#000000">,</span></span>
  361. <span class="line"><span style="color:#A31515">    "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"kubernetes"</span></span>
  362. <span class="line"><span style="color:#000000">  },</span></span>
  363. <span class="line"><span style="color:#A31515">  "Space"</span><span style="color:#000000">: {</span></span>
  364. <span class="line"><span style="color:#A31515">    "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"Spaces-1"</span><span style="color:#000000">,</span></span>
  365. <span class="line"><span style="color:#A31515">    "Name"</span><span style="color:#000000">: </span><span style="color:#A31515">"Default"</span><span style="color:#000000">,</span></span>
  366. <span class="line"><span style="color:#A31515">    "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"default"</span></span>
  367. <span class="line"><span style="color:#000000">  },</span></span>
  368. <span class="line"><span style="color:#A31515">  "SkippedSteps"</span><span style="color:#000000">: [],</span></span>
  369. <span class="line"><span style="color:#A31515">  "Steps"</span><span style="color:#000000">: [</span></span>
  370. <span class="line"><span style="color:#000000">    {</span></span>
  371. <span class="line"><span style="color:#A31515">      "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"azure-key-vault-retrieve-secrets"</span><span style="color:#000000">,</span></span>
  372. <span class="line"><span style="color:#A31515">      "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"azure-key-vault-retrieve-secrets"</span><span style="color:#000000">,</span></span>
  373. <span class="line"><span style="color:#A31515">      "ActionType"</span><span style="color:#000000">: </span><span style="color:#A31515">"Octopus.AzurePowerShell"</span><span style="color:#000000">,</span></span>
  374. <span class="line"><span style="color:#A31515">      "Enabled"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  375. <span class="line"><span style="color:#A31515">      "IsRequired"</span><span style="color:#000000">: </span><span style="color:#0000FF">false</span><span style="color:#000000">,</span></span>
  376. <span class="line"><span style="color:#A31515">      "Source"</span><span style="color:#000000">: {</span></span>
  377. <span class="line"><span style="color:#A31515">        "Type"</span><span style="color:#000000">: </span><span style="color:#A31515">"Step Template"</span><span style="color:#000000">,</span></span>
  378. <span class="line"><span style="color:#A31515">        "SlugOrId"</span><span style="color:#000000">: </span><span style="color:#A31515">"ActionTemplates-561"</span><span style="color:#000000">,</span></span>
  379. <span class="line"><span style="color:#A31515">        "Version"</span><span style="color:#000000">: </span><span style="color:#A31515">"2"</span></span>
  380. <span class="line"><span style="color:#000000">      }</span></span>
  381. <span class="line"><span style="color:#000000">    },</span></span>
  382. <span class="line"><span style="color:#000000">    {</span></span>
  383. <span class="line"><span style="color:#A31515">      "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"verify-build-artifacts-attach-sbom-to-release"</span><span style="color:#000000">,</span></span>
  384. <span class="line"><span style="color:#A31515">      "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"verify-build-artifacts-attach-sbom-to-release"</span><span style="color:#000000">,</span></span>
  385. <span class="line"><span style="color:#A31515">      "ActionType"</span><span style="color:#000000">: </span><span style="color:#A31515">"Octopus.Script"</span><span style="color:#000000">,</span></span>
  386. <span class="line"><span style="color:#A31515">      "Enabled"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  387. <span class="line"><span style="color:#A31515">      "IsRequired"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  388. <span class="line"><span style="color:#A31515">      "Source"</span><span style="color:#000000">: {</span></span>
  389. <span class="line"><span style="color:#A31515">        "Type"</span><span style="color:#000000">: </span><span style="color:#A31515">"Process Template"</span><span style="color:#000000">,</span></span>
  390. <span class="line"><span style="color:#A31515">        "SlugOrId"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-process-verify-build-artifacts"</span><span style="color:#000000">,</span></span>
  391. <span class="line"><span style="color:#A31515">        "Version"</span><span style="color:#000000">: </span><span style="color:#A31515">"2.5.0"</span></span>
  392. <span class="line"><span style="color:#000000">      }</span></span>
  393. <span class="line"><span style="color:#000000">    },</span></span>
  394. <span class="line"><span style="color:#000000">    {</span></span>
  395. <span class="line"><span style="color:#A31515">      "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"verify-build-artifacts-verify-docker-containers"</span><span style="color:#000000">,</span></span>
  396. <span class="line"><span style="color:#A31515">      "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"verify-build-artifacts-verify-docker-containers"</span><span style="color:#000000">,</span></span>
  397. <span class="line"><span style="color:#A31515">      "ActionType"</span><span style="color:#000000">: </span><span style="color:#A31515">"Octopus.Script"</span><span style="color:#000000">,</span></span>
  398. <span class="line"><span style="color:#A31515">      "Enabled"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  399. <span class="line"><span style="color:#A31515">      "IsRequired"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  400. <span class="line"><span style="color:#A31515">      "Source"</span><span style="color:#000000">: {</span></span>
  401. <span class="line"><span style="color:#A31515">        "Type"</span><span style="color:#000000">: </span><span style="color:#A31515">"Process Template"</span><span style="color:#000000">,</span></span>
  402. <span class="line"><span style="color:#A31515">        "SlugOrId"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-process-verify-build-artifacts"</span><span style="color:#000000">,</span></span>
  403. <span class="line"><span style="color:#A31515">        "Version"</span><span style="color:#000000">: </span><span style="color:#A31515">"2.5.0"</span></span>
  404. <span class="line"><span style="color:#000000">      }</span></span>
  405. <span class="line"><span style="color:#000000">    },</span></span>
  406. <span class="line"><span style="color:#000000">    {</span></span>
  407. <span class="line"><span style="color:#A31515">      "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-k8s-manifest-deploy-container"</span><span style="color:#000000">,</span></span>
  408. <span class="line"><span style="color:#A31515">      "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-k8s-manifest-deploy-container"</span><span style="color:#000000">,</span></span>
  409. <span class="line"><span style="color:#A31515">      "ActionType"</span><span style="color:#000000">: </span><span style="color:#A31515">"Octopus.KubernetesDeployRawYaml"</span><span style="color:#000000">,</span></span>
  410. <span class="line"><span style="color:#A31515">      "Enabled"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  411. <span class="line"><span style="color:#A31515">      "IsRequired"</span><span style="color:#000000">: </span><span style="color:#0000FF">false</span><span style="color:#000000">,</span></span>
  412. <span class="line"><span style="color:#A31515">      "Source"</span><span style="color:#000000">: {</span></span>
  413. <span class="line"><span style="color:#A31515">        "Type"</span><span style="color:#000000">: </span><span style="color:#A31515">"Process Template"</span><span style="color:#000000">,</span></span>
  414. <span class="line"><span style="color:#A31515">        "SlugOrId"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-process-deploy-to-kubernetes-via-manifest"</span><span style="color:#000000">,</span></span>
  415. <span class="line"><span style="color:#A31515">        "Version"</span><span style="color:#000000">: </span><span style="color:#A31515">"1.1.0"</span></span>
  416. <span class="line"><span style="color:#000000">      }</span></span>
  417. <span class="line"><span style="color:#000000">    },</span></span>
  418. <span class="line"><span style="color:#000000">    {</span></span>
  419. <span class="line"><span style="color:#A31515">      "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-k8s-manifest-verify-deployment"</span><span style="color:#000000">,</span></span>
  420. <span class="line"><span style="color:#A31515">      "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-k8s-manifest-verify-deployment"</span><span style="color:#000000">,</span></span>
  421. <span class="line"><span style="color:#A31515">      "ActionType"</span><span style="color:#000000">: </span><span style="color:#A31515">"Octopus.Script"</span><span style="color:#000000">,</span></span>
  422. <span class="line"><span style="color:#A31515">      "Enabled"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  423. <span class="line"><span style="color:#A31515">      "IsRequired"</span><span style="color:#000000">: </span><span style="color:#0000FF">false</span><span style="color:#000000">,</span></span>
  424. <span class="line"><span style="color:#A31515">      "Source"</span><span style="color:#000000">: {</span></span>
  425. <span class="line"><span style="color:#A31515">        "Type"</span><span style="color:#000000">: </span><span style="color:#A31515">"Process Template"</span><span style="color:#000000">,</span></span>
  426. <span class="line"><span style="color:#A31515">        "SlugOrId"</span><span style="color:#000000">: </span><span style="color:#A31515">"deploy-process-deploy-to-kubernetes-via-manifest"</span><span style="color:#000000">,</span></span>
  427. <span class="line"><span style="color:#A31515">        "Version"</span><span style="color:#000000">: </span><span style="color:#A31515">"1.1.0"</span></span>
  428. <span class="line"><span style="color:#000000">      }</span></span>
  429. <span class="line"><span style="color:#000000">    },</span></span>
  430. <span class="line"><span style="color:#000000">    {</span></span>
  431. <span class="line"><span style="color:#A31515">      "Id"</span><span style="color:#000000">: </span><span style="color:#A31515">"notify-team-of-deployment-status"</span><span style="color:#000000">,</span></span>
  432. <span class="line"><span style="color:#A31515">      "Slug"</span><span style="color:#000000">: </span><span style="color:#A31515">"notify-team-of-deployment-status"</span><span style="color:#000000">,</span></span>
  433. <span class="line"><span style="color:#A31515">      "ActionType"</span><span style="color:#000000">: </span><span style="color:#A31515">"Octopus.Script"</span><span style="color:#000000">,</span></span>
  434. <span class="line"><span style="color:#A31515">      "Enabled"</span><span style="color:#000000">: </span><span style="color:#0000FF">true</span><span style="color:#000000">,</span></span>
  435. <span class="line"><span style="color:#A31515">      "IsRequired"</span><span style="color:#000000">: </span><span style="color:#0000FF">false</span><span style="color:#000000">,</span></span>
  436. <span class="line"><span style="color:#A31515">      "Source"</span><span style="color:#000000">: {</span></span>
  437. <span class="line"><span style="color:#A31515">        "Type"</span><span style="color:#000000">: </span><span style="color:#A31515">"Step Template"</span><span style="color:#000000">,</span></span>
  438. <span class="line"><span style="color:#A31515">        "SlugOrId"</span><span style="color:#000000">: </span><span style="color:#A31515">"ActionTemplates-101"</span><span style="color:#000000">,</span></span>
  439. <span class="line"><span style="color:#A31515">        "Version"</span><span style="color:#000000">: </span><span style="color:#A31515">"15"</span></span>
  440. <span class="line"><span style="color:#000000">      }</span></span>
  441. <span class="line"><span style="color:#000000">    }</span></span>
  442. <span class="line"><span style="color:#000000">  ]</span></span>
  443. <span class="line"><span style="color:#000000">}</span></span></code></pre>
  444. <p>The policy engine will attempt to match that input to a policy. If it matches and the policy passes, then the Deployment (or Runbook Run) can proceed. The policy’s success (or failure) will be logged to the audit log.</p>
  445. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/policy-evaulation-logged-in-audit-log.png" alt="Policy evaluation that was logged to the audit log"></p></figure>
  446. <h3 id="requiring-a-process-template">Requiring a Process Template</h3>
  447. <p>Using the input from before, the policy to require that the Process Template is as follows:</p>
  448. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="ruby"><code><span class="line"><span style="color:#001080">name</span><span style="color:#000000"> = </span><span style="color:#A31515">"Verify Build Artifacts Required"</span></span>
  449. <span class="line"><span style="color:#001080">description</span><span style="color:#000000"> = </span><span style="color:#A31515">"Requires the Process Template Deploy Process - Verify Build Artifacts for all deployments"</span></span>
  450. <span class="line"><span style="color:#0070C1">ViolationReason</span><span style="color:#000000"> = </span><span style="color:#A31515">"Deploy Process - Verify Build Artifacts is required on all deployments to K8s"</span></span>
  451. <span class="line"></span>
  452. <span class="line"><span style="color:#000000">scope {</span></span>
  453. <span class="line"><span style="color:#001080">    rego</span><span style="color:#000000"> = </span><span style="color:#A31515">&#x3C;&#x3C;-EOT</span></span>
  454. <span class="line"><span style="color:#A31515">        # The package name MUST match the file name that is stored in git. The file name should be verify_build_artifacts_required.ocl</span></span>
  455. <span class="line"><span style="color:#A31515">        package verify_build_artifacts_required </span></span>
  456. <span class="line"></span>
  457. <span class="line"><span style="color:#A31515">            default evaluate := false</span></span>
  458. <span class="line"></span>
  459. <span class="line"><span style="color:#A31515">        # Only run this policy for deployments in the projects in the Project Group Kubernetes</span></span>
  460. <span class="line"><span style="color:#A31515">            evaluate := true if {</span></span>
  461. <span class="line"><span style="color:#A31515">            input.ProjectGroup.Slug == "kubernetes" </span></span>
  462. <span class="line"><span style="color:#A31515">            not input.Runbook           </span></span>
  463. <span class="line"><span style="color:#A31515">        }</span></span>
  464. <span class="line"><span style="color:#A31515">    EOT</span></span>
  465. <span class="line"><span style="color:#000000">}</span></span>
  466. <span class="line"></span>
  467. <span class="line"><span style="color:#000000">conditions {</span></span>
  468. <span class="line"><span style="color:#001080">    rego</span><span style="color:#000000"> = </span><span style="color:#A31515">&#x3C;&#x3C;-EOT</span></span>
  469. <span class="line"><span style="color:#A31515">        # The package name MUST match the file name that is stored in git. The file name should be verify_build_artifacts_required.ocl </span></span>
  470. <span class="line"><span style="color:#A31515">        package verify_build_artifacts_required</span></span>
  471. <span class="line"></span>
  472. <span class="line"><span style="color:#A31515">        # Assume all evaluations will fail</span></span>
  473. <span class="line"><span style="color:#A31515">        default result := {"allowed": false}</span></span>
  474. <span class="line"></span>
  475. <span class="line"><span style="color:#A31515">        result := {"allowed": true} if {</span></span>
  476. <span class="line"><span style="color:#A31515">            some step in input.Steps</span></span>
  477. <span class="line"><span style="color:#A31515">            # Match using the source.SlugOrId</span></span>
  478. <span class="line"><span style="color:#A31515">            step.Source.SlugOrId == "deploy-process-verify-build-artifacts" </span></span>
  479. <span class="line"><span style="color:#A31515">            # Ensure the step is enabled - if it is not then fail it.</span></span>
  480. <span class="line"><span style="color:#A31515">            step.Enabled == true           </span></span>
  481. <span class="line"><span style="color:#A31515">            not verify_build_artifacts_skipped</span></span>
  482. <span class="line"><span style="color:#A31515">        }</span></span>
  483. <span class="line"></span>
  484. <span class="line"><span style="color:#A31515">        result := {"allowed": false, "Reason": "The Process Template Deploy Process - Verify Build Artifacts is required and cannot be skipped for a deployment to K8s to any environment."} if {</span></span>
  485. <span class="line"><span style="color:#A31515">            verify_build_artifacts_skipped</span></span>
  486. <span class="line"><span style="color:#A31515">        }</span></span>
  487. <span class="line"></span>
  488. <span class="line"><span style="color:#A31515">        verify_build_artifacts_skipped if {</span></span>
  489. <span class="line"><span style="color:#A31515">            # Fail the evaluation if the user elects to skip the Process Template when creating the deployment</span></span>
  490. <span class="line"><span style="color:#A31515">            some step in input.Steps</span></span>
  491. <span class="line"><span style="color:#A31515">            step.Id in input.SkippedSteps</span></span>
  492. <span class="line"><span style="color:#A31515">            step.Source.SlugOrId == "deploy-process-verify-build-artifacts"</span></span>
  493. <span class="line"><span style="color:#A31515">        }</span></span>
  494. <span class="line"><span style="color:#A31515">    EOT</span></span>
  495. <span class="line"></span>
  496. <span class="line"><span style="color:#000000">}</span></span></code></pre>
  497. <h2 id="bringing-everything-together">Bringing everything together</h2>
  498. <p>Adding the Process Template to the deployment process is the same as adding any other step to a deployment process.</p>
  499. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/adding-process-template-to-deploy-process.png" alt="Adding a Process Template to the deploy process with parameters being populated"></p></figure>
  500. <p>A deployment to the test environment will then show both Policies and Process Templates in action.  Because the Process Template is part of the deployment the Policy check passes and the deployment was successful.</p>
  501. <figure><p><img src="/blog/img/supply-chain-security-with-platform-hub/deployments-with-policies-and-process-templates.png" alt="Deployment with Policies and Process Templates"></p></figure>
  502. <p>The eagle-eyed among you will likely notice that my scripts fail the deployment when a Trivy scan fails. What if you need to deploy a change to fix a show-stopping bug?  I solve that by using <a href="https://octopus.com/docs/releases/guided-failures">guided failures</a>. When Trivy or an attestation verification fails, the deployment pauses and waits for a human to intervene. They decide to ignore the failure or cancel the deployment.  Regardless of the decision, that information is logged in the audit log.</p>
  503. <h2 id="conclusion">Conclusion</h2>
  504. <p>I’m not naive enough to believe what is described in this post will 100% secure the software supply chain. Leveraging Process Templates and Policies in Platform Hub makes it much easier to secure the software supply chain in Octopus Deploy.  Using Pull Requests in GitHub, Process Templates, Policies, ITSM, and RBAC in Octopus Deploy, it’s much easier to get to <a href="https://slsa.dev/spec/v0.1/levels">SLSA Level 4</a> than ever before. The Process Template includes the necessary steps to guarantee the build artifact about to be deployed hasn’t been tampered with and no new fixed vulnerabilities have been reported. Policies guarantee that each deployment to production includes the appropriate Process Template.</p>
  505. <p>Happy deployments!</p>]]></content>
  506.    </entry>
  507.    <entry>
  508.      <title>Deprecating support for TLS 1.0 and 1.1</title>
  509.      <link href="https://octopus.com/blog/deprecating-tls-1-0-and-1-1" />
  510.      <id>https://octopus.com/blog/deprecating-tls-1-0-and-1-1</id>
  511.      <published>2025-10-14T00:00:00.000Z</published>
  512.      <updated>2025-10-14T00:00:00.000Z</updated>
  513.      <summary>Octopus Cloud will discontinue support for connecting to targets and workers that require TLS 1.0 or 1.1.</summary>
  514.      <author>
  515.        <name>Rhys Parry, Octopus Deploy</name>
  516.      </author>
  517.      <content type="html"><![CDATA[<p>Transport Layer Security (TLS) 1.0 and 1.1 are legacy cryptographic protocols that first appeared in 1999 and 2006, respectively. These protocols contain known security vulnerabilities, and more secure versions have superseded them, particularly TLS 1.2 (2008) and TLS 1.3 (2018).</p>
  518. <p>Microsoft has progressively phased out support for TLS 1.0 and 1.1 across Windows Server operating systems:</p>
  519. <ul>
  520. <li><strong>Windows Server 2019 and later</strong>: Disables TLS 1.0 and 1.1 by default</li>
  521. <li><strong>Windows Server 2016</strong>: Allows you to disable TLS 1.0 and 1.1 via registry settings</li>
  522. <li><strong>Windows Server 2012 R2</strong>: Requires updates to support TLS 1.2 as the default protocol</li>
  523. <li><strong>Windows Server 2012</strong>: Requires <a href="https://support.microsoft.com/en-au/topic/update-to-enable-tls-1-1-and-tls-1-2-as-default-secure-protocols-in-winhttp-in-windows-c4bd73d2-31d7-761e-0178-11268bb10392">specific updates</a> to support TLS 1.2</li>
  524. </ul>
  525. <p>We’re following <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/sslstream-best-practices">Microsoft’s recommendation</a> by deferring TLS version selection to the Operating System. This approach prevents systems that don’t enable legacy protocols by default from using them.</p>
  526. <h3 id="impact-on-octopus-cloud-customers">Impact on Octopus Cloud customers</h3>
  527. <p>We’re removing support for these legacy protocols on Octopus Cloud to enhance security. This change will affect Tentacles on older operating systems that don’t support TLS 1.2+.</p>
  528. <p><strong>Tentacles affected by this change include those running on:</strong></p>
  529. <ul>
  530. <li>Windows Server 2012 and 2012 R2 (without <a href="https://support.microsoft.com/en-au/topic/update-to-enable-tls-1-1-and-tls-1-2-as-default-secure-protocols-in-winhttp-in-windows-c4bd73d2-31d7-761e-0178-11268bb10392">TLS 1.2 patches</a>)</li>
  531. </ul>
  532. <p>These Tentacles will need TLS 1.2+ support to maintain secure connections and continue deployments.</p>
  533. <div class="info"><p>This will also affect newer Operating Systems if you have explicitly disabled TLS 1.2 or 1.3. If affected, you’ll need to re-enable TLS 1.2 or 1.3.</p></div>
  534. <h3 id="impact-on-self-hosted-customers-using-linux-docker">Impact on self-hosted customers using Linux Docker</h3>
  535. <p>Our upgrade to Debian 12 in January 2026 will also affect customers using our official Linux Docker image. Like Octopus Cloud, your Tentacles will need TLS 1.2+ support to connect to your Octopus Server.</p>
  536. <h3 id="impact-on-self-hosted-customers-using-windows">Impact on self-hosted customers using Windows</h3>
  537. <p>Self-hosted customers running Octopus Server on Windows won’t see direct changes to their server. However, your Operating System configuration determines your TLS version availability, so you may already use TLS 1.2+ only.</p>
  538. <p>Most Windows Server 2016+ installations already use TLS 1.2+ by default, so you’re likely already prepared.</p>
  539. <h3 id="customer-support-and-monitoring">Customer support and monitoring</h3>
  540. <p><strong>For Octopus Cloud customers:</strong> We’re monitoring Octopus Cloud for usages of TLS 1.0 and 1.1, and will reach out to affected customers.</p>
  541. <p><strong>For self-hosted customers:</strong> To ensure you’re prepared, please review your environment for TLS 1.0/1.1 dependencies before the January 2026 timeline. This step will help you identify and address any compatibility requirements early.</p>
  542. <p>If you believe your organization may be affected, or if you have questions about TLS protocol support, please don’t hesitate to contact our <a href="https://octopus.com/support">support team</a> for assistance.</p>
  543. <h3 id="what-you-can-do">What you can do</h3>
  544. <p>To keep your systems connected, you have several options:</p>
  545. <p><strong>Recommended approach for all customers:</strong></p>
  546. <ul>
  547. <li><strong>Upgrade your operating system</strong> to a supported version (Windows Server 2016 or later, recent Linux distributions)</li>
  548. <li><strong>Update your Tentacle</strong> to the latest version, which includes enhanced TLS support</li>
  549. <li><strong>Review external integrations</strong> to ensure they support TLS 1.2 or higher</li>
  550. </ul>
  551. <p><strong>Alternative options for specific systems:</strong></p>
  552. <ul>
  553. <li><strong>Windows Server 2012</strong>: Apply the <a href="https://support.microsoft.com/en-au/topic/update-to-enable-tls-1-1-and-tls-1-2-as-default-secure-protocols-in-winhttp-in-windows-c4bd73d2-31d7-761e-0178-11268bb10392">Microsoft update to enable TLS 1.1 and TLS 1.2 as default protocols</a></li>
  554. <li><strong>Windows Server 2012 R2</strong>: Install all Windows updates and enable TLS 1.2 in the registry</li>
  555. </ul>
  556. <p><strong>How to check your current setup:</strong></p>
  557. <ul>
  558. <li><strong>External service support</strong>: Most modern services already support TLS 1.2+, but you can test connections or contact service providers to confirm</li>
  559. <li><strong>Operating System TLS</strong>: Windows Server 2016+ and modern Linux distributions enable TLS 1.2+ by default. Older operating systems, such as Windows Server 2012/2012 R2, may require security updates to enable TLS 1.2. Since Tentacle uses your OS’s TLS capabilities, ensuring your OS supports TLS 1.2+ is the key step for compatibility</li>
  560. </ul>
  561. <h3 id="deprecation-timeline">Deprecation timeline</h3>
  562. <div class="table-wrap">
  563.  
  564.  
  565.  
  566.  
  567.  
  568.  
  569.  
  570.  
  571.  
  572.  
  573.  
  574.  
  575.  
  576.  
  577.  
  578.  
  579.  
  580.  
  581.  
  582.  
  583.  
  584.  
  585.  
  586.  
  587.  
  588.  
  589.  
  590.  
  591.  
  592. <table><thead><tr><th>Period</th><th>Octopus Cloud</th><th>Self-Hosted Docker</th></tr></thead><tbody><tr><td>October - November 2025</td><td>We’ll monitor for usages of TLS 1.0/1.1</td><td>Customers should assess their environments</td></tr><tr><td>Mid-November 2025</td><td>We’ll disable TLS 1.0/1.1 on Octopus Cloud (with accommodations for affected customers)</td><td>No immediate change</td></tr><tr><td>December 2025</td><td>We’ll continue to track and help affected customers</td><td>Customers should continue preparation</td></tr><tr><td>January 2026</td><td>Octopus Cloud will use TLS 1.2+ only</td><td>We’ll upgrade the official Docker image to Debian 12, supporting TLS 1.2+ only</td></tr></tbody></table></div>
  593. <div class="info"><p><strong>Note:</strong> We may adjust this timeline based on customer impact analysis and feedback. We’re committed to providing adequate notice and support throughout the transition process.</p></div>
  594. <h2 id="summary">Summary</h2>
  595. <p>Removing support for these outdated protocols brings us in line with modern security standards. Most customers won’t be affected, but if you’re running older systems, now’s the time to plan your upgrade.</p>
  596. <p><strong>Key takeaways:</strong></p>
  597. <ul>
  598. <li><strong>Octopus Cloud</strong> customers will see us disable TLS 1.0/1.1 from mid-November 2025, with complete removal by January 2026</li>
  599. <li><strong>Self-hosted Docker</strong> customers will experience changes when we upgrade the official image to Debian 12 in January 2026</li>
  600. <li><strong>Self-hosted Windows</strong> customers will continue to work as before</li>
  601. </ul>
  602. <p>The best fix is upgrading to modern operating systems with built-in TLS 1.2+ support. If you need more time, apply security patches and enable TLS 1.2 as a temporary measure.</p>
  603. <p>Our <a href="https://octopus.com/support">support team</a> is here to help throughout this transition. If you have concerns about your environment or need help with remediation, please reach out early so we can work together to ensure a smooth migration.</p>
  604. <p>Happy deployments!</p>]]></content>
  605.    </entry>
  606.    <entry>
  607.      <title>Leveling up your deployment pipelines</title>
  608.      <link href="https://octopus.com/blog/leveling-up-deployment-pipelines" />
  609.      <id>https://octopus.com/blog/leveling-up-deployment-pipelines</id>
  610.      <published>2025-10-14T00:00:00.000Z</published>
  611.      <updated>2025-10-14T00:00:00.000Z</updated>
  612.      <summary>Platform teams follow a common pattern when building deployment pipelines. Learn the three stages of evolution and how to level up your CI/CD infrastructure.</summary>
  613.      <author>
  614.        <name>Steve Fenton, Octopus Deploy</name>
  615.      </author>
  616.      <content type="html"><![CDATA[<p>Our <a href="https://octopus.com/publications/platform-engineering-pulse">Platform Engineering Pulse report</a> gathered a list of features organizations commonly add to their internal developer platforms. We grouped sample platforms into common feature collections and found that platform teams follow similar patterns when implementing deployment pipelines.</p>
  617. <p>They begin by creating an end-to-end deployment pipeline that automates the flow of changes to production and establishes monitoring. Next, they add security concerns into the pipeline to scan for vulnerabilities and manage secrets. This eventually leads to a DevOps pipeline, which adds documentation and additional automation.</p>
  618. <p>You can use this pragmatic evolution of CI/CD pipelines as a benchmark and a source of inspiration for platform teams. It’s like a natural maturity model that has been discovered through practice, rather than one that has been designed upfront.</p>
  619. <h2 id="stage-1-deployment-pipeline">Stage 1: Deployment pipeline</h2>
  620. <p>The initial concern for platform teams is to establish a complete deployment pipeline, allowing changes to flow to production with high levels of automation. Although the goal is a complete yet minimal CI/CD process, it’s reassuring to see that both test automation and monitoring are frequently present at this early stage.</p>
  621. <p>Early deployment pipelines involve integrating several tools, but these tools are designed to work together, making the integration quick and easy. Build servers, artifact management tools, deployment tools, and monitoring tools have low barriers to entry and lightweight touchpoints, so they feel very unified, even when provided by a mix of different tool vendors or open-source options.</p>
  622. <p>In fact, when teams attempt to simplify the toolchain by using a single tool for everything, they often end up with more complexity, as tool separation enforces a good pipeline architecture.</p>
  623. <figure><p><img src="/blog/img/leveling-up-deployment-pipelines/platform-pipelines-deployment.png" alt="Deployment pipeline with stages for build, test, artifact management, deployment, and monitoring"></p></figure>
  624. <h3 id="builds">Builds</h3>
  625. <p>Building an application from source code involves compiling code, linking libraries, and bundling resources so you can run the software on a target platform. While this may not be the most challenging task for software teams, build processes can become complex and require troubleshooting that takes time away from feature development.</p>
  626. <p>When a team rarely changes its build process, it tends to be less familiar with the tools it uses. It may not be aware of features that could improve build performance, such as dependency caching or parallelization.</p>
  627. <h3 id="test-automation">Test automation</h3>
  628. <p>To shorten feedback loops, it is essential to undertake all types of testing continuously. This means you need fast and reliable test automation suites. You should cover functional, security, and performance tests within your deployment pipeline.</p>
  629. <p>You must also consider how to manage test data as part of your test automation strategy. The ability to set up data in a known state will help you make tests less flaky. Test automation enables developers to identify issues early, reduces team burnout, and enhances software stability.</p>
  630. <h3 id="artifact-management">Artifact management</h3>
  631. <p>Your Continuous Integration (CI) process creates a validated build artifact that should be the canonical representation of the software version. An artifact repository ensures only one artifact exists for each version and allows tools to retrieve that version when needed.</p>
  632. <h3 id="deployment-automation">Deployment automation</h3>
  633. <p>Even at the easier end of the complexity scale, deployments are risky and often stressful. Copying the artifact, updating the configuration, migrating the database, and performing related tasks present numerous opportunities for mistakes or unexpected outcomes.</p>
  634. <p>When teams have more complex deployments or need to deploy at scale, the risk, impact, and stress increase.</p>
  635. <h3 id="monitoring-and-observability">Monitoring and observability</h3>
  636. <p>While test automation covers a suite of expected scenarios, monitoring and observability help you expand your view to the entirety of your real-world software use. Monitoring implementations tend to start with resource usage metrics, but mature into measuring software from the customer and business perspective.</p>
  637. <p>The ability to view information-rich logs can help you understand how faults occur, allowing you to design a more robust system.</p>
  638. <h2 id="stage-2-secure-pipeline">Stage 2: Secure pipeline</h2>
  639. <p>The natural next step for platform teams is to integrate security directly into the deployment pipeline. At this stage, teams add security scanning to automatically check for code weaknesses and vulnerable dependencies, alongside secrets management to consolidate how credentials and API keys are stored and rotated.</p>
  640. <p>This shift is significant because security measures are now addressed earlier in the pipeline, reducing the risk of incidents in production. Rather than treating security as a separate concern that happens after development, it becomes part of the continuous feedback loop.</p>
  641. <p>Security integration at this stage typically involves adding new tools to the existing pipeline, with well-defined touchpoints and clear interfaces. Security scanners and secrets management tools are designed to integrate with CI/CD systems, making the additions feel like natural extensions of the deployment pipeline rather than disruptive changes.</p>
  642. <figure><p><img src="/blog/img/leveling-up-deployment-pipelines/platform-pipelines-secure.png" alt="Two stages have been added to the pipeline for security scanning and secrets management"></p></figure>
  643. <h3 id="security-scanning">Security scanning</h3>
  644. <p>While everyone should take responsibility for software security, having automated scanning available within a deployment pipeline can help ensure security isn’t forgotten or delayed. Automated scanning can provide developers with rapid feedback.</p>
  645. <p>You can supplement automated scanning with security reviews and close collaboration with information security teams.</p>
  646. <h3 id="secrets-management">Secrets management</h3>
  647. <p>Most software systems must connect securely to data stores, APIs, and other services. The ability to store secrets in a single location prevents the ripple effect when a secret is rotated. Instead of updating many tools with a new API key, you can manage the change centrally with a secret store.</p>
  648. <p>When you deploy an application, you usually have to apply the correct secrets based on the environment or other characteristics of the deployment target.</p>
  649. <h2 id="stage-3-devops-pipeline">Stage 3: DevOps pipeline</h2>
  650. <p>The DevOps pipeline represents a shift from building deployment infrastructure to accelerating developer productivity. At this stage, platform teams add documentation capabilities, infrastructure automation, and one-click setup for new projects. These features focus on removing friction from the developer experience.</p>
  651. <p>The impact of this stage is felt most strongly at the start of new projects and when onboarding new team members. Instead of spending days or weeks on boilerplate setup, teams get a walking skeleton that fast-forwards them directly to writing their first unit test.</p>
  652. <p>While the earlier stages focused on moving code through the pipeline efficiently and securely, this stage is about making the pipeline itself easy to replicate and understand. The automation added here helps teams maintain consistency across projects while giving developers the freedom to focus on features rather than configuration.</p>
  653. <figure><p><img src="/blog/img/leveling-up-deployment-pipelines/platform-pipelines-devops.png" alt="Three more stages have been added for documentation, one-click setup, and infrastructure automation"></p></figure>
  654. <h3 id="documentation">Documentation</h3>
  655. <p>To provide documentation as a service to teams, you may either supply a platform for storing and finding documentation or use automation to extract documentation from APIs, creating a service directory for your organization.</p>
  656. <p>For documentation to be successful, it must be clear, well-organized, up-to-date, and easily accessible.</p>
  657. <h3 id="one-click-setup-for-new-projects">One-click setup for new projects</h3>
  658. <p>When setting up a new project, several boilerplate tasks are required to configure a source code repository, establish a project template, configure deployment pipelines, and set up associated tools. Teams often have established standards, but manual setup means projects unintentionally drift from the target setup.</p>
  659. <p>One-click automation helps teams set up a walking skeleton with sample test projects, builds, and deployment automation. This ensures a consistent baseline and speeds up the time to start writing meaningful code.</p>
  660. <h3 id="infrastructure-automation">Infrastructure automation</h3>
  661. <p>Traditional ClickOps infrastructure is hand-crafted and often drifts from the intended configuration over time. Environments may be set up differently, which means problems surface only in one environment and not another. Equally, two servers in the same environment with the same intended purpose may be configured differently, making troubleshooting problems more challenging.</p>
  662. <p>Infrastructure automation solves these problems, making it easier to create new environments, spin up and tear down ephemeral (temporary) environments, and recover from major faults.</p>
  663. <h2 id="evolving-your-platforms-pipelines">Evolving your platform’s pipelines</h2>
  664. <p>Whether you choose to introduce features according to this pattern or decide to approach things differently, it is advisable to take an evolutionary approach. Delivering a working solution that covers the flow of changes from commit to production brings early value. The evolution of the platform enhances the flow and incorporates broader concerns.</p>
  665. <p>Your organization may have additional compliance or regulatory requirements that could become part of a “compliant pipeline”, or you may have a heavyweight change approval process you could streamline with an “approved pipeline”.</p>
  666. <p>Regardless of the requirements you choose to bring under the platform’s capabilities, you’ll be more successful if you deliver a working pipeline and evolve it to add additional features.</p>
  667. <p>Happy deployments!</p>]]></content>
  668.    </entry>
  669.    <entry>
  670.      <title>Announcing Process Templates Public Preview</title>
  671.      <link href="https://octopus.com/blog/process-templates" />
  672.      <id>https://octopus.com/blog/process-templates</id>
  673.      <published>2025-10-10T00:00:00.000Z</published>
  674.      <updated>2025-10-10T00:00:00.000Z</updated>
  675.      <summary>A blog post outlining our launch of process templates in public preview.</summary>
  676.      <author>
  677.        <name>Venkatesh Vasudevan, Octopus Deploy</name>
  678.      </author>
  679.      <content type="html"><![CDATA[<p>Today, we’re excited to introduce Process Templates Public Preview, a powerful new feature designed to help teams harmonize their pipelines and reduce duplication across projects and teams. Process Templates enable you to easily create reusable, standardized deployment process building blocks.</p>
  680. <h2 id="what-are-process-templates">What are Process Templates?</h2>
  681. <p>Process Templates are reusable sets of deployment steps that can be shared across multiple Spaces in Octopus Deploy. Instead of copying and pasting deployment processes across teams and applications, which often leads to configuration drift, unnecessary duplication, and operational debt, you create a single source of truth that any project can consume. By abstracting your best practices for deployments into Process Templates, you make it easy for teams to follow standards and accelerate delivery.</p>
  682. <h2 id="why-use-process-templates">Why use Process Templates?</h2>
  683. <ul>
  684. <li><strong>Reduce duplication:</strong> Update Process Templates from one place, Platform Hub, and have the changes reflected everywhere.</li>
  685. <li><strong>Consistency at scale:</strong> Keep your pipelines consistent no matter how many teams or projects use them.</li>
  686. <li><strong>Safe and secure delivery:</strong> Centralizing important deployment patterns in Process Templates ensures that your developers automatically deploy safely and securely whenever they use a Process Template.</li>
  687. <li><strong>Faster onboarding:</strong> Developers no longer need to worry about mis-configuring deployment processes. They can consume a trusted, version-controlled Process Template that they can rely on to be updated with best practices.</li>
  688. <li><strong>Shared responsibility:</strong> Empower teams with ownership over their process, maintaining flexibility while reusing best practices, ensuring a collaborative approach to deployments.</li>
  689. </ul>
  690. <h2 id="how-to-get-started-with-process-templates">How to get started with Process Templates</h2>
  691. <p>There are several common use cases for Process Templates that show how this feature improves your deployments.</p>
  692. <h3 id="templating-part-of-a-deployment-process">Templating part of a deployment process</h3>
  693. <p>Platform engineers may expect that each project includes manual approval or email notification steps in every process. Each team might build its manual approval steps and email notifications from scratch or copy them from another project. Over time, these steps diverge in configuration, leading to drift and deployment errors. Keeping these governance steps consistent across multiple pipelines requires repetitive work and is prone to errors.</p>
  694. <p>With Process Templates, Platform Engineers can create a template containing the correct approval steps for every deployment process and share it with every Space. This template can be consumed in any project and added to an existing deployment process. The Process Template is customized to fit the project’s needs and will receive updates as it is updated in Platform Hub, ensuring developers can easily stay aligned with best practices.</p>
  695. <h3 id="templating-an-entire-deployment-process">Templating an entire deployment process</h3>
  696. <p>Some companies may use a microservices architecture, which is replicated across multiple projects with only the configuration changing. Even though the deployment steps are identical, platform teams must create and maintain separate processes for each service, customizing configuration values. Over time, these processes drift as it isn’t easy to update every pipeline with the latest changes. With Process Templates, Platform Engineers define the entire deployment process once, with parameterized values for all configuration differences (such as Docker image names, environment variables, or secrets). Each microservice project then consumes this template, supplying its configuration through parameters on the Process Template.</p>
  697. <h3 id="self-service-deployment-processes-for-application-teams">Self-Service Deployment processes for Application teams</h3>
  698. <p>Your company may have many application teams building new business components such as Web APIs, Frontend applications, or Storage components. You want to empower your application teams to go from zero to production-ready deployments in minutes, without waiting on team members or copying and pasting reference projects.With Process Templates, the platform team can create a library of standardized templates for each supported component type. These templates encode best practices for deploying business components. Application teams can pick the template matching their component type, fill out information that tailors the template to their project, and be ready to deploy to production with ease.</p>
  699. <div class="hint"><ul>
  700. <li>For more information on use-cases, please visit our <a href="https://octopus.com/use-case/platform-hub">use-cases page</a> on our website.</li>
  701. <li>For a demo of process templates, you can watch our <a href="https://www.youtube.com/watch?v=TuendU1wDPw">Youtube video</a>.</li>
  702. </ul></div>
  703. <h2 id="how-do-process-templates-work-in-octopus">How do Process Templates work in Octopus?</h2>
  704. <p>Process Templates are simple to set up and work similarly to a regular deployment process.</p>
  705. <p><strong>Prerequisites:</strong></p>
  706. <ul>
  707. <li>You must be on an Enterprise license.</li>
  708. <li>You must visit Platform Hub and connect Octopus to a Git repository.</li>
  709. </ul>
  710. <p>Here’s a quick rundown of how they work:</p>
  711. <ol>
  712. <li>
  713. <p>Add a Process Template from the Process Templates interface in Platform Hub.</p>
  714. <figure><p><img src="/blog/img/process-templates/Add-Process-Template.webp" alt="Platform Hub interface that allows users to insert process templates"></p></figure>
  715. </li>
  716. <li>
  717. <p>Add an Octopus built-in step to your deployment process. This step is similar to the existing Process Editor in your project.</p>
  718. <figure><p><img src="/blog/img/process-templates/Add-Step-Process-Template.webp" alt="Add step to Process template"></p></figure>
  719. </li>
  720. <li>
  721. <p>You can use a value or a parameter when filling out a step. To set up a parameter, visit the “Add Parameter” experience on a Process Template.</p>
  722. <figure><p><img src="/blog/img/process-templates/Add-Process-Template-Parameter.webp" alt="Add step to Process template"></p></figure>
  723. <figure><p><img src="/blog/img/process-templates/Add-Parameter-Dialog.webp" alt="Add step to Process template"></p></figure>
  724. </li>
  725. <li>
  726. <p>After you’ve added steps and configured parameters, you’ll need to commit, publish, and share the template. You must commit and publish a new version each time you change the template.</p>
  727. <figure><p><img src="/blog/img/process-templates/Process-Template-Commit-Flow.png" alt="Add step to Process template"></p></figure>
  728. </li>
  729. <li>
  730. <p>To use a Process Template in a project, you must add it via the “Add Step” experience. You can select the relevant Process Template from the dropdown and set what updates you’d like to receive.</p>
  731. <figure><p><img src="/blog/img/process-templates/Process-Template-Add-Step-Experience.png" alt="Add step to Process template"></p></figure>
  732. </li>
  733. <li>
  734. <p>In the Parameters tab, fill in the required parameters for the Process Template, and your Process Template is ready for deployment.</p>
  735. <figure><p><img src="/blog/img/process-templates/Process-Template-In-Editor.png" alt="Add step to Process template"></p></figure>
  736. </li>
  737. </ol>
  738. <div class="hint"><p>To find more in-depth information for Platform Hub, please visit <a href="https://octopus.com/docs/platform-hub">our docs</a></p></div>
  739. <h2 id="conclusion">Conclusion</h2>
  740. <p>If your current Platform Engineering approach involves building and maintaining everything yourself, we believe process templates are a more effective solution.</p>
  741. <p>Process Templates are available to all Octopus Enterprise Tier customers. An installation guide for self-hosted customers can be found on our <a href="https://octopus.com/docs/platform-hub/installation-guide">installation guide docs page</a>.</p>
  742. <p>Happy deployments!</p>]]></content>
  743.    </entry>
  744.    <entry>
  745.      <title>Resilient AI agents with MCP: Timeout and retry strategies</title>
  746.      <link href="https://octopus.com/blog/mcp-timeout-retry" />
  747.      <id>https://octopus.com/blog/mcp-timeout-retry</id>
  748.      <published>2025-10-03T00:00:00.000Z</published>
  749.      <updated>2025-10-03T00:00:00.000Z</updated>
  750.      <summary>Learn how to add timeout and retry strategies to your AI agents using the Model Context Protocol (MCP) to enhance their reliability and performance when interacting with external systems.</summary>
  751.      <author>
  752.        <name>Matthew Casperson, Octopus Deploy</name>
  753.      </author>
  754.      <content type="html"><![CDATA[<p>MCP reduces the barrier to entry for developers and organizations looking to automate workflows across multiple systems. But while it is possible to build a functional AI agent with just a few lines of code, production-grade systems need to be resilient and handle failures gracefully.</p>
  755. <p>In this post we’ll explore the options available in Langchain and Python to add timeout, retry, and circuit breaker strategies to your AI agents using MCP.</p>
  756. <h2 id="why-add-timeout-and-retry-strategies">Why add timeout and retry strategies?</h2>
  757. <p>By their very nature, MCP clients (and the AI agents that implement them) add the most value when they are orchestrating multiple platforms. However, as the number of external systems increases, so does the likelihood of failure. External systems may be unavailable, slow to respond, or return errors. Our AI agent must be resilient and handle these failures gracefully.</p>
  758. <p>Distributed systems have adopted a common set of patterns to handle failures, including timeouts, retries, and circuit breakers. These patterns help to ensure that our AI agents can continue to function, or fail gracefully, even when external systems are experiencing issues.</p>
  759. <h2 id="retry-strategies-in-langchain">Retry strategies in Langchain</h2>
  760. <p>Langchain interacts with MCP servers via tools. More specifically, tools are instances of the <a href="https://python.langchain.com/api_reference/core/tools/langchain_core.tools.structured.StructuredTool.html">StructuredTool</a> class. Langchain builds instances of StructuredTool classes for you when you call the <code>get_tools</code> function on the <a href="https://v03.api.js.langchain.com/classes/_langchain_mcp_adapters.MultiServerMCPClient.html">MultiServerMCPClient</a> class.</p>
  761. <p>In theory, Langchain has the ability to define retry strategies for tools. Specifically, the <a href="https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable">Runnable</a> class has a <code>with_retries</code> function to add retry logic to any Runnable. However, I was unable to take the <code>StructuredTool</code> instances returned by <code>get_tools</code> and add retry logic to them via the <code>with_retries</code> function, and there is no built-in support for retry strategies to tools generated by <code>MultiServerMCPClient</code> class. The inability to customize the generated tools is reflected in <a href="https://github.com/langchain-ai/langchain-mcp-adapters/issues/263">this issue</a>, which documents the limitation around error handling and MCP tools.</p>
  762. <p>To work around this limitation, we will instead use the <a href="https://en.wikipedia.org/wiki/Proxy_pattern">Gang of Four proxy pattern</a> to create a wrapper around the <code>StructuredTool</code> instances returned by the <code>get_tools</code> function. It is inside this wrapper that we will implement our retry logic.</p>
  763. <p>Fortunately we do not have to implement the proxy or retry logic from scratch. The <a href="https://pypi.org/project/wrapt/">wrapt</a> and <a href="https://pypi.org/project/tenacity/">tenacity</a> libraries make implementing the proxy and retry logic straightforward:</p>
  764. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#795E26">@wrapt.patch_function_wrapper</span><span style="color:#000000">(</span><span style="color:#A31515">"langchain_core.tools"</span><span style="color:#000000">, </span><span style="color:#A31515">"StructuredTool.ainvoke"</span><span style="color:#000000">)</span></span>
  765. <span class="line"><span style="color:#795E26">@retry</span><span style="color:#000000">(</span></span>
  766. <span class="line"><span style="color:#001080">    stop</span><span style="color:#000000">=stop_after_attempt(</span><span style="color:#098658">3</span><span style="color:#000000">),</span></span>
  767. <span class="line"><span style="color:#001080">    wait</span><span style="color:#000000">=wait_fixed(</span><span style="color:#098658">1</span><span style="color:#000000">),</span></span>
  768. <span class="line"><span style="color:#001080">    retry</span><span style="color:#000000">=retry_if_exception_type(</span><span style="color:#267F99">Exception</span><span style="color:#000000">),</span></span>
  769. <span class="line"><span style="color:#000000">)</span></span>
  770. <span class="line"><span style="color:#0000FF">async</span><span style="color:#0000FF"> def</span><span style="color:#795E26"> structuredtool_ainvoke</span><span style="color:#000000">(</span><span style="color:#001080">wrapped</span><span style="color:#000000">, </span><span style="color:#001080">instance</span><span style="color:#000000">, </span><span style="color:#001080">args</span><span style="color:#000000">, </span><span style="color:#001080">kwargs</span><span style="color:#000000">):</span></span>
  771. <span class="line"><span style="color:#795E26">    print</span><span style="color:#000000">(</span><span style="color:#A31515">"StructuredTool.ainvoke called"</span><span style="color:#000000">)</span></span>
  772. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#AF00DB"> await</span><span style="color:#000000"> wrapped(*args, **kwargs)</span></span></code></pre>
  773. <p>We intercept calls to the <code>ainvoke</code> function of the <code>StructuredTool</code> class by defining a function with the <code>@wrapt.patch_function_wrapper</code> annotation. This annotation takes two arguments: the module name and the function name.</p>
  774. <p>The intercepted calls are then retried with the <code>@retry</code> annotation. This annotation takes several arguments to define the retry strategy. In this example, we will retry up to three times, waiting one second between each attempt, and retry on all exceptions.</p>
  775. <p>Inside the function we add some logging to provide confirmation that our proxy is being called. We then called the wrapped function, which is the original <code>ainvoke</code> function of the <code>StructuredTool</code> class.</p>
  776. <p>And that is it! The <code>wrapt</code> library will intercept all calls to the <code>ainvoke</code> function of any <code>StructuredTool</code> object generated by Langchain, and our retry logic will be applied via the <code>tenacity</code> library.</p>
  777. <p>:::div{.hint} One thing to watch out for using the proxy strategy is that we are wrapping an async function. Not all retry and circuit breaker libraries support async function. You’ll need to keep this in mind if you want to use other resilience libraries. :::</p>
  778. <h2 id="timeouts-in-langchain">Timeouts in Langchain</h2>
  779. <p>Timeouts can be defined through a parameter passed to the <code>ClientSession</code> constructor. The <code>MultiServerMCPClient</code> constructor exposes the <code>session_kwargs</code> argument whose values are passed to the <code>ClientSession</code> constructor.</p>
  780. <p>This example demonstrates how set a read timeout of 60 seconds for a specific MCP server:</p>
  781. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">client = MultiServerMCPClient(</span></span>
  782. <span class="line"><span style="color:#000000">        {</span></span>
  783. <span class="line"><span style="color:#A31515">            "octopus"</span><span style="color:#000000">: {</span></span>
  784. <span class="line"><span style="color:#A31515">                "command"</span><span style="color:#000000">: </span><span style="color:#A31515">"npx"</span><span style="color:#000000">,</span></span>
  785. <span class="line"><span style="color:#A31515">                "args"</span><span style="color:#000000">: [</span></span>
  786. <span class="line"><span style="color:#A31515">                    "-y"</span><span style="color:#000000">,</span></span>
  787. <span class="line"><span style="color:#A31515">                    "@octopusdeploy/mcp-server"</span><span style="color:#000000">,</span></span>
  788. <span class="line"><span style="color:#A31515">                    "--api-key"</span><span style="color:#000000">,</span></span>
  789. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"PROD_OCTOPUS_APIKEY"</span><span style="color:#000000">),</span></span>
  790. <span class="line"><span style="color:#A31515">                    "--server-url"</span><span style="color:#000000">,</span></span>
  791. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"PROD_OCTOPUS_URL"</span><span style="color:#000000">),</span></span>
  792. <span class="line"><span style="color:#000000">                ],</span></span>
  793. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"stdio"</span><span style="color:#000000">,</span></span>
  794. <span class="line"><span style="color:#A31515">                "session_kwargs"</span><span style="color:#000000">: {</span><span style="color:#A31515">"read_timeout_seconds"</span><span style="color:#000000">: timedelta(</span><span style="color:#001080">seconds</span><span style="color:#000000">=</span><span style="color:#098658">60</span><span style="color:#000000">)},</span></span>
  795. <span class="line"><span style="color:#000000">            },</span></span>
  796. <span class="line"><span style="color:#A31515">            "zendesk"</span><span style="color:#000000">: {</span></span>
  797. <span class="line"><span style="color:#A31515">                "command"</span><span style="color:#000000">: </span><span style="color:#A31515">"uv"</span><span style="color:#000000">,</span></span>
  798. <span class="line"><span style="color:#A31515">                "args"</span><span style="color:#000000">: [</span></span>
  799. <span class="line"><span style="color:#A31515">                    "--directory"</span><span style="color:#000000">,</span></span>
  800. <span class="line"><span style="color:#A31515">                    "/home/matthew/Code/zendesk-mcp-server"</span><span style="color:#000000">,</span></span>
  801. <span class="line"><span style="color:#A31515">                    "run"</span><span style="color:#000000">,</span></span>
  802. <span class="line"><span style="color:#A31515">                    "zendesk"</span><span style="color:#000000">,</span></span>
  803. <span class="line"><span style="color:#000000">                ],</span></span>
  804. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"stdio"</span><span style="color:#000000">,</span></span>
  805. <span class="line"><span style="color:#000000">            },</span></span>
  806. <span class="line"><span style="color:#000000">        }</span></span>
  807. <span class="line"><span style="color:#000000">    )</span></span></code></pre>
  808. <h2 id="circuit-breakers-in-langchain">Circuit breakers in Langchain</h2>
  809. <p>Circuit breakers are used to prevent an application from repeatedly trying to execute an operation that is likely to fail. This prevents downstream services that are already struggling from being overwhelmed with requests.</p>
  810. <p>We’ll make use of the <a href="https://pypi.org/project/purgatory/">purgatory</a> library to implement a circuit breaker for our MCP tools.</p>
  811. <p>The first step is to create a <code>AsyncCircuitBreakerFactory</code> instance. This instance must be long-lived and shared between requests:</p>
  812. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">circuitbreaker = AsyncCircuitBreakerFactory(</span><span style="color:#001080">default_threshold</span><span style="color:#000000">=</span><span style="color:#098658">3</span><span style="color:#000000">)</span></span></code></pre>
  813. <div class="hint"><p>Circuit breakers are only useful in long-lived applications, for example, a web server or a microservice. This is because the circuit breaker logic needs to maintain state about the number of recent failures. A short-lived application, such as a script that runs once and exits, will not benefit from a circuit breaker.</p></div>
  814. <p>Similar to the retry logic, we’ll use the <code>wrapt</code> library to create a proxy around the <code>ainvoke</code> function of the <code>StructuredTool</code> class, and use the <code>@circuitbreaker</code> annotation to apply the circuit breaker logic:</p>
  815. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#795E26">@wrapt.patch_function_wrapper</span><span style="color:#000000">(</span><span style="color:#A31515">"langchain_core.tools"</span><span style="color:#000000">, </span><span style="color:#A31515">"StructuredTool.ainvoke"</span><span style="color:#000000">)</span></span>
  816. <span class="line"><span style="color:#795E26">@circuitbreaker</span><span style="color:#000000">(</span><span style="color:#A31515">"StructuredTool.ainvoke"</span><span style="color:#000000">)</span></span>
  817. <span class="line"><span style="color:#0000FF">async</span><span style="color:#0000FF"> def</span><span style="color:#795E26"> structuredtool_ainvoke</span><span style="color:#000000">(</span><span style="color:#001080">wrapped</span><span style="color:#000000">, </span><span style="color:#001080">instance</span><span style="color:#000000">, </span><span style="color:#001080">args</span><span style="color:#000000">, </span><span style="color:#001080">kwargs</span><span style="color:#000000">):</span></span>
  818. <span class="line"><span style="color:#795E26">    print</span><span style="color:#000000">(</span><span style="color:#A31515">"StructuredTool.ainvoke called"</span><span style="color:#000000">)</span></span>
  819. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#AF00DB"> await</span><span style="color:#000000"> wrapped(*args, **kwargs)</span></span></code></pre>
  820. <h2 id="simulating-failures">Simulating failures</h2>
  821. <p>To simulate failures, we can create a proxy around the <code>ainvoke</code> function of the <code>BaseTool</code> class. The <code>StructuredTool</code> class inherits from the <code>BaseTool</code> class, and the <code>ainvoke</code> function of the <code>BaseTool</code> class is called by the <code>ainvoke</code> function of the <code>StructuredTool</code> class. This gives us a convenient place to simulate failures for all tools.</p>
  822. <p>Here we randomly raise an exception two-thirds of the time to simulate a transient error:</p>
  823. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#795E26">@wrapt.patch_function_wrapper</span><span style="color:#000000">(</span><span style="color:#A31515">"langchain_core.tools"</span><span style="color:#000000">, </span><span style="color:#A31515">"BaseTool.ainvoke"</span><span style="color:#000000">)</span></span>
  824. <span class="line"><span style="color:#0000FF">async</span><span style="color:#0000FF"> def</span><span style="color:#795E26"> basetool_ainvoke</span><span style="color:#000000">(</span><span style="color:#001080">wrapped</span><span style="color:#000000">, </span><span style="color:#001080">instance</span><span style="color:#000000">, </span><span style="color:#001080">args</span><span style="color:#000000">, </span><span style="color:#001080">kwargs</span><span style="color:#000000">):</span></span>
  825. <span class="line"><span style="color:#795E26">    print</span><span style="color:#000000">(</span><span style="color:#A31515">"BaseTool.ainvoke called"</span><span style="color:#000000">)</span></span>
  826. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> random.randint(</span><span style="color:#098658">1</span><span style="color:#000000">, </span><span style="color:#098658">3</span><span style="color:#000000">) != </span><span style="color:#098658">3</span><span style="color:#000000">:</span></span>
  827. <span class="line"><span style="color:#795E26">        print</span><span style="color:#000000">(</span><span style="color:#A31515">"Simulated transient error"</span><span style="color:#000000">)</span></span>
  828. <span class="line"><span style="color:#AF00DB">        raise</span><span style="color:#267F99"> RuntimeError</span><span style="color:#000000">(</span><span style="color:#A31515">"Simulated transient error"</span><span style="color:#000000">)</span></span>
  829. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#AF00DB"> await</span><span style="color:#000000"> wrapped(*args, **kwargs)</span></span></code></pre>
  830. <p>If you have implemented a circuit breaker strategy, you should see that the MCP client eventually stops calling the MCP server after a few failures. If you have implemented a retry strategy with a high level of retries, you should see the prompt succeed as the retry library intercepts the exceptions and retries the request.</p>
  831. <h2 id="conclusion">Conclusion</h2>
  832. <p>Production-grade AI agents need to handle failures gracefully. By implementing timeout, retry, and circuit breaker strategies, we can ensure that our AI agents are resilient and can continue to function even when external systems are experiencing issues.</p>
  833. <p>Langchain has some built-in support for timeouts, but implementing retry and circuit breaker strategies requires some additional work. By using the <code>wrapt</code>, <code>retry</code>, and <code>pybreaker</code> libraries, we can easily add these strategies to our MCP tools via the proxy pattern.</p>
  834. <p>Happy deployments!</p>]]></content>
  835.    </entry>
  836.    <entry>
  837.      <title>Manage context window size with advanced AI agents</title>
  838.      <link href="https://octopus.com/blog/advanced-ai-agents" />
  839.      <id>https://octopus.com/blog/advanced-ai-agents</id>
  840.      <published>2025-10-02T00:00:00.000Z</published>
  841.      <updated>2025-10-02T00:00:00.000Z</updated>
  842.      <summary>Learn how to execute complex workflows using AI agents with Octopus Deploy while managing context window size limitations.</summary>
  843.      <author>
  844.        <name>Matthew Casperson, Octopus Deploy</name>
  845.      </author>
  846.      <content type="html"><![CDATA[<p>The promise of MCP is to expose many platforms and services to AI models, enabling complex queries and workflows to be executed with natural language prompts.</p>
  847. <p>While it is tempting to believe that MCP clients can define arbitrarily complex workflows in a single prompt, in practice, the limitations of the current generation of LLMs present challenges that must be overcome. Specifically, the context window size of LLMs defines an upper limit on how much data a single MCP prompt can consume as part of a request.</p>
  848. <p>In this post we’ll explore strategies for managing context window size limitations when working with AI agents and Octopus Deploy.</p>
  849. <h3 id="what-is-context-window-size">What is context window size?</h3>
  850. <p>You can think of the context window as being the amount of information an LLM can process.</p>
  851. <p>Context windows are measured in tokens, which are chunks of text that can be as short as one character or as long as one word. For example, the word “chat” could be one token, while the word “chatting” could be two tokens (“chatt” and “ing”). While there is not an exact ratio between tokens and words or characters (and the ratio changes between LLMs), a rough approximation is that one token is equals four characters of English text. The Amazon documentation for <a href="https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html">Amazon Titan models</a> notes that:</p>
  852. <blockquote>
  853. <p>The characters to token ratio in English is 4.7 characters per token, on average.</p>
  854. </blockquote>
  855. <p>The context window size is dependent on the specific LLM being used. For example, OpenAI’s gpt-5 model has a context window size of 400,000 tokens (272,000 tokens for input and 128,000 tokens for output), while some variations of the gpt-4 models have a context window size of 32,000 tokens.</p>
  856. <p>These sound like large numbers, but it doesn’t take long to exhaust the context window size when working with large blobs of text or API results. JSON in particular consumes a lot of tokens. In this screenshot from the <a href="https://platform.openai.com/tokenizer">OpenAI Tokenizer tool</a>, you can see that individual quotes and braces are represented as individual tokens:</p>
  857. <p><img src="/blog/img/advanced-ai-agents/openai-tokenizer.png" alt="A screenshot of a JSON blob highlighting individual tokens"></p>
  858. <h3 id="prompts-that-exhaust-the-context-window">Prompts that exhaust the context window</h3>
  859. <p>Consider the following prompt:</p>
  860. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>In Octopus, get the last 10 releases deployed to the "Production" environment in the "Octopus Server" space.</span></span>
  861. <span class="line"><span>Get the releases from the deployments.</span></span>
  862. <span class="line"><span>In ZenDesk, get the last 100 tickets and their comments.</span></span>
  863. <span class="line"><span>Create a report summarizing the issues reported by customers in the tickets. </span></span>
  864. <span class="line"><span>You must only consider tickets that mention the Octopus release versions. </span></span>
  865. <span class="line"><span>You must only consider support tickets raised by customers. </span></span>
  866. <span class="line"><span>You must use your best judgment to identify support tickets.</span></span></code></pre>
  867. <p>The intention here is to write a report that summarizes customer issues based on the last 10 releases of an application deployed to production. It is simple enough to write this prompt, but behind the scenes, the LLM must execute multiple API calls:</p>
  868. <ul>
  869. <li>Convert the space name to a space ID</li>
  870. <li>Convert the environment name to an environment ID</li>
  871. <li>Get the last 10 deployments to the environment</li>
  872. <li>Get the details of the releases from the deployments</li>
  873. <li>Get the last 100 tickets from ZenDesk</li>
  874. </ul>
  875. <p>Each of these API calls return token-gobbling JSON results that are collected and passed to the LLM to generate the report. The JSON blobs returned by Octopus can be quite verbose, and it is not hard to see how long support tickets can exhaust the context window size, especially given the tendency of email clients to include the entirety of a previous email chain in each reply.</p>
  876. <p>Even if we don’t exhaust the context window size, we may still benefit from reducing the amount of data passed to the LLM, as this <a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents">post from Anthropic</a> notes:</p>
  877. <blockquote>
  878. <p>Studies on needle-in-a-haystack style benchmarking have uncovered the concept of context rot: as the number of tokens in the context window increases, the model’s ability to accurately recall information from that context decreases.</p>
  879. </blockquote>
  880. <p>When prompts like this work, they seem almost magical. But when they fail due to context window size limitations, we need to implement some advanced strategies to help manage the context window size.</p>
  881. <h3 id="strategies-for-managing-context-window-size">Strategies for managing context window size</h3>
  882. <p>As we saw in the <a href="https://octopus.com/blog/agentic-ai-with-mcp">previous post</a>, LangChain provides the ability to extract tools from MCP servers and use them in agentic workflows. We can add additional custom tools to this collection to perform operations that help manage context window size.</p>
  883. <p>Consider this tool definition:</p>
  884. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#795E26">@tool</span></span>
  885. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> discard_deployments</span><span style="color:#000000">(</span></span>
  886. <span class="line"><span style="color:#001080">    tool_call_id</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">str</span><span style="color:#000000">, InjectedToolCallId],</span></span>
  887. <span class="line"><span style="color:#001080">    state</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">dict</span><span style="color:#000000">, InjectedState],</span></span>
  888. <span class="line"><span style="color:#000000">) -> Command:</span></span>
  889. <span class="line"><span style="color:#A31515">    """Discards the list of deployments."""</span></span>
  890. <span class="line"></span>
  891. <span class="line"><span style="color:#0000FF">    def</span><span style="color:#795E26"> trim_release</span><span style="color:#000000">(</span><span style="color:#001080">release</span><span style="color:#000000">):</span></span>
  892. <span class="line"><span style="color:#AF00DB">        if</span><span style="color:#795E26"> isinstance</span><span style="color:#000000">(release, ToolMessage) </span><span style="color:#0000FF">and</span><span style="color:#000000"> release.name == </span><span style="color:#A31515">"list_deployments"</span><span style="color:#000000">:</span></span>
  893. <span class="line"><span style="color:#000000">            release.name = </span><span style="color:#A31515">"trimmed_list_deployments"</span></span>
  894. <span class="line"><span style="color:#000000">            release.content = </span><span style="color:#A31515">""</span></span>
  895. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> release</span></span>
  896. <span class="line"></span>
  897. <span class="line"><span style="color:#000000">    trim_messages = [trim_release(msg) </span><span style="color:#AF00DB">for</span><span style="color:#000000"> msg </span><span style="color:#AF00DB">in</span><span style="color:#000000"> state[</span><span style="color:#A31515">"messages"</span><span style="color:#000000">]]</span></span>
  898. <span class="line"></span>
  899. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> Command(</span></span>
  900. <span class="line"><span style="color:#001080">        update</span><span style="color:#000000">={</span></span>
  901. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: [</span></span>
  902. <span class="line"><span style="color:#000000">                RemoveMessage(</span><span style="color:#001080">id</span><span style="color:#000000">=REMOVE_ALL_MESSAGES),</span></span>
  903. <span class="line"><span style="color:#000000">                *trim_messages,</span></span>
  904. <span class="line"><span style="color:#000000">                ToolMessage(</span></span>
  905. <span class="line"><span style="color:#A31515">                    "Discarded list of deployments"</span><span style="color:#000000">, </span><span style="color:#001080">tool_call_id</span><span style="color:#000000">=tool_call_id</span></span>
  906. <span class="line"><span style="color:#000000">                ),</span></span>
  907. <span class="line"><span style="color:#000000">            ],</span></span>
  908. <span class="line"><span style="color:#000000">        }</span></span>
  909. <span class="line"><span style="color:#000000">    )</span></span></code></pre>
  910. <p>This tool take advantage of a number of advanced features of LangChain:</p>
  911. <ul>
  912. <li>The <a href="https://python.langchain.com/api_reference/core/tools/langchain_core.tools.base.InjectedToolCallId.html">InjectedToolCallId</a> annotation to inject the unique ID of the tool call</li>
  913. <li>The <a href="https://langchain-ai.github.io/langgraph/reference/agents/#langgraph.prebuilt.tool_node.InjectedState">InjectedState</a> annotation to inject the current state of the agent</li>
  914. <li>Returning a <a href="https://langchain-ai.github.io/langgraph/reference/types/#langgraph.types.Command">Command</a> object to update the state of the agent</li>
  915. <li>Using the <a href="https://python.langchain.com/api_reference/core/messages/langchain_core.messages.modifier.RemoveMessage.html">RemoveMessage</a> class to remove all messages from the agent’s state</li>
  916. </ul>
  917. <p>Let’s go through this function line by line.</p>
  918. <p>We start by defining a tool. A tool is simply a function decorated with the <code>@tool</code> decorator. The function docstring is used to describe the tool to the LLM, and is how the LLM knows when to call the tool based on the plain text instructions in the prompt.</p>
  919. <p>This two has two parameters, <code>tool_call_id</code> and <code>state</code>.</p>
  920. <p>The <code>tool_call_id</code> parameter is annotated with the <code>InjectedToolCallId</code> annotation, which tells LangChain to inject the unique ID of the tool call into this parameter.</p>
  921. <p>The <code>state</code> parameter is annotated with the <code>InjectedState</code> annotation, which tells LangChain to inject the current state of the agent into this parameter. It is this state that we want to modify:</p>
  922. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#795E26">@tool</span></span>
  923. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> discard_deployments</span><span style="color:#000000">(</span></span>
  924. <span class="line"><span style="color:#001080">    tool_call_id</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">str</span><span style="color:#000000">, InjectedToolCallId],</span></span>
  925. <span class="line"><span style="color:#001080">    state</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">dict</span><span style="color:#000000">, InjectedState],</span></span>
  926. <span class="line"><span style="color:#000000">) -> Command:</span></span>
  927. <span class="line"><span style="color:#A31515">    """Discards the list of deployments."""</span></span></code></pre>
  928. <p>A nested function called <code>trim_release</code> is defined to process each message in the agent’s state. If the message is a <code>ToolMessage</code> with the name <code>list_deployments</code> (this is the name of the tool exposed by the Octopus MCP server), it changes the name to <code>trimmed_list_deployments</code> and clears the content. This effectively removes the verbose JSON content from the message while retaining a record that the tool was called:</p>
  929. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#0000FF">    def</span><span style="color:#795E26"> trim_release</span><span style="color:#000000">(</span><span style="color:#001080">release</span><span style="color:#000000">):</span></span>
  930. <span class="line"><span style="color:#AF00DB">        if</span><span style="color:#795E26"> isinstance</span><span style="color:#000000">(release, ToolMessage) </span><span style="color:#0000FF">and</span><span style="color:#000000"> release.name == </span><span style="color:#A31515">"list_deployments"</span><span style="color:#000000">:</span></span>
  931. <span class="line"><span style="color:#000000">            release.name = </span><span style="color:#A31515">"trimmed_list_deployments"</span></span>
  932. <span class="line"><span style="color:#000000">            release.content = </span><span style="color:#A31515">""</span></span>
  933. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> release</span></span></code></pre>
  934. <p>We then use a list comprehension to apply the <code>trim_release</code> function to each message in the agent’s state, producing a new list of messages with the deployments trimmed:</p>
  935. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">trim_messages = [trim_release(msg) </span><span style="color:#AF00DB">for</span><span style="color:#000000"> msg </span><span style="color:#AF00DB">in</span><span style="color:#000000"> state[</span><span style="color:#A31515">"messages"</span><span style="color:#000000">]]</span></span></code></pre>
  936. <p>We then return a <code>Command</code> object that updates the agent’s state. <code>Command</code> objects allow us to update the state of the agent. It is the messages in the state that are placed in the context window, so by modifying these messages, we can manage the context window size.</p>
  937. <p>By default, messages returned from a tool are appended to the existing messages in the state. However, in this case, we want to remove all existing messages and replace them with our trimmed messages. We do this by including a <code>RemoveMessage</code> object with the special ID <code>REMOVE_ALL_MESSAGES</code>, which tells LangChain to remove all existing messages from the state.</p>
  938. <p>Finally, we include our trimmed messages and a new <code>ToolMessage</code> indicating that the deployments have been discarded. This message includes the <code>tool_call_id</code> so that it can be traced back to the specific tool call:</p>
  939. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#AF00DB">return</span><span style="color:#000000"> Command(</span></span>
  940. <span class="line"><span style="color:#001080">        update</span><span style="color:#000000">={</span></span>
  941. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: [</span></span>
  942. <span class="line"><span style="color:#000000">                RemoveMessage(</span><span style="color:#001080">id</span><span style="color:#000000">=REMOVE_ALL_MESSAGES),</span></span>
  943. <span class="line"><span style="color:#000000">                *trim_messages,</span></span>
  944. <span class="line"><span style="color:#000000">                ToolMessage(</span></span>
  945. <span class="line"><span style="color:#A31515">                    "Discarded list of deployments"</span><span style="color:#000000">, </span><span style="color:#001080">tool_call_id</span><span style="color:#000000">=tool_call_id</span></span>
  946. <span class="line"><span style="color:#000000">                ),</span></span>
  947. <span class="line"><span style="color:#000000">            ],</span></span>
  948. <span class="line"><span style="color:#000000">        }</span></span>
  949. <span class="line"><span style="color:#000000">    )</span></span></code></pre>
  950. <p>Our custom tool is added to the collection of tools exported from the MCP servers:</p>
  951. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">tools = </span><span style="color:#AF00DB">await</span><span style="color:#000000"> client.get_tools()</span></span>
  952. <span class="line"><span style="color:#000000">tools.append(discard_deployments)</span></span></code></pre>
  953. <p>And we can call the new tool from our prompt with the instruction <code>Discard the list of deployments</code>:</p>
  954. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>In Octopus, get the last 10 releases deployed to the "Production" environment in the "Octopus Server" space.</span></span>
  955. <span class="line"><span>Get the releases from the deployments.</span></span>
  956. <span class="line"><span>Discard the list of deployments.</span></span>
  957. <span class="line"><span>In ZenDesk, get the last 100 tickets and their comments.</span></span>
  958. <span class="line"><span>Create a report summarizing the issues reported by customers in the tickets. </span></span>
  959. <span class="line"><span>You must only consider tickets that mention the Octopus release versions. </span></span>
  960. <span class="line"><span>You must only consider support tickets raised by customers. </span></span>
  961. <span class="line"><span>You must use your best judgement to identify support tickets.</span></span></code></pre>
  962. <p>Now, once the LLM has called the Octopus MCP server to get the list of deployments, and retrieved the releases from those deployments, it calls our custom tool to discard the deployments JSON blob from the state, which in turn means those messages are not passed to the LLM as part of the context window. The deployments were never needed for the final report, so we have reduced the amount of data passed to the LLM without losing any important information.</p>
  963. <p>There are a number of other opportunities to reduce the size of the messages passed to the LLM. The JSON blobs related to releases can be replaced by the release versions, and the ZenDesk tickets can be trimmed.</p>
  964. <h2 id="full-source-code">Full source code</h2>
  965. <p>This is the complete source code, including the additional custom tools used to trim the release details to just the version (<code>trim_releases_to_version</code>) and trim the ticket descriptions to 1000 characters (<code>trim_ticket_descriptions</code>), and the additional instructions in the prompt to call these tools:</p>
  966. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> asyncio</span></span>
  967. <span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> json</span></span>
  968. <span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> os</span></span>
  969. <span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> re</span></span>
  970. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> typing </span><span style="color:#AF00DB">import</span><span style="color:#000000"> Annotated</span></span>
  971. <span class="line"></span>
  972. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langchain_core.messages </span><span style="color:#AF00DB">import</span><span style="color:#000000"> RemoveMessage, ToolMessage, trim_messages</span></span>
  973. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langchain_core.tools </span><span style="color:#AF00DB">import</span><span style="color:#000000"> tool, InjectedToolCallId</span></span>
  974. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langchain_mcp_adapters.client </span><span style="color:#AF00DB">import</span><span style="color:#000000"> MultiServerMCPClient</span></span>
  975. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langchain_azure_ai.chat_models </span><span style="color:#AF00DB">import</span><span style="color:#000000"> AzureAIChatCompletionsModel</span></span>
  976. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langgraph.graph.message </span><span style="color:#AF00DB">import</span><span style="color:#000000"> REMOVE_ALL_MESSAGES</span></span>
  977. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langgraph.prebuilt </span><span style="color:#AF00DB">import</span><span style="color:#000000"> create_react_agent, InjectedState</span></span>
  978. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langgraph.types </span><span style="color:#AF00DB">import</span><span style="color:#000000"> Command</span></span>
  979. <span class="line"></span>
  980. <span class="line"></span>
  981. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> remove_line_padding</span><span style="color:#000000">(</span><span style="color:#001080">text</span><span style="color:#000000">):</span></span>
  982. <span class="line"><span style="color:#A31515">    """</span></span>
  983. <span class="line"><span style="color:#A31515">    Remove leading and trailing whitespace from each line in the text.</span></span>
  984. <span class="line"><span style="color:#A31515">    :param text: The text to process.</span></span>
  985. <span class="line"><span style="color:#A31515">    :return: The text with leading and trailing whitespace removed from each line.</span></span>
  986. <span class="line"><span style="color:#A31515">    """</span></span>
  987. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#A31515"> "</span><span style="color:#EE0000">\n</span><span style="color:#A31515">"</span><span style="color:#000000">.join(line.strip() </span><span style="color:#AF00DB">for</span><span style="color:#000000"> line </span><span style="color:#AF00DB">in</span><span style="color:#000000"> text.splitlines() </span><span style="color:#AF00DB">if</span><span style="color:#000000"> line.strip())</span></span>
  988. <span class="line"></span>
  989. <span class="line"></span>
  990. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> remove_thinking</span><span style="color:#000000">(</span><span style="color:#001080">text</span><span style="color:#000000">):</span></span>
  991. <span class="line"><span style="color:#A31515">    """</span></span>
  992. <span class="line"><span style="color:#A31515">    Remove &#x3C;think>...&#x3C;/think> tags and their content from the text.</span></span>
  993. <span class="line"><span style="color:#A31515">    :param text: The text to process.</span></span>
  994. <span class="line"><span style="color:#A31515">    :return: The text with &#x3C;think>...&#x3C;/think> tags and their content removed.</span></span>
  995. <span class="line"><span style="color:#A31515">    """</span></span>
  996. <span class="line"><span style="color:#000000">    stripped_text = text.strip()</span></span>
  997. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> stripped_text.startswith(</span><span style="color:#A31515">"&#x3C;think>"</span><span style="color:#000000">) </span><span style="color:#0000FF">and</span><span style="color:#A31515"> "&#x3C;/think>"</span><span style="color:#0000FF"> in</span><span style="color:#000000"> stripped_text:</span></span>
  998. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> re.sub(</span><span style="color:#0000FF">r</span><span style="color:#811F3F">"&#x3C;think>.</span><span style="color:#000000">*?</span><span style="color:#811F3F">&#x3C;/think>"</span><span style="color:#000000">, </span><span style="color:#A31515">""</span><span style="color:#000000">, stripped_text, </span><span style="color:#001080">flags</span><span style="color:#000000">=re.DOTALL)</span></span>
  999. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> stripped_text</span></span>
  1000. <span class="line"></span>
  1001. <span class="line"></span>
  1002. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> response_to_text</span><span style="color:#000000">(</span><span style="color:#001080">response</span><span style="color:#000000">):</span></span>
  1003. <span class="line"><span style="color:#A31515">    """</span></span>
  1004. <span class="line"><span style="color:#A31515">    Extract the content from the last message in the response.</span></span>
  1005. <span class="line"><span style="color:#A31515">    :param response: The response dictionary containing messages.</span></span>
  1006. <span class="line"><span style="color:#A31515">    :return: The content of the last message, or an empty string if no messages are present.</span></span>
  1007. <span class="line"><span style="color:#A31515">    """</span></span>
  1008. <span class="line"><span style="color:#000000">    messages = response.get(</span><span style="color:#A31515">"messages"</span><span style="color:#000000">, [])</span></span>
  1009. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#0000FF"> not</span><span style="color:#000000"> messages </span><span style="color:#0000FF">or</span><span style="color:#795E26"> len</span><span style="color:#000000">(messages) == </span><span style="color:#098658">0</span><span style="color:#000000">:</span></span>
  1010. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#A31515"> ""</span></span>
  1011. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> messages.pop().content</span></span>
  1012. <span class="line"></span>
  1013. <span class="line"></span>
  1014. <span class="line"><span style="color:#795E26">@tool</span></span>
  1015. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> trim_ticket_descriptions</span><span style="color:#000000">(</span></span>
  1016. <span class="line"><span style="color:#001080">        tool_call_id</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">str</span><span style="color:#000000">, InjectedToolCallId],</span></span>
  1017. <span class="line"><span style="color:#001080">        state</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">dict</span><span style="color:#000000">, InjectedState],</span></span>
  1018. <span class="line"><span style="color:#000000">) -> Command:</span></span>
  1019. <span class="line"><span style="color:#A31515">    """Trims the description of the ZenDesk tickets."""</span></span>
  1020. <span class="line"></span>
  1021. <span class="line"><span style="color:#0000FF">    def</span><span style="color:#795E26"> trim_description</span><span style="color:#000000">(</span><span style="color:#001080">ticket</span><span style="color:#000000">):</span></span>
  1022. <span class="line"><span style="color:#000000">        ticket[</span><span style="color:#A31515">"description"</span><span style="color:#000000">] = (</span></span>
  1023. <span class="line"><span style="color:#000000">            ticket[</span><span style="color:#A31515">"description"</span><span style="color:#000000">][:</span><span style="color:#098658">1000</span><span style="color:#000000">] + </span><span style="color:#A31515">"..."</span></span>
  1024. <span class="line"><span style="color:#AF00DB">            if</span><span style="color:#795E26"> len</span><span style="color:#000000">(ticket[</span><span style="color:#A31515">"description"</span><span style="color:#000000">]) > </span><span style="color:#098658">1000</span></span>
  1025. <span class="line"><span style="color:#AF00DB">            else</span><span style="color:#000000"> ticket[</span><span style="color:#A31515">"description"</span><span style="color:#000000">]</span></span>
  1026. <span class="line"><span style="color:#000000">        )</span></span>
  1027. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> ticket</span></span>
  1028. <span class="line"></span>
  1029. <span class="line"><span style="color:#0000FF">    def</span><span style="color:#795E26"> trim_description_list</span><span style="color:#000000">(</span><span style="color:#001080">message</span><span style="color:#000000">):</span></span>
  1030. <span class="line"><span style="color:#AF00DB">        if</span><span style="color:#795E26"> isinstance</span><span style="color:#000000">(message, ToolMessage) </span><span style="color:#0000FF">and</span><span style="color:#000000"> message.name == </span><span style="color:#A31515">"get_tickets"</span><span style="color:#000000">:</span></span>
  1031. <span class="line"><span style="color:#000000">            ticket_data = json.loads(message.content)</span></span>
  1032. <span class="line"><span style="color:#000000">            trimmed_ticket_data = [</span></span>
  1033. <span class="line"><span style="color:#000000">                trim_description(ticket) </span><span style="color:#AF00DB">for</span><span style="color:#000000"> ticket </span><span style="color:#AF00DB">in</span><span style="color:#000000"> ticket_data[</span><span style="color:#A31515">"tickets"</span><span style="color:#000000">]</span></span>
  1034. <span class="line"><span style="color:#000000">            ]</span></span>
  1035. <span class="line"><span style="color:#000000">            message.content = json.dumps(trimmed_ticket_data)</span></span>
  1036. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> message</span></span>
  1037. <span class="line"></span>
  1038. <span class="line"><span style="color:#000000">    trim_messages = [trim_description_list(msg) </span><span style="color:#AF00DB">for</span><span style="color:#000000"> msg </span><span style="color:#AF00DB">in</span><span style="color:#000000"> state[</span><span style="color:#A31515">"messages"</span><span style="color:#000000">]]</span></span>
  1039. <span class="line"></span>
  1040. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> Command(</span></span>
  1041. <span class="line"><span style="color:#001080">        update</span><span style="color:#000000">={</span></span>
  1042. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: [</span></span>
  1043. <span class="line"><span style="color:#000000">                RemoveMessage(</span><span style="color:#001080">id</span><span style="color:#000000">=REMOVE_ALL_MESSAGES),</span></span>
  1044. <span class="line"><span style="color:#000000">                *trim_messages,</span></span>
  1045. <span class="line"><span style="color:#000000">                ToolMessage(</span><span style="color:#A31515">"Trimmed releases to version"</span><span style="color:#000000">, </span><span style="color:#001080">tool_call_id</span><span style="color:#000000">=tool_call_id),</span></span>
  1046. <span class="line"><span style="color:#000000">            ],</span></span>
  1047. <span class="line"><span style="color:#000000">        }</span></span>
  1048. <span class="line"><span style="color:#000000">    )</span></span>
  1049. <span class="line"></span>
  1050. <span class="line"></span>
  1051. <span class="line"><span style="color:#795E26">@tool</span></span>
  1052. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> discard_deployments</span><span style="color:#000000">(</span></span>
  1053. <span class="line"><span style="color:#001080">        tool_call_id</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">str</span><span style="color:#000000">, InjectedToolCallId],</span></span>
  1054. <span class="line"><span style="color:#001080">        state</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">dict</span><span style="color:#000000">, InjectedState],</span></span>
  1055. <span class="line"><span style="color:#000000">) -> Command:</span></span>
  1056. <span class="line"><span style="color:#A31515">    """Discards the list of deployments."""</span></span>
  1057. <span class="line"></span>
  1058. <span class="line"><span style="color:#0000FF">    def</span><span style="color:#795E26"> trim_release</span><span style="color:#000000">(</span><span style="color:#001080">release</span><span style="color:#000000">):</span></span>
  1059. <span class="line"><span style="color:#AF00DB">        if</span><span style="color:#795E26"> isinstance</span><span style="color:#000000">(release, ToolMessage) </span><span style="color:#0000FF">and</span><span style="color:#000000"> release.name == </span><span style="color:#A31515">"list_deployments"</span><span style="color:#000000">:</span></span>
  1060. <span class="line"><span style="color:#000000">            release.name = </span><span style="color:#A31515">"trimmed_list_deployments"</span></span>
  1061. <span class="line"><span style="color:#000000">            release.content = </span><span style="color:#A31515">""</span></span>
  1062. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> release</span></span>
  1063. <span class="line"></span>
  1064. <span class="line"><span style="color:#000000">    trim_messages = [trim_release(msg) </span><span style="color:#AF00DB">for</span><span style="color:#000000"> msg </span><span style="color:#AF00DB">in</span><span style="color:#000000"> state[</span><span style="color:#A31515">"messages"</span><span style="color:#000000">]]</span></span>
  1065. <span class="line"></span>
  1066. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> Command(</span></span>
  1067. <span class="line"><span style="color:#001080">        update</span><span style="color:#000000">={</span></span>
  1068. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: [</span></span>
  1069. <span class="line"><span style="color:#000000">                RemoveMessage(</span><span style="color:#001080">id</span><span style="color:#000000">=REMOVE_ALL_MESSAGES),</span></span>
  1070. <span class="line"><span style="color:#000000">                *trim_messages,</span></span>
  1071. <span class="line"><span style="color:#000000">                ToolMessage(</span><span style="color:#A31515">"Discarded list of deployments"</span><span style="color:#000000">, </span><span style="color:#001080">tool_call_id</span><span style="color:#000000">=tool_call_id),</span></span>
  1072. <span class="line"><span style="color:#000000">            ],</span></span>
  1073. <span class="line"><span style="color:#000000">        }</span></span>
  1074. <span class="line"><span style="color:#000000">    )</span></span>
  1075. <span class="line"></span>
  1076. <span class="line"></span>
  1077. <span class="line"><span style="color:#795E26">@tool</span></span>
  1078. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> trim_releases_to_version</span><span style="color:#000000">(</span></span>
  1079. <span class="line"><span style="color:#001080">        tool_call_id</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">str</span><span style="color:#000000">, InjectedToolCallId],</span></span>
  1080. <span class="line"><span style="color:#001080">        state</span><span style="color:#000000">: Annotated[</span><span style="color:#267F99">dict</span><span style="color:#000000">, InjectedState],</span></span>
  1081. <span class="line"><span style="color:#000000">) -> Command:</span></span>
  1082. <span class="line"><span style="color:#A31515">    """Trims the details of Octopus releases to their version."""</span></span>
  1083. <span class="line"></span>
  1084. <span class="line"><span style="color:#0000FF">    def</span><span style="color:#795E26"> trim_release</span><span style="color:#000000">(</span><span style="color:#001080">release</span><span style="color:#000000">):</span></span>
  1085. <span class="line"><span style="color:#AF00DB">        if</span><span style="color:#795E26"> isinstance</span><span style="color:#000000">(release, ToolMessage) </span><span style="color:#0000FF">and</span><span style="color:#000000"> release.name == </span><span style="color:#A31515">"get_release_by_id"</span><span style="color:#000000">:</span></span>
  1086. <span class="line"><span style="color:#000000">            release_data = json.loads(release.content)</span></span>
  1087. <span class="line"><span style="color:#000000">            release.name = </span><span style="color:#A31515">"trimmed_release"</span></span>
  1088. <span class="line"><span style="color:#000000">            release.content = release_data.get(</span><span style="color:#A31515">"version"</span><span style="color:#000000">, </span><span style="color:#A31515">"Unknown Version"</span><span style="color:#000000">)</span></span>
  1089. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> release</span></span>
  1090. <span class="line"></span>
  1091. <span class="line"><span style="color:#000000">    trim_messages = [trim_release(msg) </span><span style="color:#AF00DB">for</span><span style="color:#000000"> msg </span><span style="color:#AF00DB">in</span><span style="color:#000000"> state[</span><span style="color:#A31515">"messages"</span><span style="color:#000000">]]</span></span>
  1092. <span class="line"></span>
  1093. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> Command(</span></span>
  1094. <span class="line"><span style="color:#001080">        update</span><span style="color:#000000">={</span></span>
  1095. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: [</span></span>
  1096. <span class="line"><span style="color:#000000">                RemoveMessage(</span><span style="color:#001080">id</span><span style="color:#000000">=REMOVE_ALL_MESSAGES),</span></span>
  1097. <span class="line"><span style="color:#000000">                *trim_messages,</span></span>
  1098. <span class="line"><span style="color:#000000">                ToolMessage(</span><span style="color:#A31515">"Trimmed releases to version"</span><span style="color:#000000">, </span><span style="color:#001080">tool_call_id</span><span style="color:#000000">=tool_call_id),</span></span>
  1099. <span class="line"><span style="color:#000000">            ],</span></span>
  1100. <span class="line"><span style="color:#000000">        }</span></span>
  1101. <span class="line"><span style="color:#000000">    )</span></span>
  1102. <span class="line"></span>
  1103. <span class="line"></span>
  1104. <span class="line"><span style="color:#0000FF">async</span><span style="color:#0000FF"> def</span><span style="color:#795E26"> main</span><span style="color:#000000">():</span></span>
  1105. <span class="line"><span style="color:#A31515">    """</span></span>
  1106. <span class="line"><span style="color:#A31515">    The entrypoint to our AI agent.</span></span>
  1107. <span class="line"><span style="color:#A31515">    """</span></span>
  1108. <span class="line"><span style="color:#000000">    client = MultiServerMCPClient(</span></span>
  1109. <span class="line"><span style="color:#000000">        {</span></span>
  1110. <span class="line"><span style="color:#A31515">            "octopus"</span><span style="color:#000000">: {</span></span>
  1111. <span class="line"><span style="color:#A31515">                "command"</span><span style="color:#000000">: </span><span style="color:#A31515">"npx"</span><span style="color:#000000">,</span></span>
  1112. <span class="line"><span style="color:#A31515">                "args"</span><span style="color:#000000">: [</span></span>
  1113. <span class="line"><span style="color:#A31515">                    "-y"</span><span style="color:#000000">,</span></span>
  1114. <span class="line"><span style="color:#A31515">                    "@octopusdeploy/mcp-server"</span><span style="color:#000000">,</span></span>
  1115. <span class="line"><span style="color:#A31515">                    "--api-key"</span><span style="color:#000000">,</span></span>
  1116. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"PROD_OCTOPUS_APIKEY"</span><span style="color:#000000">),</span></span>
  1117. <span class="line"><span style="color:#A31515">                    "--server-url"</span><span style="color:#000000">,</span></span>
  1118. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"PROD_OCTOPUS_URL"</span><span style="color:#000000">),</span></span>
  1119. <span class="line"><span style="color:#000000">                ],</span></span>
  1120. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"stdio"</span><span style="color:#000000">,</span></span>
  1121. <span class="line"><span style="color:#000000">            },</span></span>
  1122. <span class="line"><span style="color:#A31515">            "zendesk"</span><span style="color:#000000">: {</span></span>
  1123. <span class="line"><span style="color:#A31515">                "command"</span><span style="color:#000000">: </span><span style="color:#A31515">"uv"</span><span style="color:#000000">,</span></span>
  1124. <span class="line"><span style="color:#A31515">                "args"</span><span style="color:#000000">: [</span></span>
  1125. <span class="line"><span style="color:#A31515">                    "--directory"</span><span style="color:#000000">,</span></span>
  1126. <span class="line"><span style="color:#A31515">                    "/home/matthew/Code/zendesk-mcp-server"</span><span style="color:#000000">,</span></span>
  1127. <span class="line"><span style="color:#A31515">                    "run"</span><span style="color:#000000">,</span></span>
  1128. <span class="line"><span style="color:#A31515">                    "zendesk"</span><span style="color:#000000">,</span></span>
  1129. <span class="line"><span style="color:#000000">                ],</span></span>
  1130. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"stdio"</span><span style="color:#000000">,</span></span>
  1131. <span class="line"><span style="color:#000000">            },</span></span>
  1132. <span class="line"><span style="color:#000000">        }</span></span>
  1133. <span class="line"><span style="color:#000000">    )</span></span>
  1134. <span class="line"></span>
  1135. <span class="line"><span style="color:#008000">    # Use an Azure AI model</span></span>
  1136. <span class="line"><span style="color:#000000">    llm = AzureAIChatCompletionsModel(</span></span>
  1137. <span class="line"><span style="color:#001080">        endpoint</span><span style="color:#000000">=os.getenv(</span><span style="color:#A31515">"AZURE_AI_URL"</span><span style="color:#000000">),</span></span>
  1138. <span class="line"><span style="color:#001080">        credential</span><span style="color:#000000">=os.getenv(</span><span style="color:#A31515">"AZURE_AI_APIKEY"</span><span style="color:#000000">),</span></span>
  1139. <span class="line"><span style="color:#001080">        model</span><span style="color:#000000">=</span><span style="color:#A31515">"gpt-5-mini"</span><span style="color:#000000">,</span></span>
  1140. <span class="line"><span style="color:#000000">    )</span></span>
  1141. <span class="line"></span>
  1142. <span class="line"><span style="color:#000000">    tools = </span><span style="color:#AF00DB">await</span><span style="color:#000000"> client.get_tools()</span></span>
  1143. <span class="line"><span style="color:#000000">    tools.append(discard_deployments)</span></span>
  1144. <span class="line"><span style="color:#000000">    tools.append(trim_releases_to_version)</span></span>
  1145. <span class="line"><span style="color:#000000">    tools.append(trim_ticket_descriptions)</span></span>
  1146. <span class="line"></span>
  1147. <span class="line"><span style="color:#000000">    agent = create_react_agent(llm, tools)</span></span>
  1148. <span class="line"><span style="color:#000000">    response = </span><span style="color:#AF00DB">await</span><span style="color:#000000"> agent.ainvoke(</span></span>
  1149. <span class="line"><span style="color:#000000">        {</span></span>
  1150. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: remove_line_padding(</span></span>
  1151. <span class="line"><span style="color:#A31515">                """</span></span>
  1152. <span class="line"><span style="color:#A31515">                In Octopus, get the last 10 releases deployed to the "Production" environment in the "Octopus Server" space.</span></span>
  1153. <span class="line"><span style="color:#A31515">                Get the releases from the deployments.</span></span>
  1154. <span class="line"><span style="color:#A31515">                Trim the details of Octopus releases to their version.</span></span>
  1155. <span class="line"><span style="color:#A31515">                Discard the list of deployments.</span></span>
  1156. <span class="line"><span style="color:#A31515">                In ZenDesk, get the last 100 tickets and their comments.</span></span>
  1157. <span class="line"><span style="color:#A31515">                Trim the description of the ZenDesk tickets.</span></span>
  1158. <span class="line"><span style="color:#A31515">                Create a report summarizing the issues reported by customers in the tickets. </span></span>
  1159. <span class="line"><span style="color:#A31515">                You must only consider tickets that mention the Octopus release versions. </span></span>
  1160. <span class="line"><span style="color:#A31515">                You must only consider support tickets raised by customers. </span></span>
  1161. <span class="line"><span style="color:#A31515">                You must use your best judgment to identify support tickets.</span></span>
  1162. <span class="line"><span style="color:#A31515">                """</span></span>
  1163. <span class="line"><span style="color:#000000">            )</span></span>
  1164. <span class="line"><span style="color:#000000">        }</span></span>
  1165. <span class="line"><span style="color:#000000">    )</span></span>
  1166. <span class="line"><span style="color:#795E26">    print</span><span style="color:#000000">(remove_thinking(response_to_text(response)))</span></span>
  1167. <span class="line"></span>
  1168. <span class="line"></span>
  1169. <span class="line"><span style="color:#000000">asyncio.run(main())</span></span></code></pre>
  1170. <h2 id="alternative-strategies">Alternative strategies</h2>
  1171. <p>LangChain also exposes the <a href="https://docs.langchain.com/oss/python/langchain/agents#pre-model-hook">pre-model hook</a> and <a href="https://docs.langchain.com/oss/python/langchain/agents#post-model-hook">post-model hook</a> to allow you to manipulate the state of the AI agent at various points in the processing of a request. The pre-model hook specifically is designed to support message trimming and summarization as a way to manage context window size.</p>
  1172. <h2 id="conclusion">Conclusion</h2>
  1173. <p>The ability of MCP to define complex, multi-system workflows in natural language is almost magical. By hiding the complexity of API calls behind simple prompts, MCP empowers users to automate tasks that would otherwise require significant custom code.</p>
  1174. <p>However, as you work with larger datasets and more complex workflows, you will encounter limitations around LLM context window sizes, and at this point you will need to implement strategies to manage the context window size.</p>
  1175. <p>Fortunately, LangChain exposes a number of advanced features that provide a deep level of control over the state of the agent, which in turn allows you to manage the context window size effectively.</p>
  1176. <p>This post provided examples of custom tools that manipulated the state of the agent to trim or discard unnecessary data. This allows you to work with data more efficiently and allows your prompts to scale across more systems and larger datasets.</p>
  1177. <p>Happy deployments!</p>]]></content>
  1178.    </entry>
  1179.    <entry>
  1180.      <title>Agentic AI with model context protocol (MCP)</title>
  1181.      <link href="https://octopus.com/blog/agentic-ai-with-mcp" />
  1182.      <id>https://octopus.com/blog/agentic-ai-with-mcp</id>
  1183.      <published>2025-10-01T00:00:00.000Z</published>
  1184.      <updated>2025-10-01T00:00:00.000Z</updated>
  1185.      <summary>Learn how to create a simple AI agent using the Octopus Model Context Protocol (MCP) server to implement an agentic AI system.</summary>
  1186.      <author>
  1187.        <name>Matthew Casperson, Octopus Deploy</name>
  1188.      </author>
  1189.      <content type="html"><![CDATA[<p>If you’ve ever asked a platform like ChatGPT to book your next holiday, order groceries, or schedule a meeting, you’ll understand the potential of large language models (LLMs), and likely have been frustrated by a response like <code>I'm not able to book flights directly</code>.</p>
  1190. <p>By default, LLMs are disconnected from the internet and can’t perform tasks on your behalf. But they feel so tantalizingly close to being your own personal assistant.</p>
  1191. <p>The model context protocol (MCP) is an open standard that enables LLMs to connect to external tools and data sources. When an LLM is connected to an MCP server, it gains the ability to perform actions on your behalf, transforming it from a passive information source into an active agent.</p>
  1192. <p>Agentic AI systems go one step further with the creation of AI agents with specific instructions to perform tasks autonomously, as this quote from <a href="https://www.ibm.com/think/topics/agentic-ai">IBM</a> describes:</p>
  1193. <blockquote>
  1194. <p>Agentic AI is an artificial intelligence system that can accomplish a specific goal with limited supervision. It consists of AI agents—machine learning models that mimic human decision-making to solve problems in real time.</p>
  1195. </blockquote>
  1196. <p>In this post, we’ll explore how to create a simple AI agent connecting the Octopus and GitHub MCP servers to implement an agentic AI system.</p>
  1197. <h2 id="prerequisites">Prerequisites</h2>
  1198. <p>The sample application demonstrated in this post is written in Python. You can download Python from <a href="https://www.python.org/downloads/">python.org</a>.</p>
  1199. <p>We’ll also use <code>uv</code> to manage our virtual environment. You can install <code>uv</code> by following the <a href="https://docs.astral.sh/uv/getting-started/installation/">documentation</a>.</p>
  1200. <h2 id="dependencies">Dependencies</h2>
  1201. <p>Our AI agent will use <a href="https://www.langchain.com/">LangChain</a>, a popular framework for building AI applications.</p>
  1202. <p>Save the following dependencies to a <code>requirements.txt</code> file:</p>
  1203. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>langchain-mcp-adapters</span></span>
  1204. <span class="line"><span>langchain-ollama</span></span>
  1205. <span class="line"><span>langchain-azure-ai</span></span>
  1206. <span class="line"><span>langgraph</span></span></code></pre>
  1207. <p>Create a virtual environment and install the dependencies:</p>
  1208. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">uv</span><span style="color:#A31515"> venv</span></span>
  1209. <span class="line"><span style="color:#008000"># This instruction is provided by the previous command and is specific to your OS</span></span>
  1210. <span class="line"><span style="color:#795E26">source</span><span style="color:#A31515"> .venv/bin/activate</span></span>
  1211. <span class="line"><span style="color:#795E26">uv</span><span style="color:#A31515"> pip</span><span style="color:#A31515"> install</span><span style="color:#0000FF"> -r</span><span style="color:#A31515"> requirements.txt</span></span></code></pre>
  1212. <h2 id="text-processing-functions">Text processing functions</h2>
  1213. <p>A big part of working with LLMs is cleaning and processing text. AI agents sit at the intersection between LLMs consuming and generating natural language and imperative code that requires predictable inputs and outputs. In practice, this means AI agents spend a lot of time manipulating strings.</p>
  1214. <p>We start with a function to trim the leading and trailing whitespace from each line in a block of text:</p>
  1215. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> remove_line_padding</span><span style="color:#000000">(</span><span style="color:#001080">text</span><span style="color:#000000">):</span></span>
  1216. <span class="line"><span style="color:#A31515">    """</span></span>
  1217. <span class="line"><span style="color:#A31515"> Remove leading and trailing whitespace from each line in the text.</span></span>
  1218. <span class="line"><span style="color:#A31515"> :param text: The text to process.</span></span>
  1219. <span class="line"><span style="color:#A31515"> :return: The text with leading and trailing whitespace removed from each line.</span></span>
  1220. <span class="line"><span style="color:#A31515"> """</span></span>
  1221. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#A31515"> "</span><span style="color:#EE0000">\n</span><span style="color:#A31515">"</span><span style="color:#000000">.join(line.strip() </span><span style="color:#AF00DB">for</span><span style="color:#000000"> line </span><span style="color:#AF00DB">in</span><span style="color:#000000"> text.splitlines() </span><span style="color:#AF00DB">if</span><span style="color:#000000"> line.strip())</span></span></code></pre>
  1222. <p>Next, we have a function to remove <code>&#x3C;think>...&#x3C;/think></code> tags and their content from the text. The <code>&#x3C;think></code> tag is an informal convention used by reasoning LLMs to wrap the model’s internal reasoning steps.</p>
  1223. <p>OpenAI describes reasoning models <a href="https://platform.openai.com/docs/guides/reasoning">like this</a>:</p>
  1224. <blockquote>
  1225. <p>Reasoning models think before they answer, producing a long internal chain of thought before responding to the user.</p>
  1226. </blockquote>
  1227. <p>We typically don’t want to display the internal chain of thought in the final output, so we remove it:</p>
  1228. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> remove_thinking</span><span style="color:#000000">(</span><span style="color:#001080">text</span><span style="color:#000000">):</span></span>
  1229. <span class="line"><span style="color:#A31515">    """</span></span>
  1230. <span class="line"><span style="color:#A31515"> Remove &#x3C;think>...&#x3C;/think> tags and their content from the text.</span></span>
  1231. <span class="line"><span style="color:#A31515"> :param text: The text to process.</span></span>
  1232. <span class="line"><span style="color:#A31515"> :return: The text with &#x3C;think>...&#x3C;/think> tags and their content removed.</span></span>
  1233. <span class="line"><span style="color:#A31515"> """</span></span>
  1234. <span class="line"><span style="color:#000000"> stripped_text = text.strip()</span></span>
  1235. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> stripped_text.startswith(</span><span style="color:#A31515">"&#x3C;think>"</span><span style="color:#000000">) </span><span style="color:#0000FF">and</span><span style="color:#A31515"> "&#x3C;/think>"</span><span style="color:#0000FF"> in</span><span style="color:#000000"> stripped_text:</span></span>
  1236. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> re.sub(</span><span style="color:#0000FF">r</span><span style="color:#811F3F">"&#x3C;think>.</span><span style="color:#000000">*?</span><span style="color:#811F3F">&#x3C;/think>"</span><span style="color:#000000">, </span><span style="color:#A31515">""</span><span style="color:#000000">, stripped_text, </span><span style="color:#001080">flags</span><span style="color:#000000">=re.DOTALL)</span></span>
  1237. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> stripped_text</span></span></code></pre>
  1238. <h2 id="extracting-responses">Extracting responses</h2>
  1239. <p>LangChain provides a common abstraction over many AI platforms. These platforms will often have their own specific APIs. Consequently, LangChain functions frequently return generic dictionary objects containing the response to API calls. It is our responsibility to access the required data from these dictionaries.</p>
  1240. <p>We’ll be using the Azure AI Foundry service for this demo, and making use of the <a href="https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/chatgpt">Chat completions API</a>.</p>
  1241. <p>The response from the Chat completion API is a dictionary with a <code>messages</code> key containing a list of message objects. Here we extract the list of messages and return the content of the last message, or an empty string if no messages are present:</p>
  1242. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> response_to_text</span><span style="color:#000000">(</span><span style="color:#001080">response</span><span style="color:#000000">):</span></span>
  1243. <span class="line"><span style="color:#A31515">    """</span></span>
  1244. <span class="line"><span style="color:#A31515">    Extract the content from the last message in the response.</span></span>
  1245. <span class="line"><span style="color:#A31515">    :param response: The response dictionary containing messages.</span></span>
  1246. <span class="line"><span style="color:#A31515">    :return: The content of the last message, or an empty string if no messages are present.</span></span>
  1247. <span class="line"><span style="color:#A31515">    """</span></span>
  1248. <span class="line"><span style="color:#000000">    messages = response.get(</span><span style="color:#A31515">"messages"</span><span style="color:#000000">, [])</span></span>
  1249. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#0000FF"> not</span><span style="color:#000000"> messages </span><span style="color:#0000FF">or</span><span style="color:#795E26"> len</span><span style="color:#000000">(messages) == </span><span style="color:#098658">0</span><span style="color:#000000">:</span></span>
  1250. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#A31515"> ""</span></span>
  1251. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> messages.pop().content</span></span></code></pre>
  1252. <h2 id="setting-up-mcp-servers">Setting up MCP servers</h2>
  1253. <p>We can now start building the core functionality of our AI agent.</p>
  1254. <p>Our agent will use two MCP servers: the Octopus MCP server to interact with an Octopus instance, and the GitHub MCP server to interact with GitHub repositories.</p>
  1255. <p>These MCP servers are represented by the <code>MultiServerMCPClient</code> class, which is a client for connecting to multiple MCP servers:</p>
  1256. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#0000FF">async</span><span style="color:#0000FF"> def</span><span style="color:#795E26"> main</span><span style="color:#000000">():</span></span>
  1257. <span class="line"><span style="color:#A31515">    """</span></span>
  1258. <span class="line"><span style="color:#A31515">    The entrypoint to our AI agent.</span></span>
  1259. <span class="line"><span style="color:#A31515">    """</span></span>
  1260. <span class="line"><span style="color:#000000">    client = MultiServerMCPClient(</span></span>
  1261. <span class="line"><span style="color:#000000">        {</span></span>
  1262. <span class="line"><span style="color:#A31515">            "octopus"</span><span style="color:#000000">: {</span></span>
  1263. <span class="line"><span style="color:#A31515">                "command"</span><span style="color:#000000">: </span><span style="color:#A31515">"npx"</span><span style="color:#000000">,</span></span>
  1264. <span class="line"><span style="color:#A31515">                "args"</span><span style="color:#000000">: [</span></span>
  1265. <span class="line"><span style="color:#A31515">                    "-y"</span><span style="color:#000000">,</span></span>
  1266. <span class="line"><span style="color:#A31515">                    "@octopusdeploy/mcp-server"</span><span style="color:#000000">,</span></span>
  1267. <span class="line"><span style="color:#A31515">                    "--api-key"</span><span style="color:#000000">,</span></span>
  1268. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"OCTOPUS_CLI_API_KEY"</span><span style="color:#000000">),</span></span>
  1269. <span class="line"><span style="color:#A31515">                    "--server-url"</span><span style="color:#000000">,</span></span>
  1270. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"OCTOPUS_CLI_SERVER"</span><span style="color:#000000">),</span></span>
  1271. <span class="line"><span style="color:#000000">                ],</span></span>
  1272. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"stdio"</span><span style="color:#000000">,</span></span>
  1273. <span class="line"><span style="color:#000000">            },</span></span>
  1274. <span class="line"><span style="color:#A31515">            "github"</span><span style="color:#000000">: {</span></span>
  1275. <span class="line"><span style="color:#A31515">                "url"</span><span style="color:#000000">: </span><span style="color:#A31515">"https://api.githubcopilot.com/mcp/"</span><span style="color:#000000">,</span></span>
  1276. <span class="line"><span style="color:#A31515">                "headers"</span><span style="color:#000000">: {</span><span style="color:#A31515">"Authorization"</span><span style="color:#000000">: </span><span style="color:#0000FF">f</span><span style="color:#A31515">"Bearer </span><span style="color:#0000FF">{</span><span style="color:#000000">os.getenv(</span><span style="color:#A31515">'GITHUB_PAT'</span><span style="color:#000000">)</span><span style="color:#0000FF">}</span><span style="color:#A31515">"</span><span style="color:#000000">},</span></span>
  1277. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"streamable_http"</span><span style="color:#000000">,</span></span>
  1278. <span class="line"><span style="color:#000000">            },</span></span>
  1279. <span class="line"><span style="color:#000000">        }</span></span>
  1280. <span class="line"><span style="color:#000000">    )</span></span></code></pre>
  1281. <p>We then create an instance of the <code>AzureAIChatCompletionsModel</code> class to allow us to interact with the Azure AI service. This class is responsible for building the Azure AI API requests and processing the responses:</p>
  1282. <div class="hint"><p>LangChain provides a variety of classes to interact with different AI platforms. The ability to swap out one AI platform for another is a key benefit of frameworks like LangChain.</p></div>
  1283. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#008000">    # Use an Azure AI model</span></span>
  1284. <span class="line"><span style="color:#000000">    llm = AzureAIChatCompletionsModel(</span></span>
  1285. <span class="line"><span style="color:#001080">        endpoint</span><span style="color:#000000">=os.getenv(</span><span style="color:#A31515">"AZURE_AI_URL"</span><span style="color:#000000">),</span></span>
  1286. <span class="line"><span style="color:#001080">        credential</span><span style="color:#000000">=os.getenv(</span><span style="color:#A31515">"AZURE_AI_APIKEY"</span><span style="color:#000000">),</span></span>
  1287. <span class="line"><span style="color:#001080">        model</span><span style="color:#000000">=</span><span style="color:#A31515">"gpt-5-mini"</span><span style="color:#000000">,</span></span>
  1288. <span class="line"><span style="color:#000000">    )</span></span></code></pre>
  1289. <p>The <code>MultiServerMCPClient</code> provides access to the tools exposed by each MCP server:</p>
  1290. <div class="hint"><p>Tools are typically self-describing functions that an LLM can call. It is possible to <a href="https://python.langchain.com/docs/concepts/tools/">create tools manually</a>. However, the benefit of using MCP servers is that they expose tools in a consistent way that any MCP client can consume.</p></div>
  1291. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">tools = </span><span style="color:#AF00DB">await</span><span style="color:#000000"> client.get_tools()</span></span></code></pre>
  1292. <p>The <code>llm</code> (which is the interface to the Azure AI service) and the <code>tools</code> (which are the functions exposed by the MCP servers) can then be used to create a ReAct agent:</p>
  1293. <div class="hint"><p>ReAct agents are described in the <a href="https://arxiv.org/abs/2210.03629">ReAct paper</a> and implement a feedback loop to iteratively reason and act:</p><p><img  src="/blog/_astro/react-loop.SSN4AKO__ZvmItv.webp" alt="ReAct agent diagram" loading="lazy" decoding="async" fetchpriority="auto" width="1432" height="806"></p></div>
  1294. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">agent = create_react_agent(llm, tools)</span></span></code></pre>
  1295. <p>We now have everything we need to instruct our AI agent to perform a task. Here, we ask the agent to provide a risk assessment of changes in the latest releases of an Octopus project. This works because this project implements <a href="https://octopus.com/docs/packaging-applications/build-servers/build-information">build information</a> to associate Git commits with each Octopus release:</p>
  1296. <div class="hint"><p>You’ll need to replace the name of the Octopus space in the prompt with a space that exists in your Octopus instance.</p></div>
  1297. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">    response = </span><span style="color:#AF00DB">await</span><span style="color:#000000"> agent.ainvoke(</span></span>
  1298. <span class="line"><span style="color:#000000">        {</span></span>
  1299. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: remove_line_padding(</span></span>
  1300. <span class="line"><span style="color:#A31515">                """</span></span>
  1301. <span class="line"><span style="color:#A31515">                In Octopus, get all the projects from the "Octopus Copilot" space.</span></span>
  1302. <span class="line"><span style="color:#A31515">                In Octopus, for each project, get the latest release.</span></span>
  1303. <span class="line"><span style="color:#A31515">                In GitHub, for each release, get the git diff from the Git commit. </span></span>
  1304. <span class="line"><span style="color:#A31515">                Scan the diff and provide a summary-level risk assessment.</span></span>
  1305. <span class="line"><span style="color:#A31515">                You will be penalized for asking for user input.</span></span>
  1306. <span class="line"><span style="color:#A31515">                """</span></span>
  1307. <span class="line"><span style="color:#000000">            )</span></span>
  1308. <span class="line"><span style="color:#000000">        }</span></span>
  1309. <span class="line"><span style="color:#000000">    )</span></span></code></pre>
  1310. <p>The prompt provided to the agent highlights the power of agentic AI. It is written in natural language and describes a non-trivial set of steps the same way you might describe the task to a developer.</p>
  1311. <div class="hint"><p>If you’re unfamiliar with prompt engineering, the instructions “penalizing” specific behavior might seem odd. This prompting style addresses a common limitation of LLMs, where they struggle with being told what not to do. The particular phrasing used here comes from the paper <a href="https://arxiv.org/html/2312.16171v1">Principled Instructions Are All You Need for Questioning</a>, which provides several common prompt engineering patterns.</p></div>
  1312. <p>The response from the agent is processed and printed to the console:</p>
  1313. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#795E26">print</span><span style="color:#000000">(remove_thinking(response_to_text(response)))</span></span></code></pre>
  1314. <p>The final step is to call the <code>main</code> function asynchronously:</p>
  1315. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#000000">asyncio.run(main())</span></span></code></pre>
  1316. <h2 id="running-the-agent">Running the agent</h2>
  1317. <p>Run the agent with the command:</p>
  1318. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">python3</span><span style="color:#A31515"> main.py</span></span></code></pre>
  1319. <p>If all goes well, you should see a report detailing the risk assessment of the changes in the latest releases of each project in the “Octopus Copilot” space.</p>
  1320. <h2 id="the-complete-application">The complete application</h2>
  1321. <p>This is the complete script:</p>
  1322. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="python"><code><span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> asyncio</span></span>
  1323. <span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> os</span></span>
  1324. <span class="line"><span style="color:#AF00DB">import</span><span style="color:#000000"> re</span></span>
  1325. <span class="line"></span>
  1326. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langchain_mcp_adapters.client </span><span style="color:#AF00DB">import</span><span style="color:#000000"> MultiServerMCPClient</span></span>
  1327. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langchain_azure_ai.chat_models </span><span style="color:#AF00DB">import</span><span style="color:#000000"> AzureAIChatCompletionsModel</span></span>
  1328. <span class="line"><span style="color:#AF00DB">from</span><span style="color:#000000"> langgraph.prebuilt </span><span style="color:#AF00DB">import</span><span style="color:#000000"> create_react_agent</span></span>
  1329. <span class="line"></span>
  1330. <span class="line"></span>
  1331. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> remove_line_padding</span><span style="color:#000000">(</span><span style="color:#001080">text</span><span style="color:#000000">):</span></span>
  1332. <span class="line"><span style="color:#A31515">    """</span></span>
  1333. <span class="line"><span style="color:#A31515">    Remove leading and trailing whitespace from each line in the text.</span></span>
  1334. <span class="line"><span style="color:#A31515">    :param text: The text to process.</span></span>
  1335. <span class="line"><span style="color:#A31515">    :return: The text with leading and trailing whitespace removed from each line.</span></span>
  1336. <span class="line"><span style="color:#A31515">    """</span></span>
  1337. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#A31515"> "</span><span style="color:#EE0000">\n</span><span style="color:#A31515">"</span><span style="color:#000000">.join(line.strip() </span><span style="color:#AF00DB">for</span><span style="color:#000000"> line </span><span style="color:#AF00DB">in</span><span style="color:#000000"> text.splitlines() </span><span style="color:#AF00DB">if</span><span style="color:#000000"> line.strip())</span></span>
  1338. <span class="line"></span>
  1339. <span class="line"></span>
  1340. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> remove_thinking</span><span style="color:#000000">(</span><span style="color:#001080">text</span><span style="color:#000000">):</span></span>
  1341. <span class="line"><span style="color:#A31515">    """</span></span>
  1342. <span class="line"><span style="color:#A31515">    Remove &#x3C;think>...&#x3C;/think> tags and their content from the text.</span></span>
  1343. <span class="line"><span style="color:#A31515">    :param text: The text to process.</span></span>
  1344. <span class="line"><span style="color:#A31515">    :return: The text with &#x3C;think>...&#x3C;/think> tags and their content removed.</span></span>
  1345. <span class="line"><span style="color:#A31515">    """</span></span>
  1346. <span class="line"><span style="color:#000000">    stripped_text = text.strip()</span></span>
  1347. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#000000"> stripped_text.startswith(</span><span style="color:#A31515">"&#x3C;think>"</span><span style="color:#000000">) </span><span style="color:#0000FF">and</span><span style="color:#A31515"> "&#x3C;/think>"</span><span style="color:#0000FF"> in</span><span style="color:#000000"> stripped_text:</span></span>
  1348. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#000000"> re.sub(</span><span style="color:#0000FF">r</span><span style="color:#811F3F">"&#x3C;think>.</span><span style="color:#000000">*?</span><span style="color:#811F3F">&#x3C;/think>"</span><span style="color:#000000">, </span><span style="color:#A31515">""</span><span style="color:#000000">, stripped_text, </span><span style="color:#001080">flags</span><span style="color:#000000">=re.DOTALL)</span></span>
  1349. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> stripped_text</span></span>
  1350. <span class="line"></span>
  1351. <span class="line"></span>
  1352. <span class="line"><span style="color:#0000FF">def</span><span style="color:#795E26"> response_to_text</span><span style="color:#000000">(</span><span style="color:#001080">response</span><span style="color:#000000">):</span></span>
  1353. <span class="line"><span style="color:#A31515">    """</span></span>
  1354. <span class="line"><span style="color:#A31515">    Extract the content from the last message in the response.</span></span>
  1355. <span class="line"><span style="color:#A31515">    :param response: The response dictionary containing messages.</span></span>
  1356. <span class="line"><span style="color:#A31515">    :return: The content of the last message, or an empty string if no messages are present.</span></span>
  1357. <span class="line"><span style="color:#A31515">    """</span></span>
  1358. <span class="line"><span style="color:#000000">    messages = response.get(</span><span style="color:#A31515">"messages"</span><span style="color:#000000">, [])</span></span>
  1359. <span class="line"><span style="color:#AF00DB">    if</span><span style="color:#0000FF"> not</span><span style="color:#000000"> messages </span><span style="color:#0000FF">or</span><span style="color:#795E26"> len</span><span style="color:#000000">(messages) == </span><span style="color:#098658">0</span><span style="color:#000000">:</span></span>
  1360. <span class="line"><span style="color:#AF00DB">        return</span><span style="color:#A31515"> ""</span></span>
  1361. <span class="line"><span style="color:#AF00DB">    return</span><span style="color:#000000"> messages.pop().content</span></span>
  1362. <span class="line"></span>
  1363. <span class="line"></span>
  1364. <span class="line"><span style="color:#0000FF">async</span><span style="color:#0000FF"> def</span><span style="color:#795E26"> main</span><span style="color:#000000">():</span></span>
  1365. <span class="line"><span style="color:#A31515">    """</span></span>
  1366. <span class="line"><span style="color:#A31515">    The entrypoint to our AI agent.</span></span>
  1367. <span class="line"><span style="color:#A31515">    """</span></span>
  1368. <span class="line"><span style="color:#000000">    client = MultiServerMCPClient(</span></span>
  1369. <span class="line"><span style="color:#000000">        {</span></span>
  1370. <span class="line"><span style="color:#A31515">            "octopus"</span><span style="color:#000000">: {</span></span>
  1371. <span class="line"><span style="color:#A31515">                "command"</span><span style="color:#000000">: </span><span style="color:#A31515">"npx"</span><span style="color:#000000">,</span></span>
  1372. <span class="line"><span style="color:#A31515">                "args"</span><span style="color:#000000">: [</span></span>
  1373. <span class="line"><span style="color:#A31515">                    "-y"</span><span style="color:#000000">,</span></span>
  1374. <span class="line"><span style="color:#A31515">                    "@octopusdeploy/mcp-server"</span><span style="color:#000000">,</span></span>
  1375. <span class="line"><span style="color:#A31515">                    "--api-key"</span><span style="color:#000000">,</span></span>
  1376. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"OCTOPUS_CLI_API_KEY"</span><span style="color:#000000">),</span></span>
  1377. <span class="line"><span style="color:#A31515">                    "--server-url"</span><span style="color:#000000">,</span></span>
  1378. <span class="line"><span style="color:#000000">                    os.getenv(</span><span style="color:#A31515">"OCTOPUS_CLI_SERVER"</span><span style="color:#000000">),</span></span>
  1379. <span class="line"><span style="color:#000000">                ],</span></span>
  1380. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"stdio"</span><span style="color:#000000">,</span></span>
  1381. <span class="line"><span style="color:#000000">            },</span></span>
  1382. <span class="line"><span style="color:#A31515">            "github"</span><span style="color:#000000">: {</span></span>
  1383. <span class="line"><span style="color:#A31515">                "url"</span><span style="color:#000000">: </span><span style="color:#A31515">"https://api.githubcopilot.com/mcp/"</span><span style="color:#000000">,</span></span>
  1384. <span class="line"><span style="color:#A31515">                "headers"</span><span style="color:#000000">: {</span><span style="color:#A31515">"Authorization"</span><span style="color:#000000">: </span><span style="color:#0000FF">f</span><span style="color:#A31515">"Bearer </span><span style="color:#0000FF">{</span><span style="color:#000000">os.getenv(</span><span style="color:#A31515">'GITHUB_PAT'</span><span style="color:#000000">)</span><span style="color:#0000FF">}</span><span style="color:#A31515">"</span><span style="color:#000000">},</span></span>
  1385. <span class="line"><span style="color:#A31515">                "transport"</span><span style="color:#000000">: </span><span style="color:#A31515">"streamable_http"</span><span style="color:#000000">,</span></span>
  1386. <span class="line"><span style="color:#000000">            },</span></span>
  1387. <span class="line"><span style="color:#000000">        }</span></span>
  1388. <span class="line"><span style="color:#000000">    )</span></span>
  1389. <span class="line"></span>
  1390. <span class="line"><span style="color:#008000">    # Use an Azure AI model</span></span>
  1391. <span class="line"><span style="color:#000000">    llm = AzureAIChatCompletionsModel(</span></span>
  1392. <span class="line"><span style="color:#001080">        endpoint</span><span style="color:#000000">=os.getenv(</span><span style="color:#A31515">"AZURE_AI_URL"</span><span style="color:#000000">),</span></span>
  1393. <span class="line"><span style="color:#001080">        credential</span><span style="color:#000000">=os.getenv(</span><span style="color:#A31515">"AZURE_AI_APIKEY"</span><span style="color:#000000">),</span></span>
  1394. <span class="line"><span style="color:#001080">        model</span><span style="color:#000000">=</span><span style="color:#A31515">"gpt-5-mini"</span><span style="color:#000000">,</span></span>
  1395. <span class="line"><span style="color:#000000">    )</span></span>
  1396. <span class="line"></span>
  1397. <span class="line"><span style="color:#000000">    tools = </span><span style="color:#AF00DB">await</span><span style="color:#000000"> client.get_tools()</span></span>
  1398. <span class="line"><span style="color:#000000">    agent = create_react_agent(llm, tools)</span></span>
  1399. <span class="line"><span style="color:#000000">    response = </span><span style="color:#AF00DB">await</span><span style="color:#000000"> agent.ainvoke(</span></span>
  1400. <span class="line"><span style="color:#000000">        {</span></span>
  1401. <span class="line"><span style="color:#A31515">            "messages"</span><span style="color:#000000">: remove_line_padding(</span></span>
  1402. <span class="line"><span style="color:#A31515">                """</span></span>
  1403. <span class="line"><span style="color:#A31515">                In Octopus, get all the projects from the "Octopus Copilot" space.</span></span>
  1404. <span class="line"><span style="color:#A31515">                In Octopus, for each project, get the latest release.</span></span>
  1405. <span class="line"><span style="color:#A31515">                In GitHub, for each release, get the git diff from the GitHub Commit. </span></span>
  1406. <span class="line"><span style="color:#A31515">                Scan the diff and provide a summary-level risk assessment.</span></span>
  1407. <span class="line"><span style="color:#A31515">                You will be penalized for asking for user input.</span></span>
  1408. <span class="line"><span style="color:#A31515">                """</span></span>
  1409. <span class="line"><span style="color:#000000">            )</span></span>
  1410. <span class="line"><span style="color:#000000">        }</span></span>
  1411. <span class="line"><span style="color:#000000">    )</span></span>
  1412. <span class="line"><span style="color:#795E26">    print</span><span style="color:#000000">(remove_thinking(response_to_text(response)))</span></span>
  1413. <span class="line"></span>
  1414. <span class="line"></span>
  1415. <span class="line"><span style="color:#000000">asyncio.run(main())</span></span></code></pre>
  1416. <h2 id="challenges-with-agentic-ai">Challenges with agentic AI</h2>
  1417. <p>Describing complex tasks in natural language is a powerful capability, but it can mask some of the challenges with agentic AI.</p>
  1418. <p>The ability to correctly execute the instructions depends a great deal on the model used. We used the <code>gpt-5-mini</code> model from Azure AI Foundry in this example. I selected this model after some trial and error with different models that failed to select the correct tools to complete the task. For instance, GPT 4.1 continually attempted to load Octopus project details from GitHub. Other models can produce very different results for exactly the same code and prompt, and it is often unclear why.</p>
  1419. <p>The quality of the prompt is also important. LLMs are not universally advanced enough today to correctly interpret all prompts. Prompt engineering is still required to allow AI agents to perform complex tasks.</p>
  1420. <h2 id="next-steps">Next steps</h2>
  1421. <p>This example provides the ability to generate one-off reports. A more complete solution would run as a long-lived process responding to events such as those generated by <a href="https://octopus.com/docs/administration/managing-infrastructure/subscriptions">Octopus subscriptions</a> to generate reports automatically when new releases are created. You’d also likely post the results to an email MCP server or an MCP server for one of the many chat platforms available today.</p>
  1422. <h2 id="conclusion">Conclusion</h2>
  1423. <p>It is early days for agentic AI, but the potential is clear. Agentic AI may finally deliver on the promise of low/no-code solutions that allow non-developers to automate complex tasks.</p>
  1424. <p>Agentic AI is not without its challenges. You still need a good understanding of the capabilities and limitations of LLMs, and prompt engineering is still required to get the best results.</p>
  1425. <p>However, once you strip away all the boilerplate code to interact with multiple MCP servers, it is possible to orchestrate complex tasks with just a few lines of code and a well-crafted prompt.</p>
  1426. <p>Learn more about the Octopus MCP server in our <a href="https://octopus.com/docs/octopus-ai/mcp">documentation</a>.</p>
  1427. <p>Happy deployments!</p>]]></content>
  1428.    </entry>
  1429.    <entry>
  1430.      <title>The power of patterns: How technology helped us see what matters</title>
  1431.      <link href="https://octopus.com/blog/building-our-jira-journey-in-people" />
  1432.      <id>https://octopus.com/blog/building-our-jira-journey-in-people</id>
  1433.      <published>2025-10-01T00:00:00.000Z</published>
  1434.      <updated>2025-10-01T00:00:00.000Z</updated>
  1435.      <summary>How we integrated Jira into our people team at Octopus Deploy</summary>
  1436.      <author>
  1437.        <name>Mary Lee, Octopus Deploy</name>
  1438.      </author>
  1439.      <content type="html"><![CDATA[<p>When I joined Octopus in January 2023, the People team looked very different from what it is today. Back then, most of our work centered on ongoing projects and answering employee questions through Slack or email. We were like engines keeping the ship moving, focused on making sure employees were supported and cared for.</p>
  1440. <p>The team was still relatively new. Only a few years in, our priority was to build trust and move away from being seen as the stereotypical “HR” group. We were small and lean, most of the time we were so absorbed in the day-to-day that we rarely paused to think strategically or dive into data.</p>
  1441. <p>Looking back now, it is surprising that we waited so long to implement Jira. IT had already been using it successfully, and although the People team had talked about it for some time, it never became a priority. Ultimately, I took ownership and drove this forward.What started as a small idea became one of the most exciting and rewarding projects I have had the chance to lead.</p>
  1442. <h2 id="from-idea-to-implementation">From idea to implementation</h2>
  1443. <p>When we began, we honestly did not know what to expect. The goal was simple: build it, launch it, and see what happens. What followed far exceeded our expectations.</p>
  1444. <p>We knew we had to design Jira in a way that worked with Octopus’s culture. Slack is our primary communication tool, and most employee inquiries came through Slack messages. Requests from third parties, such as employment verifications, arrived in our inbox. With that in mind, we needed Jira to integrate seamlessly with Slack, so employees could continue asking questions in the same way they always had.</p>
  1445. <p>On the Operations side, we automated our People inbox directly into Jira and set up Slack workflows where adding specific emojis to messages automatically created tickets. This helped us avoid missing requests and improved our responsiveness.</p>
  1446. <p>On the HRBP side, Jira changed the way we work. Previously, we operated in silos, using Google Docs to manage performance cases, reorganizations, and other sensitive processes. With Jira, HRBPs now have a centralized place to document and track cases. This makes our work more consistent and allows any team member to step in seamlessly if needed.</p>
  1447. <h2 id="what-changed">What changed?</h2>
  1448. <p>For both Ops and HRBPs, the most transformative part of Jira has been reporting. What did not surprise us at first was the sheer number of tickets coming in. We already knew we were a lean team working across many priorities, but seeing the reality in numbers gave us a new appreciation for the scale of what we handle and gratitude for the team behind it.</p>
  1449. <p>What went beyond the numbers, however, was far more revealing. With the addition of AI powered reporting, we could move past raw volume and see deeper insights. The data showed us where questions and issues were coming from across departments, roles, and regions, which exposed weak points we had not clearly seen before. Instead of assumptions, we had evidence of where employees needed the most support.</p>
  1450. <p>The AI highlighted recurring themes, surfaced gaps in our processes, and even suggested where our attention should shift. It became clear for us that onboarding, offboarding, and employee recognition required more investment than we had realized. These patterns were not obvious through traditional reporting, but the AI made them visible.</p>
  1451. <p>This combination of real time data and AI driven analysis has turned reporting into a strategic tool. It not only helps us prioritize projects with more confidence and precision, it also guides us toward targeted improvements that will have the greatest impact on employees.</p>
  1452. <h2 id="whats-next">What’s next?</h2>
  1453. <p>We are only just beginning. Our next steps include expanding Jira automations, creating new workflows to support our Work from Anywhere policy, and streamlining processes around benefits and other key employee touch points.</p>
  1454. <p>Implementing Jira has been fun and energizing, but more than that, it has been transformative. It has given us structure, clarity, and consistency. It has helped us evolve from a team focused on tasks to one that is strategic, data informed, and scalable.</p>
  1455. <p>Happy deployments!</p>]]></content>
  1456.    </entry>
  1457.    <entry>
  1458.      <title>Introducing Argo CD in Octopus</title>
  1459.      <link href="https://octopus.com/blog/argo-cd-in-octopus" />
  1460.      <id>https://octopus.com/blog/argo-cd-in-octopus</id>
  1461.      <published>2025-09-30T00:00:00.000Z</published>
  1462.      <updated>2025-09-30T00:00:00.000Z</updated>
  1463.      <summary>Argo CD is now integrated into Octopus to simplify your Kubernetes deployments</summary>
  1464.      <author>
  1465.        <name>Robert Erez, Octopus Deploy</name>
  1466.      </author>
  1467.      <content type="html"><![CDATA[<p>Argo CD is the leading GitOps solution for Kubernetes. It excels at syncing manifests to clusters and gives engineers a powerful UI to verify and troubleshoot deployments.</p>
  1468. <p>However Argo CD was never designed to handle the full software delivery cycle. While platform teams can use it for cluster bootstrapping and configuration management, most real-world delivery pipelines require multiple environments, tests, security and compliance checks, change management, and many other steps. Today, many teams work around this gap by stitching together tools like Jenkins or GitHub Actions and Argo CD with custom scripting. This quickly becomes brittle. As the number of Argo CD instances, Applications, and clusters grows, so does the maintenance overhead. Engineers also lose centralized visibility and must jump between different Argo CD dashboards to understand deployment status.</p>
  1469. <p>Octopus solves deployment challenges at scale. As a complete CD platform, it provides guardrails, orchestration, and visibility. We realised that combining Argo CD and Octopus would allow our users to get a solution that can scale and has both great CD and GitOps capabilities. Now, with built-in support for Argo CD, teams can eliminate custom glue code and combine the strengths of both approaches out of the box.</p>
  1470. <p>This post introduces Argo CD in Octopus and shows how to get started.</p>
  1471. <p>Argo CD in Octopus is currently in <strong>Early Access</strong>, rolling out to Octopus Cloud in early October.</p>
  1472. <h2 id="what-is-argo-cd">What is Argo CD?</h2>
  1473. <p>Argo CD is a declarative, GitOps-based tool for deploying Kubernetes manifests. Following the <a href="https://opengitops.dev/">GitOps principles</a>, it continuously monitors Git repositories for changes and ensures that the cluster state matches the desired state described in those repositories.</p>
  1474. <h3 id="why-is-it-good">Why is it good?</h3>
  1475. <p>Argo CD makes Kubernetes delivery predictable and transparent. Configuration is stored as code in Git, so every deployment is versioned, auditable, and easy to roll back. Engineers can rely on Git history to know exactly what is running in a cluster. Developers don’t need direct cluster access—they simply commit changes using familiar tools and workflows, and Argo CD applies them.</p>
  1476. <p>A major advantage of Argo CD over many other GitOps tools is its built-in UI. The dashboard gives teams real-time visibility into application health and sync status, reducing the need for extra tooling to verify or troubleshoot deployments.</p>
  1477. <h3 id="argo-cds-limitations">Argo CD’s limitations</h3>
  1478. <p>It’s important to be clear about what Argo CD was designed to do and what it was not. Argo CD excels at synchronizing manifests to Kubernetes, but it is not an end-to-end Continuous Delivery solution. In practice, teams often combine it with CI/CD tools to orchestrate broader delivery pipelines. Many of Argo CD’s limitations show up when it is stretched beyond its intended scope.</p>
  1479. <h4 id="environment-promotions">Environment promotions</h4>
  1480. <p>Successful software delivery involves more than updating a single cluster. Changes typically progress through a series of environments, with tests and approvals at each stage. Some teams try to model this workflow directly in Git, automating Git updates with scripts, but Git was not built for environment promotion management. As a result, promotion logic can become brittle and hard to maintain, especially at scale.</p>
  1481. <p><strong>Octopus solves this</strong> with advanced <a href="https://octopus.com/docs/releases/lifecycles">Environment Lifecycle modelling</a>, providing enforced guardrails that reduce the risk of premature production changes.</p>
  1482. <h4 id="complex-orchestrations">Complex orchestrations</h4>
  1483. <p>Argo CD’s job is to sync Kubernetes manifests. Real-world deployments often require much more — database migrations, external cloud resource updates, integration tests, compliance checks, notifications, and monitoring. Teams can add scripts or tools to cover these steps, but doing so outside of Argo CD creates fragmentation. This disjointed approach is error-prone and makes it difficult to reason about the state of the system as a whole.</p>
  1484. <p>Enterprises also deploy more than just Kubernetes workloads. VMs, serverless functions, and SaaS integrations are common, but Argo CD alone cannot unify these deployment targets.</p>
  1485. <p><strong>Octopus solves this</strong> through its broad library of <a href="https://octopus.com/docs/projects/steps">Steps</a> and <a href="https://octopus.com/docs/infrastructure/deployment-targets">Deployment Targets</a>, covering Kubernetes, Argo CD, and much more.</p>
  1486. <h4 id="rich-rbac-controls">Rich RBAC controls</h4>
  1487. <p>While Argo CD integrates with Kubernetes RBAC, it lacks fine-grained role-based access control for approvals, gates, and deployment responsibilities across environments. For organizations in regulated industries, this can make it difficult to enforce compliance requirements or maintain clear audit trails.</p>
  1488. <p>The audit trail that can be found in git history might provide some record after the fact, but git was never built with fine-grained permissions in mind, particularly when coupled with the complex access configurations required for multi-team environment promotions or more advanced compliance and governance requirements.</p>
  1489. <p><strong>Octopus solves this</strong> through the customizable <a href="https://octopus.com/docs/best-practices/octopus-administration/users-roles-and-teams">RBAC controls</a>, external <a href="https://octopus.com/docs/approvals">ITSM integrations</a> and (coming soon) <a href="https://octopus.com/docs/platform-hub/policies">Policies</a>.</p>
  1490. <h4 id="multi-cluster-management">Multi-cluster management</h4>
  1491. <p>Argo CD supports several different installation topologies for managing multiple clusters, <a href="https://octopus.com/docs/platform-hub/policies">each with their pros and their cons</a>.</p>
  1492. <p>The “hub and spoke” model allows for a single Argo CD instance to manage multiple clusters, however it requires opening up access to each cluster and does not provide very good isolation or security. A popular alternative model is installing a “standalone” Argo CD instance in each required cluster. This reduces some scaling and isolation concerns however there is now additional management and access complexity that is introduced in its place.</p>
  1493. <p><strong>Octopus solves this</strong> by providing a single pane of glass through which you can view your application’s sync state, regardless of the underlying cluster that they are running on. While Octopus doesn’t currently manage the Argo CD instances themselves, the projects that your team cares about can all be presented in one place. The job of keeping the state in sync remains left to Argo CD, but this state is directed by Octopus through your git manifests.</p>
  1494. <h2 id="introducing-argo-cd-with-octopus">Introducing Argo CD with Octopus</h2>
  1495. <p>Octopus has had native support for Kubernetes for many years, most recently with our own live object status capability. We know that some customers want to leverage the strengths that Argo CD provides in supporting GitOps workflows. We can now provide capabilities in Octopus to get the best of both products, leveraging the strong points of each without sacrificing what makes both Argo CD and Octopus useful and without trying to hide the fact that Argo CD is playing its role.</p>
  1496. <p>Let’s take a look at how this new feature works. This blog post won’t explore all the possible configurations but instead run through some of the highlights as well as discuss some of our suggested best practices to make the most of these new capabilities.</p>
  1497. <h3 id="declarative-integration">Declarative integration</h3>
  1498. <p>In providing Argo CD integration into Octopus Deploy, it was important to us that we provide a model that would align with the way that typical users of Argo CD want to work. This means that rather than thinking of Argo CD as a typical Octopus target, we instead automatically use declarative annotations stored in the Application manifests from connected instances.</p>
  1499. <p>Since each Application in an Argo CD instance typically maps to a specific deployable app in a specific environment (perhaps per cluster), a series of annotations can be added to the declarative Application manifest to signal to Octopus which are relevant for a given Project and Environment deployment context.</p>
  1500. <p>No need to configure each application in Octopus, just connect your Argo CD to Octopus and your declarative configuration does the rest. This configuration more naturally fits into the GitOps mindset that is prevalent in Kubernetes pipelines.</p>
  1501. <h3 id="deployments-are-commits">Deployments are commits</h3>
  1502. <p>In Octopus, a deployment is the execution of a pipeline in the context of an environment. A successful deployment means every step in the pipeline has been completed. For Kubernetes, this might involve applying manifests with <code>kubectl</code> or upgrading Helm charts with <code>helm upgrade</code>.</p>
  1503. <p>Argo CD, however, works differently. Its core responsibility is to synchronize the contents of a Git repository with a Kubernetes cluster. This means that during a deployment, Octopus modifies the desired state in Git, and Argo CD takes care of reconciling those changes to the cluster.</p>
  1504. <p>We’ve modelled Git updates as deployment steps in Octopus. This lets you combine GitOps-driven updates with the rest of your delivery process. For example, you can run a database migration before promoting an Application through Argo CD, execute smoke tests after the update, and send a Slack notification if those tests fail.</p>
  1505. <h3 id="argo-cd-connectivity">Argo CD connectivity</h3>
  1506. <p>The first thing you will need to do is to set up the connection between Octopus Deploy and your Argo CD instances.</p>
  1507. <p>Connecting your Argo CD instance involves running an Octopus-Argo CD Gateway in the cluster alongside each Argo CD instance, and we provide a similar helpful Helm installation flow to that used by our popular <a href="https://octopus.com/docs/kubernetes/targets/kubernetes-agent">Kubernetes Agent</a>.</p>
  1508. <p><img  src="/blog/_astro/octopus-argo-gateway.BozMTYdZ_Qp3A0.webp" alt="Octopus Argo CD Gateway" loading="lazy" decoding="async" fetchpriority="auto" width="1510" height="592"></p>
  1509. <p>As part of this process, there is also the option to scope the connection to specific environments or, when relevant, tenants. Used in conjunction with Octopus Deploy’s RBAC system, this configuration provides one example where stricter controls can be optionally placed around your Argo CD instance and, by extension, deployments to the applications it manages.</p>
  1510. <h3 id="new-octopus-steps">New Octopus steps</h3>
  1511. <p>This first release of Argo CD integration introduces two new steps that make promotions of Argo CD Applications easier and safer.</p>
  1512. <h4 id="update-argo-cd-application-image-tags">Update Argo CD Application Image Tags</h4>
  1513. <p>Most changes to Kubernetes manifests are simple container image tag updates. For every infrastructure change, there are usually dozens—or even hundreds—of new application versions to deploy.</p>
  1514. <p>If your team manages manifest files through a separate promotion process (manual or automated) but needs a reliable way to detect new builds, update container tags, and safely promote them through environments, the <strong>Update Argo CD Application Image Tags</strong> step handles this.</p>
  1515. <p><img  src="/blog/_astro/deploy-is-commit-1.DbnNJPg3_Z2oCDSi.webp" alt="Argo CD watches repository for changes" loading="lazy" decoding="async" fetchpriority="auto" width="748" height="438"></p>
  1516. <p>When an Octopus deployment runs, it looks for Argo CD Applications annotated for the relevant project and environment. For each discovered Application (across one or many Argo CD instances), Octopus retrieves the Git location, updates the image tags in the manifests (or Helm values files), and commits the changes.</p>
  1517. <p><img  src="/blog/_astro/deploy-is-commit-2.DYiFWaRJ_1DpuGJ.webp" alt="Octopus updates manifest in repository" loading="lazy" decoding="async" fetchpriority="auto" width="748" height="438"></p>
  1518. <p>Argo CD then detects the change and syncs the updated manifests to the cluster.</p>
  1519. <p><img  src="/blog/_astro/deploy-is-commit-3.EVlpM1q-_11hrq4.webp" alt="Desired state applied to cluster" loading="lazy" decoding="async" fetchpriority="auto" width="724" height="415"></p>
  1520. <h4 id="update-argo-cd-application-manifests">Update Argo CD Application Manifests</h4>
  1521. <p>The second step supports more complex scenarios and introduces a stricter approach to managing manifests.</p>
  1522. <p>Unlike the Update Argo CD Application Image Tags step, which modifies existing files in an Application’s <code>source</code> folder, this step copies one or more files from a designated <code>input</code> folder and commits them into the Application <code>source</code>.</p>
  1523. <p><img  src="/blog/_astro/template-step-1.Bp63AZ1U_cwAVJ.webp" alt="Argo CD watches repository for changes" loading="lazy" decoding="async" fetchpriority="auto" width="1236" height="450"></p>
  1524. <p>During a deployment, Octopus commits the selected files. Any files with the same name are overwritten.</p>
  1525. <p><img  src="/blog/_astro/template-step-2.CuJUVEKz_Z2w0Eu2.webp" alt="Argo CD watches repository for changes" loading="lazy" decoding="async" fetchpriority="auto" width="1096" height="439"></p>
  1526. <p>Argo CD then syncs those changes to the cluster.</p>
  1527. <p><img  src="/blog/_astro/template-step-3.vp21_KjR_29dset.webp" alt="Argo CD watches repository for changes" loading="lazy" decoding="async" fetchpriority="auto" width="1132" height="427"></p>
  1528. <p>What does this enable?</p>
  1529. <ul>
  1530. <li><strong>Full manifest control</strong> – You can modify, add, or remove any fields in your manifests, not just image tags.</li>
  1531. <li><strong>Application creation</strong> – You can create manifests for entirely new Argo CD Applications. For example, when creating a new tenant, environment, or cluster, Argo CD (with ApplicationSets if configured) will create a new Application, and Octopus will pick it up and generate the manifests with the next deployment.</li>
  1532. <li><strong>Stricter governance</strong> – By overwriting files, this step enforces a single source of truth. Even if someone makes direct changes in an Application’s <code>source</code> folder, Octopus can reset them during the next deployment. This ensures only tested, approved configurations flow into production.</li>
  1533. </ul>
  1534. <p>What can go in the <code>input</code> folder?</p>
  1535. <ul>
  1536. <li><strong>Shared files</strong> – The simplest option is a single file (such as Helm values) applied across environments. Updating the file before deployment creates a clear, auditable history of changes in Git.</li>
  1537. <li><strong>Environment-specific configurations</strong> – For per-environment differences, you can either maintain separate folders or treat files as templates. Octopus variables can replace placeholders during deployment, injecting the correct values for each environment.</li>
  1538. </ul>
  1539. <p>This flexibility lets teams choose between lightweight image updates and strict manifest ownership, depending on their governance and security needs.</p>
  1540. <h2 id="best-practices">Best practices</h2>
  1541. <p>We designed this feature with established Argo CD best practices in mind. Following them helps you get the most value from Octopus and avoid common pitfalls. This isn’t a full guide to GitOps and Argo CD best practices, but here are a few that are especially relevant when using Octopus with Argo CD:</p>
  1542. <ul>
  1543. <li><strong>Avoid putting environment configuration directly in Argo CD Application manifests.</strong> Applications should describe how to deploy, not encode environment-specific details.</li>
  1544. <li><strong>Keep values files separate from the application source.</strong> Store them in dedicated locations so they can be promoted and managed consistently across environments.</li>
  1545. <li><strong>Separate manifest repositories from application code repositories.</strong> This makes pipelines cleaner and avoids coupling application development with infrastructure changes.</li>
  1546. <li><strong>Be cautious with Helm for internal applications.</strong> Helm can be useful, but for simple services, you may not need the overhead. Additionally, if Applications from different environments reference the same Helm chart, updating the chart will result in simultaneous changes to all Applications. A more secure approach would be to promote these changes through the environments instead. If you use Helm, keep a copy of the chart per environment to prevent cross-environment drift.</li>
  1547. </ul>
  1548. <p>For more context, see <a href="https://codefresh.io/blog/argo-cd-anti-patterns-for-gitops/">Argo CD anti-patterns</a> from one of our Argo CD maintainers, and this guide on <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">structuring repositories with ApplicationSets</a>.</p>
  1549. <h2 id="current-limitations--future-plans">Current limitations &#x26; future plans</h2>
  1550. <p>Our goal with this first offering is to provide the core capabilities required to perform a deployment to a Kubernetes cluster via Argo CD. As such, we plan to continue investing in further features as well as improve the capabilities initially delivered.</p>
  1551. <p>Talking to Argo CD users, we found that about 50% polled liked having the ability to make direct changes to the manifests that Argo CD is watching to quickly push changes that might bypass typical promotion mechanisms. The other 50% preferred managing centralized templating repositories using tools like Helm or Kustomize and enforcing that all changes must be promoted through successive environments. Confusingly, a not-insignificant percentage wanted the capabilities of both patterns at once!</p>
  1552. <p>To move fast, we have built on top of Octopus’s current systems, and this means that particularly large repositories may take time to clone. Since the industry best practices are to separate your application code repository from your infrastructure manifests, we expect that this shouldn’t be a problem for most users; however, we are investigating some promising improvements that can be made.</p>
  1553. <h2 id="how-to-try-it-out">How to try it out</h2>
  1554. <p>Argo CD in Octopus is currently in <strong>Early Access</strong>, rolling out to Octopus Cloud in early October.</p>
  1555. <p>If you’re an Octopus Cloud user, there’s nothing extra required to enable the feature. Simply open the <strong>Argo CD Instances</strong> section under <strong>Infrastructure</strong>, and connect one of your Argo CD instances to Octopus. From there, you can start modeling deployments that combine GitOps and Continuous Delivery out of the box.</p>
  1556. <h2 id="conclusion">Conclusion</h2>
  1557. <p>Argo CD is a powerful GitOps tool for Kubernetes, but it wasn’t built to manage the full software delivery lifecycle. Octopus complements Argo CD by adding environment promotions, orchestration across diverse workloads, fine-grained RBAC, and centralized visibility across clusters.</p>
  1558. <p>With Argo CD integration, Octopus lets teams combine the strengths of GitOps and Continuous Delivery without building custom automation. You get the reliability of Git-driven deployments and the safety, governance, and flexibility of a full CD platform—all in one place.</p>
  1559. <p>Happy deployments!</p>]]></content>
  1560.    </entry>
  1561.    <entry>
  1562.      <title>Launching the Octopus MCP Server</title>
  1563.      <link href="https://octopus.com/blog/launching-octopus-mcp" />
  1564.      <id>https://octopus.com/blog/launching-octopus-mcp</id>
  1565.      <published>2025-09-29T00:00:00.000Z</published>
  1566.      <updated>2025-09-29T00:00:00.000Z</updated>
  1567.      <summary>The Octopus MCP Server provides your AI assistant with powerful tools that allow it to explore, inspect, and diagnose problems within your Octopus instance, transforming it into your ultimate DevOps wingmate.</summary>
  1568.      <author>
  1569.        <name>Andrew Best, Octopus Deploy</name>
  1570.      </author>
  1571.      <content type="html"><![CDATA[<p>Artificial intelligence, and GenAI technologies in particular, are transforming our technology landscape.</p>
  1572. <p>For Continuous Delivery, AI allows us to solve previously-unsolvable problems. Parsing complex log files and diagnosing root cause failures and providing intelligent remediation; natural-language exploration of your software landscape, simplifying auditing, compliance, and standardization; agentic workflows providing intelligent glue between your essential software services.</p>
  1573. <p>At Octopus, we are bringing these capabilities to the best Continuous Delivery tool on the market, lowering risk, improving efficiency, and accelerating your software delivery.</p>
  1574. <p>Our most recent AI-powered capability is the <a href="https://github.com/OctopusDeploy/mcp-server">Octopus MCP Server</a>.</p>
  1575. <h2 id="what-is-mcp">What is MCP?</h2>
  1576. <p><a href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol (MCP)</a> allows the AI assistants you use in your day-to-day work, like VS Code, Claude, or ChatGPT, to connect to the systems and services you own in a standardized fashion, allowing them to pull information from those systems and services to answer questions and perform tasks.</p>
  1577. <h2 id="the-octopus-mcp-server">The Octopus MCP Server</h2>
  1578. <p>The Octopus MCP Server provides your AI assistant with powerful tools that allow it to explore deployments, inspect configuration, and diagnose problems within your Octopus instance, transforming it into your ultimate DevOps wingmate. For a list of supported use-cases and sample prompts, see <a href="https://octopus.com/docs/octopus-ai/mcp">our documentation</a>.</p>
  1579. <p>The MCP server architecture ensures that your deployment data remains secure while enabling powerful AI-assisted workflows. All interactions are logged and auditable, maintaining the compliance and governance standards your organization requires.</p>
  1580. <p>The initial release of the Octopus MCP Server is a local MCP server - this means it runs on your local machine, and communicates with your Octopus instance via secure HTTPS. If you are interested in remote MCP, in which the MCP server is embedded within your Octopus instance, please register your interest <a href="https://roadmap.octopus.com/c/228-remote-mcp-server-ai-">on our roadmap</a>.</p>
  1581. <h2 id="breaking-down-devops-silos">Breaking down DevOps silos</h2>
  1582. <p>One of the strengths of MCP is that it allows your AI assistant to perform tasks <em>across</em> your DevOps ecosystem. It can work alongside other MCP servers to accomplish more complex orchestrations across Octopus and your other essential DevOps services.</p>
  1583. <p>With Continuous Delivery, one of the challenges that arises is understanding the continual flow of changes to production, and monitoring to ensure they are happy and healthy. Typically, you need to work across a number of systems to answer questions within this space. Let’s explore how MCP supercharges our ability to do this all in one place - our AI client of choice.</p>
  1584. <h3 id="what-has-just-shipped-and-is-it-healthy">What has just shipped, and is it healthy?</h3>
  1585. <p>This example uses Claude Desktop, with Sonnet 4 as the model.</p>
  1586. <blockquote>
  1587. <p>I’d like to know what Octopus changes are just about to be released to our customers. Find the latest untenanted deployment of the Octopus Server project to the Production environment, and then find a commit in GitHub in OctopusDeploy/OctopusDeploy with a tag matching the version number of the deployment, and tell me the commit details so I can understand what is being shipped.</p>
  1588. </blockquote>
  1589. <figure><p><img src="/blog/img/launching-octopus-mcp/mcp-example-1.png" alt="Octopus MCP finding the latest deployed version"></p></figure>
  1590. <p>Claude has used the Octopus MCP server to explore our Octopus instance (of course we use Octopus to ship Octopus!) and find the latest release to our Production environment. It then digs into the release to find the version number of the release.</p>
  1591. <p>Next it uses the Github MCP server to find a commit tagged with that version. Traceability is essential in Continuous Delivery - you have to know what changes in your source control correspond to what released versions of your software.</p>
  1592. <figure><p><img src="/blog/img/launching-octopus-mcp/mcp-example-2.png" alt="GitHub MCP finding the details of a deployed change"></p></figure>
  1593. <p>It finds the tagged commit, digs into the details, and gives us a summary of the change - in this case it looks like a small bugfix to ensure backwards compatibility for API clients. Great!</p>
  1594. <p>Now, has the deployment gone smoothly? The deployment itself is green, but we know that is only the start of the story - let’s provide another prompt in the same chat to see how it is behaving out in the real world.</p>
  1595. <blockquote>
  1596. <p>Can you see any errors in Honeycomb regarding this release?</p>
  1597. </blockquote>
  1598. <p>We use <a href="https://www.honeycomb.io/">Honeycomb</a> as a key part of our observability stack at Octopus, helping us monitor our Production environment to identify, diagnose, and remediate problems as they come up.</p>
  1599. <figure><p><img src="/blog/img/launching-octopus-mcp/mcp-example-3.png" alt="Honeycomb MCP checking for errors after a recent deployment"></p></figure>
  1600. <p>LLMs can be quite persistent in their pursuit of an answer. In this case, Claude keeps looking for more specific examples of errors that might indicate a serialization problem, as it has understood that this is what the change was intended to fix. Very clever!</p>
  1601. <h2 id="getting-started">Getting started</h2>
  1602. <p>Begin your AI-powered DevOps journey today with the Octopus MCP Server. As an early access participant, you’ll help shape these features while gaining early access to capabilities that will transform how teams deploy software.</p>
  1603. <p><a href="https://github.com/OctopusDeploy/mcp-server?tab=readme-ov-file#-installation">Get Started with the Octopus MCP Server</a></p>]]></content>
  1604.    </entry>
  1605.    <entry>
  1606.      <title>Adoption strategies for internal platforms</title>
  1607.      <link href="https://octopus.com/blog/adoption-strategies-internal-platforms" />
  1608.      <id>https://octopus.com/blog/adoption-strategies-internal-platforms</id>
  1609.      <published>2025-09-23T00:00:00.000Z</published>
  1610.      <updated>2025-09-23T00:00:00.000Z</updated>
  1611.      <summary>Our Platform Engineering pulse report looked at many aspects of real-world practice, but one interesting study area was the themes for common platform features.</summary>
  1612.      <author>
  1613.        <name>Matthew Allford, Octopus Deploy</name>
  1614.      </author>
  1615.      <content type="html"><![CDATA[<p>Our upcoming report examines how organizations <a href="https://octopus.com/publications/platform-engineering-pulse">adopt and succeed with Platform Engineering</a>. We’ll be launching a broader survey soon to dive deeper into patterns and practices of Platform Engineering, but one of the areas that surfaced was the platform adoption strategy; whether companies make the platform optional or mandatory, and perceived success factors for each approach.</p>
  1616. <p>The Platform Engineering community has been pretty clear on this one. Treat your platform like a product, let it compete for internal market share, and generally, don’t mandate adoption. After all, if your platform genuinely provides a path to success and compliance with the organization’s standards, developers should naturally gravitate toward it, right?</p>
  1617. <p>This philosophy draws heavily from product management thinking. Just as external products succeed by solving customer problems better than alternatives, internal platforms should win developer adoption by genuinely improving their daily experience. The theory suggests that when platforms must compete for users, they naturally evolve to be more user-focused, innovative, and valuable.</p>
  1618. <h2 id="mandatory-adoption">Mandatory adoption</h2>
  1619. <p>Let’s be honest about why mandatory platforms are so appealing. They work. At least from an organizational standpoint. When leadership mandates platform adoption, it’s because the platform initiative is created and funded to solve specific business problems. Our research shows that the top 3 reasons for adopting Platform Engineering are to:</p>
  1620. <ul>
  1621. <li>Improve efficiency</li>
  1622. <li>Standardize processes</li>
  1623. <li>Increase developer productivity</li>
  1624. </ul>
  1625. <p>From a business perspective, these are legitimate wins.</p>
  1626. <p>The budget numbers back up the mandatory approach too. Our research found that mandatory platforms report almost 2.5x higher confidence that their funding will remain secure over the next five years.</p>
  1627. <h2 id="optional-adoption">Optional adoption</h2>
  1628. <p>Optional platforms can face real challenges. They struggle with budget uncertainty (2x more likely to worry about funding cuts) compared to mandatory platforms, but when platforms must compete for users, something important happens. They hyper-focus on solving real developer problems because frustrated users will simply choose alternatives if they don’t.</p>
  1629. <p>Organizations often undermine their own platform adoption by inconsistently enforcing standards. When teams can bypass organizational requirements for security, compliance, and operational practices, they see little value in adopting an internal developer platform. These teams happily create their own build pipelines, deploy infrastructure manually through portals, and sidestep approval processes. After all, they’re avoiding both the platform and the problems it was designed to solve.</p>
  1630. <p>The dynamic shifts dramatically when organizations enforce standards uniformly. When teams must meet security, compliance, and operational requirements regardless of whether they use the platform, the platform transforms from an obstacle into the obvious solution. Rather than struggling to meet complex requirements independently, teams naturally gravitate toward a platform that makes compliance straightforward and built-in.</p>
  1631. <p>Users choose these platforms because they genuinely make work easier while allowing them to take actions without triple-checking whether they comply with the organization’s rules.</p>
  1632. <h2 id="the-perception-gap-between-builders-and-users">The perception gap between builders and users</h2>
  1633. <p>This is where our research gets very interesting. We found a significant disconnect between how platform teams (producers) measure success, what consumers experience, and how the platform’s sponsors view the platform’s success.</p>
  1634. <figure><p><img src="/blog/img/adoption-strategies-internal-platforms/success-radar-chart.png" alt="Radar chart displaying satisfaction levels split between mandatory and optional platforms from both producer and consumer perspectives."></p></figure>
  1635. <p>For mandatory platforms, 87.5% of producers said the platform met many or all of its goals; however, only 50% of those consuming the platform agreed.</p>
  1636. <p>For optional platforms, 50% of producers rate them highly successful, saying they meet many or all goals, compared to 67% of consumers rating the platform as successful.</p>
  1637. <p>Producers think mandatory platforms are more successful, whereas consumers are more likely to rate an optional platform as successful. Mandatory platforms may suffer from producer bias, where producers overestimate the value of the platform they are building. That’s why moving beyond assumptions and measuring multiple aspects of the Platform is crucial to tracking success.</p>
  1638. <p>The executives and budget holders who sponsor platform initiatives overwhelmingly find that only some goals have been met, regardless of adoption strategy. This suggests that even when platforms achieve technical success, they may not deliver the business outcomes that justify their investment. This disconnect can stem from unclear or misaligned platform goals. When platform teams aren’t certain what business problems they should solve, they default to technical achievements rather than user or business outcomes.</p>
  1639. <h2 id="measuring-platform-success">Measuring platform success</h2>
  1640. <p>Whether the platform is mandatory or optional, you’ll hear people discuss platform adoption metrics, usually to help support how successful the platform is. However, focusing purely on adoption tells you almost nothing about whether the platform is successful. Mandatory platforms should, by definition, have high adoption rates. For optional platforms, low adoption rates could be one signal to indicate a problem with the platform that needs further investigation, but it doesn’t tell the whole story. Either way, looking at adoption alone won’t tell you whether the business is getting value, if users are satisfied, or if the platform is meeting the goals it was set out to achieve.</p>
  1641. <p>In fact, one metric alone won’t tell you much about the goals and success of a platform, which is why it’s crucial to capture metrics that tie back to the platform’s goals. Platform sponsors want to know whether the money and time being invested are making an impact. Capturing and presenting vanity metrics like “90% adoption rate” or “5 nines of uptime” might make platform teams feel good, but they don’t answer a fundamental question. Is this platform helping our developers be more productive and our business more successful?</p>
  1642. <p>We found that organizations that measure more dimensions of the Platform were more likely to be successful. In fact, there’s a clear correlation between the number of metrics tracked and platform success rates. Organizations measuring 6 or more different aspects of their platform reported the highest success rates.</p>
  1643. <figure><p><img src="/blog/img/adoption-strategies-internal-platforms/high-performer-metric-count.png" alt="Bar chart showing platform success rates increasing from 33% with one metric to 75% with six or more metrics tracked."></p></figure>
  1644. <p>We found that 23% of organizations don’t use metrics and instead rely on intuitive or subjective assessments. In their <a href="https://platformengineering.org/reports/state-of-platform-engineering-vol-3">state of Platform Engineering report</a>, Humanitec found 44% of organizations don’t measure any metrics. Not measuring anything creates a <a href="https://octopus.com/blog/how-organizations-measure-platform-engineering#breaking-the-success-illusion">zero-metric success illusion</a> where platform teams report high success rates simply because they’re not collecting data that might contradict their assumptions. Among organizations that do measure, many rely on technical metrics that make platform teams feel good but completely miss user satisfaction and business impact.</p>
  1645. <p>The solution is user-centric measurement. You can use the <a href="https://octopus.com/devops/metrics/monk-metrics/">MONK metrics</a> to measure your Platform Engineering initiative, which provides a balanced approach of benchmarkable metrics with user satisfaction that can help track the platform’s progress against business objectives.</p>
  1646. <h2 id="when-optional-tooling-naturally-wins">When optional tooling naturally wins</h2>
  1647. <p>Many years ago, I worked at a University, and the team I was on managed, among hundreds of other applications, the Microsoft Exchange environment for thousands of staff members. Creating shared resources like distribution lists, rooms, or shared mailboxes required several teams to be involved.</p>
  1648. <p>The ticket went from the support desk to team 1, to team 2, back to team 1, back to the service desk, and finally back to the user. It took an even longer path if information was missing or something needed clarification. A request from an end user for a new resource would take days, sometimes weeks. Many rules were required regarding resource names, email address formats, mail routing configurations, permissions, etc.</p>
  1649. <p>It was a frustrating experience for everyone involved, and we got dozens of these requests every month, sometimes more. An independent consultant surveyed the business, and at the time, the email system was the #1 critical tool being used day-to-day. We had buy-in from our managers to improve the process because it distracted systems teams from strategic work, and resulted in frustrated customers who were waiting days for their requests to be completed.</p>
  1650. <p>We decided to write a tool to automate the process. Initially, this was purely selfish, as we wanted to eliminate the manual work and reduce errors when we received the ticket. However, as we developed it, we realized we could give this tool directly to the service desk staff, who received the initial requests from the end users. We engaged with the service desk team, informed them of what we were planning, and listened to their requirements if they were to use the tool.</p>
  1651. <p>We didn’t mandate the use of the tool. The service desk team could still assign the ticket to us, and we would use the tool to create the resource. However, we found that the service desk team used the tool 100% of the time, and only issues with the tool itself were escalated to our team.</p>
  1652. <p>Everyone was happier. The service desk could provision resources immediately instead of waiting days for our team to get to the ticket. They had confidence that they were doing it correctly because the tool guided them through the process and validated all the inputs. Users were delighted because their requests were fulfilled instantly. We were happier as our team could focus on more strategic work instead of routine provisioning tasks, which were viewed as tedious administrative work.</p>
  1653. <p>I get it - this is a bespoke example, and it’s not an entire platform. But that’s kind of the point. Don’t try to build the whole platform on day 1. Find genuine pain points in existing workflows today, and start there, not with abstract goals like “improve security” that are challenging for platform creators to interpret, implement, and measure. My aim with this story isn’t to tell you about specific technology or even automation. It’s about what happens when you build something that genuinely improves the experience for everyone involved. When platforms deliver real value, adoption becomes natural rather than forced.</p>
  1654. <h2 id="building-platforms-worth-choosing">Building platforms worth choosing</h2>
  1655. <p>While our initial research shows that mandatory platforms achieve specific organizational metrics and enjoy better budget security, it also reveals why the Platform Engineering community advocates so strongly for optionality. The most successful platforms we studied had something in common: they would thrive even if they weren’t mandated. Consumers chose them not because they had to, but because they were the best tool for getting work done quickly and aligning with organizational standards.</p>
  1656. <p>So before you decide on your adoption strategy, ask yourself this question: If this platform weren’t required, would consumers still choose it? If the answer is no, you might have a platform problem, not an adoption problem. And that’s precisely the problem that Platform Engineering, done well, is designed to solve.</p>
  1657. <p>Happy Deployments!</p>]]></content>
  1658.    </entry>
  1659.    <entry>
  1660.      <title>The top 5 features of internal developer platforms</title>
  1661.      <link href="https://octopus.com/blog/top-5-features-of-internal-developer-platforms" />
  1662.      <id>https://octopus.com/blog/top-5-features-of-internal-developer-platforms</id>
  1663.      <published>2025-09-16T00:00:00.000Z</published>
  1664.      <updated>2025-09-16T00:00:00.000Z</updated>
  1665.      <summary>Our Platform Engineering pulse report looked at many aspects of real-world practice, but one interesting study area was the themes for common platform features.</summary>
  1666.      <author>
  1667.        <name>Steve Fenton, Octopus Deploy</name>
  1668.      </author>
  1669.      <content type="html"><![CDATA[<p>Our <a href="https://octopus.com/publications/platform-engineering-pulse">Platform Engineering Pulse report</a> looked at many aspects of real-world practice, but one interesting study area was the themes for common platform features. We’re launching a broader study to investigate this further, but here are the top 5 features the platform teams we asked had added to internal developer platforms.</p>
  1670. <ol>
  1671. <li>Build automation</li>
  1672. <li>Deployment automation</li>
  1673. <li>Infrastructure automation</li>
  1674. <li>Test automation</li>
  1675. <li>Monitoring and observability</li>
  1676. </ol>
  1677. <h2 id="1-build-automation">1. Build automation</h2>
  1678. <p>Build automation should be triggered each time you change the code and provide you with fast feedback on your changes. If you’re practicing Continuous Delivery, you’ll commit changes to the main branch every few hours, with the feedback from the build process, including fast-running tests, arriving in around 5 minutes.</p>
  1679. <p>The build process includes compilation, linking, and bundling. It usually consists of a suite of fast-running tests that validate the functionality and fitness of the application. In practice, this is typically a sequence of scripts or commands that run each tool in the build chain, but some build processes are more complex.</p>
  1680. <p>The benefit of bringing build automation into your internal developer platform is the opportunity to harmonize pipelines and bring them all up to a common standard for fast and secure builds. For example, instead of each team having to invent a way to sign their package so it can be verified later in the deployment pipeline, platform teams can make this a standard part of the build.</p>
  1681. <p>Development teams rarely touch their build process, compared to how much focus a platform team might put into builds. That means they are less familiar with the tools and may not be aware of features that could improve build performance or security.</p>
  1682. <h2 id="2-deployment-automation">2. Deployment automation</h2>
  1683. <p>Manual deployments are one of the primary reasons for bad paths to production. Even simple manual deployments can go drastically wrong, and when they do, the organization responds by introducing process steps that slow down the flow of change. This is well-meaning, but we’ve learned that reducing deployment frequency results in larger batches, which carry more risk and cause bigger problems.</p>
  1684. <p>Deployment automation is the heart of change in your software delivery. When deployments are reliable, repeatable, and low-effort, you can teach the organization to do them more often. Automated deployments help you build trust and reduce heavyweight processes like change approval by committee, which universally clogs the ability to deploy small batches frequently and safely.</p>
  1685. <p>The steps, process templates, project templates, and policies related to deployments are a perfect fit for Platform Engineering. The platform team can ensure crucial steps are included in all deployments, like verifying the provenance of the packages being deployed and ensuring the packages and deployment process have progressed through appropriate environments before reaching production.</p>
  1686. <p>When the organization needs to level up security across all deployments, a platform team using the right deployment tools can quickly bring the whole organization into compliance with the new standards.</p>
  1687. <h2 id="3-infrastructure-automation">3. Infrastructure automation</h2>
  1688. <p>There was a time when ClickOps was the default for infrastructure changes. Modern software teams have rejected the idea of managing their infrastructure by clicking around in administration consoles, cloud portals, and command lines, as the result is that no two instances of anything are the same. These differences, though sometimes subtle, can cause production incidents that are difficult to identify and resolve.</p>
  1689. <p>By treating infrastructure as code, the definitions can be stored in version control and follow the same review and promotion patterns developers apply to application code. When infrastructure is created and managed this way, each instance is an identical copy, and it’s easier to scale out, recover from a disaster, or create ephemeral environments for testing.</p>
  1690. <p>Platform teams can create standard setups for teams to use that are aligned to the technology choices in their golden pathways. Reducing the breadth of choice makes it easier to secure the infrastructure, control costs, and apply patches and updates.</p>
  1691. <h2 id="4-test-automation">4. Test automation</h2>
  1692. <p>Sometimes it seems there are automated tests everywhere, from unit tests that run continuously in the background as a developer makes changes, to tests used in the build process, to validate end-to-end functionality, and test the fitness of the application’s performance and security. The reason for their prevalence is that testing really works.</p>
  1693. <p>Without test automation, your team would need to perform the same checks manually, and when you don’t do it, your customers do, which is incredibly frustrating.</p>
  1694. <p>While platform teams don’t typically write tests (the research shows the most effective automated tests are written by the developer creating the feature), they can provide the tools that make it easy to define tests and have them run throughout the software delivery process. Platform teams may also provide easy ways to consume test services, making it easy to spin up end-to-end tests or performance tests without managing infrastructure.</p>
  1695. <p>To shorten feedback loops, all types of testing should be performed continuously and ideally represented by fast, reliable test automation suites. You should cover functional, security, and performance tests within your deployment pipeline.</p>
  1696. <p>You should consider how test data is managed as part of your test automation strategy. The ability to set up data in a known state will help you make tests less flaky. Test automation lets developers know early if their change causes a fault, reduces team burnout, and increases software stability.</p>
  1697. <h2 id="5-monitoring-and-observability">5. Monitoring and observability</h2>
  1698. <p>While test automation covers a suite of expected scenarios, monitoring and observability help you expand your view to the entirety of your real-world software use. Monitoring implementations tend to start with resource usage metrics, but mature into measuring software from the customer and business perspective.</p>
  1699. <p>The ability to see information-rich logs can help you understand how faults occur so you can design a more robust system.</p>
  1700. <h2 id="other-popular-features">Other popular features</h2>
  1701. <p>Though they didn’t make it into the top 5, documentation, security scanning, one-click setup for new projects, artifact management, and secrets management are common features of internal developer platforms.</p>
  1702. <p>The features are useful on their own, but when combined, they amplify their effects on overall software delivery and operational performance. Platform teams who provide a seamless toolchain from commit through to production and on to day 2 operations will find they have lifted a massive burden from developers.</p>
  1703. <figure><p><img src="/blog/img/top-5-features-of-internal-developer-platforms/top-10-platform-features.png" alt="The relative popularity of the top 10 features"></p></figure>
  1704. <h2 id="what-high-performers-do-differently">What high performers do differently</h2>
  1705. <p>The high-performing organizations used the amplification effect to provide a complete toolchain for deployment pipelines. Platforms that provided this set of functionality had more impact on the organization and its goals for Platform Engineering.</p>
  1706. <ul>
  1707. <li>Builds</li>
  1708. <li>Test automation</li>
  1709. <li>Artifact management</li>
  1710. <li>Deployment automation</li>
  1711. <li>Infrastructure automation</li>
  1712. <li>Monitoring and observability</li>
  1713. </ul>
  1714. <p>By creating a strong pipeline with equal consideration for long-term sustainability of the platform, you can create an offering that helps developers today and continues to help them as the organization navigates changing requirements, regulations, and competition.</p>
  1715. <p>We also looked at organizations with low performance; they were often missing 3 key features:</p>
  1716. <ul>
  1717. <li>Test automation</li>
  1718. <li>Artifact management</li>
  1719. <li>Monitoring and observability</li>
  1720. </ul>
  1721. <p>If your platform is missing any of these, you may be missing an opportunity to amplify the benefits of platform adoption.</p>
  1722. <h2 id="platform-success">Platform success</h2>
  1723. <p>Throughout our study, we used each organization’s individual definition of success for its platform. There are many different motivators for funding a platform initiative, and the ultimate test of a platform is whether it meets the specific goals it was founded to achieve.</p>
  1724. <p>You can use the <a href="https://octopus.com/devops/metrics/monk-metrics/">MONK metrics</a> to measure your Platform Engineering initiative. They balance benchmarkable metrics with contextual measures that track the platform’s progress against the organization’s goals.</p>
  1725. <p>Despite the many different ways platforms are measured, the presence of a complete feature set increased the chances of success.</p>
  1726. <p>Happy deployments!</p>]]></content>
  1727.    </entry>
  1728.    <entry>
  1729.      <title>Introducing Kubernetes Live Object Status</title>
  1730.      <link href="https://octopus.com/blog/kubernetes-live-object-status" />
  1731.      <id>https://octopus.com/blog/kubernetes-live-object-status</id>
  1732.      <published>2025-04-16T00:00:00.000Z</published>
  1733.      <updated>2025-09-15T00:00:00.000Z</updated>
  1734.      <summary>The Octopus Kubernetes monitor is the next major expansion of capabilities of the Octopus Kubernetes agent.</summary>
  1735.      <author>
  1736.        <name>Nick Josevski, Octopus Deploy</name>
  1737.      </author>
  1738.      <content type="html"><![CDATA[<p>Kubernetes is rapidly becoming the dominant platform for hosting and running applications.</p>
  1739. <p>At Octopus, we want to provide the best experience for deploying and monitoring your applications on Kubernetes.</p>
  1740. <p>To make your deployments to Kubernetes simpler, faster, and safer, Octopus has a deployment target called the Kubernetes agent. The Kubernetes agent is a small, lightweight application you install into your Kubernetes cluster.</p>
  1741. <p>Kubernetes Live Object Status—our Kubernetes monitor—is the next major expansion of this deployment agent’s capabilities.</p>
  1742. <h2 id="post-deployment-monitoring">Post-deployment monitoring</h2>
  1743. <p>Monitoring is an important part of the deployment process and is even more important for Kubernetes. It gives you confidence that your applications are running as expected.</p>
  1744. <p>When troubleshooting an unhealthy app, you often need to use a combination of tools and login credentials to figure out what’s going wrong. That can be quite fiddly, especially with Kubernetes. Your Octopus Deploy instance already has these credentials and awareness of your applications, similar to runbooks. We’re aiming to make Octopus the first port of call as you and your team continuously deliver software.</p>
  1745. <p>We roll up the combined status for the objects in a given release, per environment, into a single live status.</p>
  1746. <p><img src="/blog/img/kubernetes-live-object-status/klos-project-view.png" alt="Project view with live status"><em>Project view with live status</em></p>
  1747. <h3 id="detailed-resource-inspection">Detailed resource inspection</h3>
  1748. <p>Dive into the details of each Kubernetes resource to understand how your deployment has been configured and monitor your application itself.</p>
  1749. <p>Kubernetes Live Object Status gives a quick view of all the Kubernetes resources included in your application. You can then dig in to each resource to view the manifests, events and application logs for each Kubernetes resource.</p>
  1750. <p><img src="/blog/img/kubernetes-live-object-status/live-status-drawer-manifest.png" alt="Kubernetes resource manifest"><em>Kubernetes resource manifest</em></p>
  1751. <h2 id="if-youre-already-using-the-kubernetes-agent">If you’re already using the Kubernetes agent</h2>
  1752. <p>If you already use the Kubernetes agent, your upgrade path will be simple.</p>
  1753. <h3 id="upgrading-your-agents-to-the-version-containing-the-monitor">Upgrading your agents to the version containing the monitor</h3>
  1754. <p>We’re working on a one-click upgrade process you can access in Octopus Deploy.</p>
  1755. <p>If you can’t wait until then, you can upgrade existing Kubernetes agents by running a Helm command on your cluster. <a href="https://octopus.com/docs/kubernetes/live-object-status/installation#upgrading-an-existing-kubernetes-agent">See our documentation for all the details</a>.</p>
  1756. <h2 id="new-to-using-octopus-for-kubernetes-deployments">New to using Octopus for Kubernetes deployments?</h2>
  1757. <p>After you install the agent, it registers itself with Octopus Server as a new deployment target. This lets you deploy your applications and manifests into that cluster, without the need for workers, external credentials, or custom tooling. All new installations of the agent will have the monitor enabled.</p>
  1758. <h3 id="installing-the-agent">Installing the agent</h3>
  1759. <p>The Kubernetes agent gets packaged and installed via a Helm chart. This makes managing the agent very simple and makes automated installation easy.</p>
  1760. <p>The Kubernetes monitoring component comes along for the ride. <a href="https://octopus.com/docs/kubernetes/live-object-status/installation">See our docs for detailed instructions</a>.</p>
  1761. <p><img src="/blog/img/kubernetes-live-object-status/kubernetes-agent-wizard-config.png" alt="Kubernetes agent wizard configuration options"><em>Kubernetes agent configuration options</em></p>
  1762. <h2 id="new-to-octopus-deploy-entirely">New to Octopus Deploy entirely?</h2>
  1763. <p>How exciting! Welcome to scalable, simple, and safe Kubernetes CD with Octopus.</p>
  1764. <p>Octopus is one user-friendly tool for developers to deploy, verify, and troubleshoot their apps. Platform engineers use this powerful tool to fully automate Continuous Delivery, manage configuration templates, and implement compliance, security, and auditing best practices.</p>
  1765. <p>We empower your teams to spend less time managing and troubleshooting Kubernetes deployments and more time shipping new features to improve your software.</p>
  1766. <p>Octopus models environments out-of-the-box and reduces the need for custom scripting. You define your deployment process once and reuse it for all your environments. You can go to production confidently as your process has already worked in other environments.</p>
  1767. <p>If you’re interested in trying it out, sign up for a <a href="https://octopus.com/start">free 30-day trial</a>.</p>
  1768. <h3 id="getting-started-with-the-agent-and-monitor">Getting started with the agent and monitor</h3>
  1769. <p>The Octopus Kubernetes agent targets are a mechanism for executing Kubernetes steps and monitoring application health from inside the target Kubernetes cluster, rather than via an external API connection.</p>
  1770. <p>Like the Octopus Tentacle, the Kubernetes agent is a small, lightweight application that’s installed into the target Kubernetes cluster.</p>
  1771. <p>You install the Kubernetes agent using Helm via the octopusdeploy/kubernetes-agent chart. For the complete details, see our docs about <a href="https://octopus.com/docs/kubernetes/targets/kubernetes-agent#installing-the-kubernetes-agent">installing the Kubernetes agent</a>.</p>
  1772. <h3 id="when-can-i-use-it">When can I use it?</h3>
  1773. <div class="info"><p>Kubernetes Live Object Status is now generally available and recommended for production use for Octopus Cloud and self-hosted customers running Octopus Server 2025.3 or later.</p><p>Support for Octopus Server running in high availability clusters is not yet available, but will be coming in the next self-hosted release.</p></div>
  1774. <p>The Kubernetes agent is available now as an Early Access Preview (EAP) in Octopus Cloud! If you don’t see the feature available, please reach out and we can fast-track your cloud instance getting this release.</p>
  1775. <p>Remember this is an opt-in upgrade for existing Octopus agents installed on your cluster(s). <a href="https://octopus.com/docs/kubernetes/live-object-status/installation#upgrading-an-existing-kubernetes-agent">See this documentation page for all the details</a>.</p>
  1776. <p><img src="/blog/img/kubernetes-live-object-status/kubernetes-agent-wizard-config.png" alt="Kubernetes agent as deployment targets"><em>Kubernetes agent as deployment targets</em></p>
  1777. <h2 id="how-we-built-kubernetes-live-object-status">How we built Kubernetes Live Object Status</h2>
  1778. <p>To facilitate a potentially large flow of new data coming to Octopus Server, a separate and non-disruptive web host runs alongside the main host. This isolation level gives us confidence that this is an additive feature and if there are performance complications, they’ll get isolated and managed with minimal impact on Octopus Server’s regular operations.</p>
  1779. <p>The cluster-based monitoring capability uses two values to identify the incoming request:</p>
  1780. <ul>
  1781. <li>The client certificate thumbprint</li>
  1782. <li>An installation ID in the request headers</li>
  1783. </ul>
  1784. <p>Octopus Server uses a long-lived bearer token as a shared secret for authentication. The token gets generated when the monitoring capability installs in the cluster and registers with Octopus Server. This token is rotatable by customers and only valid for use on the gRPC endpoint.</p>
  1785. <p>This allowed us to build gRPC services to handle the data flowing from the monitoring agent in the Kubernetes clusters. <a href="https://grpc.io/">gRPC</a> is a modern open-source high-performance remote procedure call (RPC) framework. This is the first time we’re using gRPC as part of an Octopus feature.</p>
  1786. <p>In the cluster, alongside the Octopus Kubernetes agent, we have this new component that’s responsible for the monitoring aspect. It sits in the cluster and monitors the deployed resources, pumping relevant live-status data back out over gRPC to Octopus Deploy.</p>
  1787. <p>As we also run Octopus Deploy in Kubernetes for our Octopus Cloud customers, we have a new nginx-based ingress configuration to help with partitioning and scalability. To find out more have a look at <a href="https://www.youtube.com/watch?v=DH7YDySEPHU">how we use Kubernetes for Octopus Cloud</a>.</p>
  1788. <h3 id="written-in-go">Written in Go</h3>
  1789. <p>This is the first large-scale feature our team has built in <a href="https://go.dev/">Golang</a> in Octopus. This has given us access to a large set of great libraries built for Kubernetes. Examples include Helm packages and the Argo GitOps engine. Our team got the expertise uplift from the Codefresh engineers who are now part of Octopus.</p>
  1790. <p>The GitOps engine is a flexible library with enough configuration and extension points for us to save very specific information on a per-resource basis. This helps us get the right information out of the cluster and back to Octopus. Go is also the de facto programming language for Kubernetes.</p>
  1791. <p>We’re exploring options to open-source parts of our implementation. Stay tuned for when that’s all decided, as we’ll have a follow-up blog post. The likely first step will be making the source available for inspection. This is part of offering more transparency into the tools we’re asking customers to run in the security context of their clusters.</p>
  1792. <h2 id="whats-coming-next">What’s coming next</h2>
  1793. <p>Today’s release is the EAP. The list below represents capabilities we think are worth adding next (though it’s not the complete list). If you have thoughts and opinions, please reach out to us in the comments section below or on our <a href="https://octopus.com/slack">community Slack</a>.</p>
  1794. <ul>
  1795. <li>Terraform-based setup</li>
  1796. <li>Support Kubernetes API targets</li>
  1797. <li>Octopus HA (multi-node server) support</li>
  1798. <li>Custom health checks</li>
  1799. <li>Orphan and drift detection</li>
  1800. </ul>
  1801. <h3 id="this-looks-cool-but-what-if-i-dont-deploy-to-kubernetes">This looks cool, but what if I don’t deploy to Kubernetes?</h3>
  1802. <p>Currently, there are no plans to extend this beyond Kubernetes deployments. Please let us know where and why you’d like to use this monitoring capability.</p>
  1803. <h2 id="let-us-know-your-thoughts">Let us know your thoughts</h2>
  1804. <p>We’re excited to see how you use this monitoring feature. Please let us know in the comments section below or on our <a href="https://octopus.com/slack">community Slack</a> what new opportunities this opens up for your application delivery objectives.</p>
  1805. <p>Happy deployments!</p>]]></content>
  1806.    </entry>
  1807.    <entry>
  1808.      <title>Troubleshooting common Octopus Deploy issues</title>
  1809.      <link href="https://octopus.com/blog/troubleshooting-common-octopus-deploy-issues" />
  1810.      <id>https://octopus.com/blog/troubleshooting-common-octopus-deploy-issues</id>
  1811.      <published>2025-09-11T00:00:00.000Z</published>
  1812.      <updated>2025-09-11T00:00:00.000Z</updated>
  1813.      <summary>Exploring common issues in Octopus Deploy and ways to resolve them.</summary>
  1814.      <author>
  1815.        <name>Donny Bell, Octopus Deploy</name>
  1816.      </author>
  1817.      <content type="html"><![CDATA[<p>Octopus Deploy provides a powerful, flexible platform for automating deployments and runbook execution. However, there are times when you may encounter challenges that require troubleshooting. In this post, we’ll walk through some of the most common issues that users may run into and provide guidance on how to resolve them.</p>
  1818. <h2 id="tentacle-communication-issues">Tentacle communication issues</h2>
  1819. <p>Deployment targets and workers running the Octopus Tentacle agent are key to Octopus infrastructure. If Tentacles cannot communicate with your Octopus Server, deployments and runbooks will fail. Some of the issues we commonly see when setting up a new Tentacle agent include: firewall restrictions, network connectivity issues, SSL offloading, and misconfigured certificates.</p>
  1820. <p>Our <a href="https://octopus.com/docs/infrastructure/deployment-targets/tentacle/troubleshooting-tentacles">Troubleshooting Tentacles documentation</a> is a comprehensive guide for troubleshooting Tentacle communication issues. It covers:</p>
  1821. <ul>
  1822. <li>Verifying that Tentacle services are running</li>
  1823. <li>Checking firewall rules</li>
  1824. <li>Ensuring correct thumbprints are configured</li>
  1825. <li>Debugging connectivity with Octopus diagnostic tools</li>
  1826. </ul>
  1827. <h2 id="calamari-issues-and-antivirus-exclusions">Calamari issues and antivirus exclusions</h2>
  1828. <p>If you ever run into an error message in your logs that includes:</p>
  1829. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span>Bootstrapper did not return the bootstrapper service message</span></span></code></pre>
  1830. <p>This normally indicates that antivirus or other security software is interfering with an Octopus task (such as a deployment or runbook).</p>
  1831. <p>Octopus tasks are powered by <a href="https://octopus.com/docs/octopus-rest-api/calamari">Calamari</a>, a lightweight deployment bootstrapper invoked for each deployment or runbook step. It’s automatically installed and updated as needed in the <em>Tools</em> folder of the <em>Tentacle home directory</em>. Additionally, steps for a given task are processed in a temporary folder inside of the Work folder, also residing in the Tentacle home directory.</p>
  1832. <p>Sometimes, antivirus or endpoint protection software can lock or quarantine files in these folders, causing deployments to fail.
  1833. To prevent this, we recommend working with your security team to add exclusions as necessary for these directories. For additional information, please review our <a href="https://octopus.com/docs/security/hardening-octopus#configure-malware-protection">Hardening Octopus documentation</a>.</p>
  1834. <h2 id="polling-tentacles-over-port-443-https">Polling tentacles over port 443 (HTTPS)</h2>
  1835. <p>In some environments, firewall policies can make it difficult or impossible to open additional ports. <a href="https://octopus.com/docs/infrastructure/deployment-targets/tentacle/polling-tentacles-over-port-443">Octopus supports configuring Polling Tentacles over port 443</a>, which allows communication through a port that is typically already allowed in enterprise networks.</p>
  1836. <p>This option simplifies network configuration and can reduce the setup burden in restrictive environments. This also allows for Octopus instances that communicate with other organizations or network environments to have a path for Tentacle communication that may otherwise not be possible.</p>
  1837. <h2 id="variable-snapshots-in-projects-and-runbooks">Variable snapshots in Projects and Runbooks</h2>
  1838. <p>Octopus variables are a seemingly simple, but common point of confusion that we often help our users with. Octopus leverages <a href="https://octopus.com/docs/releases">project releases</a> and <a href="https://octopus.com/docs/runbooks/runbook-publishing">runbooks snapshots</a> to preserve an immutable set of information to make deployments and runbooks repeatable and predictable.</p>
  1839. <p>You can view the variable values associated with a release by selecting the [Show Snapshot] option in the Variable Snapshot section of a release or runbook. This can be a helpful step for confirming the variable values for a given release or runbook.</p>
  1840. <figure>
  1841. <p><img src="/blog/img/troubleshooting-common-octopus-deploy-issues/light-image1.png" alt="Octopus Deploy UI showing a project release and the steps to update the variables for that release"></p>
  1842. </figure>
  1843. <p><a href="https://octopus.com/docs/projects/variables">Project variables and associated library variable set variables</a> are captured in a snapshot when a release or runbook snapshot is created. In order for variable updates to take effect, you must also do the following in an associated project or runbook:</p>
  1844. <p>For projects:</p>
  1845. <ul>
  1846. <li>Create a new release so that the variable snapshot updates, or</li>
  1847. <li>Update an existing release’s variable snapshot</li>
  1848. </ul>
  1849. <p>For runbooks:</p>
  1850. <ul>
  1851. <li>Create and publish a new runbook snapshot</li>
  1852. </ul>
  1853. <div class="hint"><p>The exception to the above is changes to Tenant variables.</p><p>From our <a href="https://octopus.com/docs/tenants/tenant-variables#tenant-variables-and-snapshots">Tenant Variables documentation</a>:</p><blockquote>
  1854. <p><em>[…] we don’t take a snapshot of tenant variables. This enables you to add new tenants at any time and deploy to them without creating a new release. This means any changes you make to tenant-variables will take immediate effect.</em></p>
  1855. </blockquote></div>
  1856. <h2 id="debugging-variables-with-variable-logging">Debugging variables with variable logging</h2>
  1857. <p>When deployments or runbooks don’t behave as expected, variable issues are a common culprit. Octopus provides the ability to debug variables by <a href="https://octopus.com/docs/support/how-to-turn-on-variable-logging-and-export-the-task-log">enabling variable logging and viewing the raw task log</a>.</p>
  1858. <p>By turning on variable logging, you can:</p>
  1859. <ul>
  1860. <li>Inspect the evaluated values of your variables</li>
  1861. <li>Verify scoping and precedence rules</li>
  1862. <li>Export raw task logs for detailed review</li>
  1863. <li>Save significant troubleshooting time when debugging complex variable configurations</li>
  1864. </ul>
  1865. <p>Alternatively, you may now enable Debug Mode for Octopus deployments and runbooks. For project deployments, this option is available on the “deploy” screen:</p>
  1866. <figure>
  1867. <p><img src="/blog/img/troubleshooting-common-octopus-deploy-issues/light-image2.png" alt="Octopus Deploy UI showing a project release and the steps to enable or disable debug mode"></p>
  1868. </figure>
  1869. <p>When running a runbook, you must click the <strong><code>Show advanced</code></strong> button to reveal Debug mode:</p>
  1870. <figure>
  1871. <p><img src="/blog/img/troubleshooting-common-octopus-deploy-issues/light-image3.png" alt="Octopus Deploy UI showing a runbook snapshot and the steps to enable or disable debug mode"></p>
  1872. </figure>
  1873. <h2 id="resources-for-custom-api-scripts">Resources for custom API scripts</h2>
  1874. <p><a href="https://octopus.com/docs/octopus-rest-api">Octopus Deploy features a powerful REST API</a>. Many Octopus users extend their automation by writing custom scripts that interact with Octopus programmatically. You can find <a href="https://octopus.com/docs/octopus-rest-api/examples">API examples in our documentation</a>. We also offer a <a href="https://github.com/OctopusDeploy/OctopusDeploy-Api/tree/master/REST">public GitHub repository</a> with many scripts that may fit your needs as written or provide a good baseline to iterate and customize for your needs.</p>
  1875. <p>If you can’t find what you need or would like additional inspiration, our <a href="https://octopus.com/slack">Octopus Community Slack channel</a> is a great place to interact with other Octopus users and Octopus employees who can help!</p>
  1876. <h2 id="conclusion">Conclusion</h2>
  1877. <p>Octopus Deploy is a powerful deployment tool that can handle many complex and scaled scenarios. If you need additional help, <a href="https://octopus.com/support">contact Octopus Support</a>.</p>]]></content>
  1878.    </entry>
  1879.    <entry>
  1880.      <title>Your IDP needs DDD</title>
  1881.      <link href="https://octopus.com/blog/your-idp-needs-ddd" />
  1882.      <id>https://octopus.com/blog/your-idp-needs-ddd</id>
  1883.      <published>2025-09-09T00:00:00.000Z</published>
  1884.      <updated>2025-09-09T00:00:00.000Z</updated>
  1885.      <summary>As Platform Engineering grows into a movement at scale, we need to revisit the past and apply some lessons from domain-driven design to our internal developer platforms (IDPs).</summary>
  1886.      <author>
  1887.        <name>Steve Fenton, Octopus Deploy</name>
  1888.      </author>
  1889.      <content type="html"><![CDATA[<p>It has been more than two decades since Eric Evans published his book on domain-driven design (DDD). The idea was to create software designed after the business domain, using the same language and mental models people used outside of the software team.</p>
  1890. <p>We don’t talk about domain-driven design much these days. But as George Santayana said: “Those who cannot remember the past are condemned to repeat it.”</p>
  1891. <p>As Platform Engineering grows into a movement at scale, we need to revisit the past and apply some lessons from domain-driven design to our internal developer platforms (IDPs). Otherwise, we continually step on rakes to learn why they shouldn’t be left on the lawn.</p>
  1892. <p>There’s a host of interesting interconnected ideas in domain-driven design, but one that resonates with Platform Engineering is the concept of a core domain.</p>
  1893. <h2 id="what-core-domains-are">What core domains are</h2>
  1894. <p>When you build software, there are several areas where your innovation, opinions, and solutions create unique value for your organization. You also need many things that don’t add much value, but your software isn’t viable without them.</p>
  1895. <p>Let’s use a pizza restaurant as an example. If you sell pizza, you want to make it easy for customers to choose what they want to eat and have it delivered. To complete the process, you need to look up their address and take a payment.</p>
  1896. <p>Core domains are the areas where you want to do something different that will give you a competitive edge. For your pizza company, that might be how you present the menu, collect customizations, and offer deals and rewards.</p>
  1897. <p>Non-core domains, also called generic domains, are areas where innovation and differentiation make little difference, or where doing things differently may even be undesirable. Customers expect that looking up their address and paying will work like elsewhere. They don’t want you to be innovative here, as it makes it harder to use.</p>
  1898. <p>So, core domains are something unique or special to your organization. It’s essential to your business’s existence, and where you should invest the most. This is something you want to do so well that it’s hard for your competitors to copy.</p>
  1899. <h2 id="the-problem-of-generic-domains">The problem of generic domains</h2>
  1900. <p>When you spend time on generic domains, you direct time, attention, energy, and investment away from the areas that impact your organization most. Generic domains have limited value because they don’t benefit from doing something different or unique. The pizza company will never create such an excellent payment flow that you’d choose their offering over a competitor who offers better customization.</p>
  1901. <p>Generic domains can be just as complex as your core domains, which means they can consume large amounts of investment. Suppose there’s a commercial provider of an offering in your generic domain space. In that case, they’ll be treating it as a core domain and innovating the space, which puts additional pressure on you to invest to avoid falling behind user expectations.</p>
  1902. <p>Organizations that avoid the generic domain trap can outpace their competitors as they spend more time working on features that will make them stand out.</p>
  1903. <h2 id="how-to-tame-generic-domains">How to tame generic domains</h2>
  1904. <p>There’s an easy way to avoid the generic domain trap. Domain-driven design provides a pattern for managing them, which recognizes the asymmetry in the value gained by investment in core domains versus generic domains.</p>
  1905. <p>Instead of reducing costs on paper, the goal is to minimize the real cost of working on generic domains: Lost value.</p>
  1906. <figure><p><img src="/blog/img/your-idp-needs-ddd/taming-generic-domains.png" alt="Domain-driven design prefers to buy off the shelf, then falls back to isolation, outsourcing, and minimalism"></p></figure>
  1907. <p>You should work through this list from the top and choose the earliest exit available.</p>
  1908. <ol>
  1909. <li><strong>Off-the-shelf</strong>: Look for existing software or services that address the generic domain. In particular, look to use:
  1910. <ol>
  1911. <li>Commercial products and software-as-a-service offerings where the provider treats it as a core domain. Their innovation and support will ensure the generic domain doesn’t become an anchor dragging you back.</li>
  1912. <li>Open source tools that are robust and maintained, and where the overheads of adopting and updating them are low.</li>
  1913. </ol>
  1914. </li>
  1915. <li><strong>Isolation</strong>: Where you have to build custom code for a generic domain, encapsulate and isolate it. Placing it behind a well-defined interface minimizes the impact on your core domain and lets you switch it out if an off-the-shelf solution emerges later.</li>
  1916. <li><strong>Outsourcing</strong>: While outsourcing your core domain can cause problems, outsourcing generic domains helps control the cost and distraction of the work. You can define the interface and have an outsourced team focus on the implementation details.</li>
  1917. <li><strong>Minimalism</strong>: When no other option is available, create a simple minimalist solution that meets the immediate need. Don’t over-engineer the generic domain or add features you don’t need. You can be reluctant to iterate the solution and keep your eyes and ears open for when someone creates a software product or service you can use to replace it.</li>
  1918. </ol>
  1919. <h2 id="analyzing-build-versus-buy">Analyzing build versus buy</h2>
  1920. <p>Platform Teams who want to create the most significant force multiplier for the developers they serve need to protect their focus on fitting the tools to the organization. To do that, they need to identify and eliminate areas where their skills are wasted.</p>
  1921. <p>A crucial part of this optimization process is performing a solid build versus buy analysis, which should factor in the initial development cost, ongoing maintenance and support costs, and the opportunity cost of diverting resources away from the core domains.</p>
  1922. <p>Returning to the pizza restaurant, offloading the address lookup to a vendor that commonly provides this feature will mean users are familiar with how it works. The vendor will dedicate more attention to improving the user experience of their tool, and when the vendor introduces innovations, they appear commonly enough that users accept them.</p>
  1923. <p>Similarly, you’d want to offload payments to a fast and secure payment provider. Hence, it works like other sites and keeps pace with developments like 3D secure, tokenization, card security codes, multi-factor authorization, and biometrics. These industry innovations would have forced the developers to revisit this generic domain many times just to keep pace with the baseline.</p>
  1924. <h2 id="why-this-is-crucial-for-platform-engineering">Why this is crucial for Platform Engineering</h2>
  1925. <p>Domain-driven design (DDD) tells us to optimize our development efforts where they will have the most impact on the organization’s success. You shouldn’t over-invest in non-core domains, as they have limited business value and drain resources. Building custom solutions for non-core domains brings unnecessary complexity and maintenance burden, and for what? To build something that you could have bought or outsourced.</p>
  1926. <p>The software industry is about to re-learn the lessons that led to the discovery of domain-driven design. Industry-wide, we are dedicating thousands of developers to building the same thing. Not a simple minimalist solution to fill a gap left by commercial products, software-as-a-service, or open source software, but a million giant platforms that are out of date before they’ve been pushed to production.</p>
  1927. <p>Internal developer platforms sink unless platform teams can shift weight down to underlying tools. By transferring ballast to commercial products or open source tools, the platform team can get back to agility and create simple, minimal solutions that handle real gaps in toolchains caused by truly bespoke needs.</p>
  1928. <p>And it’s these very needs that are missed with behemoth platforms. An organization that needed to go an extra two miles on security may adopt Platform Engineering to tailor a truly robust solution to their security needs. As that platform grows and accumulates additional custom features, the focus on security is lost, and the investment is wasted building and maintaining code that doesn’t solve a unique need for the organization.</p>
  1929. <p>But it’s not all doom and gloom for Platform Engineers. Commercial and open source tools can rescue them from this inevitability by providing features that make it easy to shift the weight down and keep the platform light enough to float.</p>
  1930. <h2 id="float-on-our-platform-hub">Float on our Platform Hub</h2>
  1931. <p>That’s where Platform Hub comes in. By adding features platform teams need, like process templates, project templates, and policies, platform teams can transfer the effort down to Octopus and lighten their platform by removing thousands of lines of bespoke templating code.</p>
  1932. <p>Platform Teams can get back to focusing on the unique needs that make their platform vital to their organization. They will benefit from our innovative mechanisms for template management and policies, which go well beyond the attack of the template clones and the synchronization conflicts that platform teams report with their bespoke solutions.</p>
  1933. <p>Happy deployments!</p>]]></content>
  1934.    </entry>
  1935.    <entry>
  1936.      <title>Focus on your end users when creating AI workloads</title>
  1937.      <link href="https://octopus.com/blog/focus-on-end-users-for-ai" />
  1938.      <id>https://octopus.com/blog/focus-on-end-users-for-ai</id>
  1939.      <published>2025-09-04T00:00:00.000Z</published>
  1940.      <updated>2025-09-04T00:00:00.000Z</updated>
  1941.      <summary>Why it is important to focus on helping end users above all else when creating AI workloads.</summary>
  1942.      <author>
  1943.        <name>Bob Walker, Octopus Deploy</name>
  1944.      </author>
  1945.      <content type="html"><![CDATA[<p>Recently, I attended a conference targeted at CIOs, CTOs, and VPs of Technology. As expected, there were many sessions on AI and how it can help companies be more efficient. The example given was the well-known use of AI in the hiring process; using AI as gatekeepers to quickly weed out all the unqualified candidates. “Your human resources people won’t have to wade through so many CVs and phone screens!”</p>
  1946. <p>That use case improves the efficiency of human resources or your people team. But that efficiency comes at the cost of the end users, the people you are trying to hire. <em>Everyone hates</em> how AI is used in hiring processes today. Phrases like dystopian and Orwellian are common. In this article, I’ll discuss why focusing on both your AI feature’s beneficiary and end users is essential.</p>
  1947. <h2 id="beneficiary-user-vs-end-user">Beneficiary User vs. End User</h2>
  1948. <p>A beneficiary user is a person who benefits from leveraging AI. The end user is the person who will use an AI feature to accomplish a specific task.</p>
  1949. <p>Returning to the hiring process, the beneficiary user is responsible for wading through CVs and performing the initial phone screen. The end user is the person submitting their CV. The person in charge of going through CVs benefits from AI by offloading the repetitive work of screening unqualified candidates. Imagine a job posting for a senior .NET developer, but 30% of CVs submitted only include project manager experience. You might think I’m exaggerating, but you’d be surprised. As a former hiring manager who had to wade through CVs, I was shocked by how many people were “CV Bombing” - applying for as many positions as possible.</p>
  1950. <p>Looking at Octopus Deploy, the beneficiary of our AI Assistant is the platform engineer. The end user is the developer who uses the assistant to accomplish a particular task. For example, you can ask the Octopus AI Assistant why a deployment or runbook run failed. The AI Assistant will look at the failure message, and using our knowledge base, our docs, and the web, will come up with a reason why the failure occurred and suggestions on how to fix it. Assuming the suggestion is correct, the developer can quickly self-serve a solution without involving the platform engineer. The platform engineer benefits because they can focus on high-value tasks instead of helping debug a specific deployment failure. If the platform engineer didn’t know the answer, they’d go through our docs or do a Google search.</p>
  1951. <p>Now that we understand the two kinds of users, let’s examine what happens when a person is both the beneficiary and the end user.</p>
  1952. <h2 id="learning-the-wrong-lessons-from-the-success-of-chatgpt">Learning the wrong lessons from the success of ChatGPT</h2>
  1953. <p>ChatGPT and similar tools are unique because their users are both the beneficiary and the end user.</p>
  1954. <p>One of many benefits of ChatGPT is that it is an evolution of search engines. Before ChatGPT, you did a Google search, which returned a list of results. The search engine ranked the results for you. They had complex algorithms to find the best results based on their internal ranking system. A cottage SEO industry (Search Engine Optimization) sprang up to get higher results. ChatGPT changed that by providing you with answers curated from the content of many websites.</p>
  1955. <p>For common questions, with many sources agreeing on the same answer, the results between Google and ChatGPT are close. ChatGPT is not infallible; once, it insisted that Omaha, Nebraska, was 29 nautical miles from Chicago, Illinois. Google can be more accurate, but that is a result of maturity. They’ve had 25 years to improve and iterate their search results algorithm.</p>
  1956. <p>ChatGPT is popular because of the interface. It is very similar to the Google Search box. The results are where they differ. ChatGPT collates information and generates an answer that is easy to read. In addition, Google Searches are very transactional: search, get a result, move on with your day. With ChatGPT, the sessions are interactive. You can ask additional questions, and ChatGPT remembers the entire conversation.</p>
  1957. <p>I’m only focused on ChatGPT’s question/answer aspect. I know it can do so much more, including generating content, images, composing songs, and more.</p>
  1958. <p>Unfortunately, companies seem insistent on learning the wrong lessons when analyzing popular trends. They see that “people like prompts and providing answers or content to them. Let’s do that for [insert use case here]!”</p>
  1959. <h2 id="an-awful-user-experience-and-its-impact">An awful user experience and its impact</h2>
  1960. <p>That wrong lesson has its roots in computer graphic adventure games from the 1980s/early 1990s.</p>
  1961. <p>My first computer game was <a href="https://en.wikipedia.org/wiki/Space_Quest_III">Space Quest III</a> from Sierra. Like computer games of that era, I typed in commands to get the on-screen character to act. There was no help guide or tutorial. I had to figure it out. My brother and I spent <em>weeks</em> trying to escape the first area. We had to find the magic set of commands to execute in a specific sequence in specific areas.</p>
  1962. <p>Last year, I started the multi-month process of changing banks from a regional to a national bank. The national bank offered a high-yield savings account, while the regional bank didn’t. I had to call the national bank a few times. They have followed the latest trend in phone support. Dial the number, and a human-sounding voice asks you what you need help with. Too often, the response is “I’m sorry, I didn’t get that” or “I didn’t understand.”  I needed to know the magic phrase to get help. There was no clear escape hatch to get to an operator.</p>
  1963. <p>Their online AI help agent was no better. The AI help agent was trained on their public documents. If the answer wasn’t in the documents, it couldn’t help me. Often, it referred me to calling their support line, creating an endless cycle of frustration.</p>
  1964. <p>That experience was so bad that I went back to the regional bank. They proudly promote that you’ll talk to a real person when calling for help. I would rather lose thousands of dollars over many years than deal with the national bank’s awful AI-based help system.</p>
  1965. <p>I’m not the only one who hates talking to AI chatbots for support. The Commonwealth Bank of Australia (CBA) <a href="https://www.abc.net.au/news/2025-08-21/cba-backtracks-on-ai-job-cuts-as-chatbot-lifts-call-volumes/105679492">recently reversed its decision</a> to eliminate jobs after introducing an AI-powered help due to poor customer experience.</p>
  1966. <h2 id="augmenting-the-end-user-experience">Augmenting the end user experience</h2>
  1967. <p>The problem is that, just like humans, AI makes mistakes. Without appropriate settings, it will insist that it is correct. Where humans and AI differ is that AI is “book smart” but not “street smart,” but people can be “book smart” and “street smart.”  That means that humans use a combination of experiences and acquired knowledge to make decisions. Humans learn and evolve. At the same time, AI needs to be retrained. The best analogy came from Neil deGrasse Tyson on a <a href="https://www.youtube.com/watch?v=BYizgB2FcAQ">recent interview with Hasan Minhaj</a> - think of AI like Albert Einstein, but he is locked in a box. It is a sensory deprivation tank, where he does not know the outside world. Someone asks him random questions, and he responds with his current knowledge. He has no context outside the knowledge he has acquired before going into the box.</p>
  1968. <p>As a result, AI struggles with complex decisions. It doesn’t do well when something is outside the expected parameters. In a recent study from <a href="https://arxiv.org/pdf/2412.14161">Carnegie Mellon University and Duke University</a>, AI Agents are correct 30 to 35 percent for multi-step tasks. And the results depended on the model used, with GPT-4o achieving an 8.6% success rate. In a <a href="https://ml-site.cdn-apple.com/papers/the-illusion-of-thinking.pdf">recent study</a> by Apple Computers, many popular LRMs (Large Reasoning Models) models couldn’t handle puzzles (Tower of Hanoi, Checker Jumping, Block World, and River Crossing) once the number of pieces increased beyond simple examples. Today’s AI still has to undergo many more evolutions to become similar to <a href="https://en.wikipedia.org/wiki/J.A.R.V.I.S.">Tony Stark’s Jarvis</a> in the <abbr title="Marvel Cinematic Universe">MCU</abbr>.</p>
  1969. <p>I’m not against using AI. Far from it. However, it’s essential to understand its limitations when designing an end-user experience.</p>
  1970. <p>We’ve been very methodical in finding the proper use cases for our AI Assistant. We looked at how AI could augment the user experience. That means the Octopus AI Assistant <strong>is not</strong> intended to replace the current end-user interface. That would result in a sub-par experience, the opposite of augmentation.</p>
  1971. <p>The challenge we wanted to solve was surfacing the correct information for the users at the right time. We wanted to let the user ask AI for help and not annoy them with unwanted pop-ups or suggestions. We didn’t want to create Clippy 2.0 in the product.</p>
  1972. <p>Knowing that, our four use cases for the AI Assistant are:</p>
  1973. <ol>
  1974. <li><strong>Deployment Failure Analyzer</strong>: Read the logs of a failed deployment and offer suggestions to fix the issue.</li>
  1975. <li><strong>Tier-0 Support</strong>: Provide answers to end-users for common Octopus-related questions. For example, “summarize this deployment process” or “what’s a project?”</li>
  1976. <li><strong>Best Practices Analyzer</strong>: Using Octopus Deploy’s strong opinions, review the user’s instance to find areas for improvement.</li>
  1977. <li><strong>Prompt-Based Project Creation</strong>: Using templates provided by Octopus Deploy, create a new project to deploy to specific deployment targets.</li>
  1978. </ol>
  1979. <p>Interestingly, you don’t need AI to list the first three items. I can take a deployment failure, do a Google search, and likely produce similar results. Or, I can use our Octopus Linting tool, <a href="https://octopus.com/blog/octolint-best-practices">Octolint</a>, for best practices. AI is short-cutting all of that by collating all that information and surfacing it to the user. It’s enabling self-service for the end user.</p>
  1980. <p>But just as necessary, if the AI assistant can’t help, users can still ask their DevOps or Platform Engineers for help.</p>
  1981. <p>That is very different from using AI in hiring or AI-based help agents. They are replacement end-user interfaces. They don’t augment the user experience. Instead, they act as pseudo-gatekeepers to the hiring managers and provide support. They only focus on reducing the load for the beneficiary users. Most likely as a way for companies to cut costs or keep demand for additional headcount down. Unless you know someone at the hiring company or the magic phrase for AI Agent-based help, there are no alternatives.</p>
  1982. <p>But end users hate that experience. I believe that is one of the main reasons why <a href="https://www.ibm.com/thought-leadership/institute-business-value/en-us/c-suite-study/ceo">IBM found</a> that only 25% of AI initiatives have delivered the expected ROI over the past few years.</p>
  1983. <h2 id="considerations-for-the-end-user-experience">Considerations for the end user experience</h2>
  1984. <p>When designing the <a href="https://octopus.com/use-case/ai-assistant">Octopus AI assistant</a>, we started with multiple questions about augmenting the end-user experience. We didn’t want to “sprinkle AI” into the product and claim we had an AI strategy.</p>
  1985. <ol>
  1986. <li>What problem is the AI feature attempting to solve for the end user?</li>
  1987. <li>What is the fallback when the AI feature encounters an unknown use case?</li>
  1988. <li>What is an acceptable level of accuracy for the AI feature?</li>
  1989. <li>If the response is wrong, what is the escalation process for the end user?</li>
  1990. <li>How will the functionality be discovered?</li>
  1991. </ol>
  1992. <p>The answers for the deployment failure functionality of the AI Assistant are:</p>
  1993. <ol>
  1994. <li>Often, failures result from an incorrect configuration, transient error, bug in the script, permissions, or some other common problem. In many cases, it is outside the direct control of Octopus. Surface the information to the user to enable them to self-service the fix and decrease the recovery time.</li>
  1995. <li>Provide a generic answer and encourage the user to contact Octopus Support or their internal experts.</li>
  1996. <li>Reasonable accuracy is expected. Various conditions outside the control of Octopus Deploy can cause errors. Provide multiple suggestions using publicly available documentation. If none work, encourage the user to escalate to a human.</li>
  1997. <li>If the response doesn’t help, provide a link to Octopus Support or to contact their internal experts. In either case, they will escalate to a human.</li>
  1998. <li>When navigating to a failed deployment or runbook run, the Octopus AI Assistant will provide a suggestion that the user can click on to get the answer.</li>
  1999. </ol>
  2000. <p>The focus has been “How can we take what we have and make it better?”, not ” How can we ensure that Platform or DevOps engineers are never bothered again?”</p>
  2001. <h2 id="conclusion">Conclusion</h2>
  2002. <p>When an AI feature has a beneficiary user and end user, focus on providing a fantastic experience for the end user. Augment the end-user experience. But assume that at some point the AI will be incorrect (just like a person is incorrect), and offer a clear escalation path. Despite the many advances in AI, experienced people can handle complex scenarios much better. When the end-user isn’t considered, and the only focus is “improving the bottom line,” it creates an inferior replacement for an existing experience. End users will only put up with so much before they decide to change.</p>]]></content>
  2003.    </entry>
  2004.    <entry>
  2005.      <title>How organizations measure Platform Engineering</title>
  2006.      <link href="https://octopus.com/blog/how-organizations-measure-platform-engineering" />
  2007.      <id>https://octopus.com/blog/how-organizations-measure-platform-engineering</id>
  2008.      <published>2025-09-02T00:00:00.000Z</published>
  2009.      <updated>2025-09-02T00:00:00.000Z</updated>
  2010.      <summary>One of the areas we explored in the Platform Engineering Pulse report was how organizations measure their internal developer platforms, with results that varied from technical measures to not collecting any metrics at all.</summary>
  2011.      <author>
  2012.        <name>Steve Fenton, Octopus Deploy</name>
  2013.      </author>
  2014.      <content type="html"><![CDATA[<p>Our Platform Engineering Pulse report is coming soon, containing insights, strategies, and real-world data on how organizations adopt and succeed with Platform Engineering. We’ll also launch a survey to deepen our understanding of the patterns, challenges, and future direction of successful platform building.</p>
  2015. <p>One of the areas we explored in the <a href="https://octopus.com/publications/platform-engineering-pulse">Platform Engineering Pulse report</a> was how organizations measure their internal developer platforms, with results that varied from technical measures to not collecting any metrics at all.</p>
  2016. <p>This post looks at which metrics were popular and how you can use them to structure your measurement approach to avoid the zero-metric illusion.</p>
  2017. <h2 id="measurement-is-essential">Measurement is essential</h2>
  2018. <p>At the start of a <a href="https://octopus.com/devops/platform-engineering/">Platform Engineering</a> initiative, the immediate problems take precedence, and measurement becomes an afterthought. This leaves platform teams without a pre-platform baseline, which makes it challenging to demonstrate the platform’s impact.</p>
  2019. <p>The lack of measurement also increases the risk of platforms adding features that don’t align with the organization’s core motivations. For instance, a platform team might focus on standardization, only to discover that the primary driver for investing in Platform Engineering was to improve developer experience.</p>
  2020. <p>Platforms often fail not because they are inherently bad, but because they target the wrong personas and attempt to solve the wrong problems.</p>
  2021. <p>Even successful platforms struggle with ongoing justification and optimization when they lack clear goals, a robust measurement system that reflects those goals, and baseline data. The platform team may find leadership challenges continued investment if they can’t show the platform’s value.</p>
  2022. <p>Therefore, it is crucial to measure platform performance against its established goals. But how are organizations currently approaching this?</p>
  2023. <h2 id="what-organizations-measure">What organizations measure</h2>
  2024. <p>The survey found organizations focused on 3 key areas for measurement: Software delivery, operational performance, and user experience. The variety of metrics likely reflects the variety of contexts that platforms can assist, though is also evidence that metric systems are skewed to <a href="https://octopus.com/blog/productivity-delusion">the elusive <em>productivity problem</em></a> rather than true developer experience and engagement.</p>
  2025. <h3 id="software-delivery-metrics">Software delivery metrics</h3>
  2026. <p>The software delivery metrics assess the speed, quality, and throughput of development teams using the platform. They often align with DORA measurements, highlighting how platforms are seen as a route to shipping software more often and at higher quality. The metrics encompass throughput (e.g., deployment frequency, build time, features delivered) and stability (e.g., change failure rate, build success rate).</p>
  2027. <ul>
  2028. <li>Deployment frequency</li>
  2029. <li>Deployment times</li>
  2030. <li>Change failure rate</li>
  2031. <li>Recovery time</li>
  2032. <li>Build success rate</li>
  2033. <li>Build time</li>
  2034. <li>Features delivered</li>
  2035. </ul>
  2036. <p>While valuable for understanding delivery performance, these metrics primarily reflect downstream outcomes rather than the platform’s direct contribution to developer productivity. They are most effective when measured before and after platform adoption to demonstrate improvement.</p>
  2037. <h3 id="operational-performance-metrics">Operational performance metrics</h3>
  2038. <p>Metrics for operational performance track the cost, performance, and efficiency of applications that use the platform. They combine traditional infrastructure monitoring (e.g., reliability, error rates, system performance) with resource optimization (e.g., usage efficiency, cost management). Project count is a basic adoption indicator, but it lacks the context of the addressable market size.</p>
  2039. <ul>
  2040. <li>Reliability</li>
  2041. <li>Error rates</li>
  2042. <li>System performance</li>
  2043. <li>Resource usage</li>
  2044. <li>Infrastructure cost</li>
  2045. <li>Project count</li>
  2046. </ul>
  2047. <p>These metrics are crucial for demonstrating that the platform is operationally sound and cost-effective, but they don’t necessarily indicate developer value or ease of use.</p>
  2048. <h3 id="user-experience-metrics">User experience metrics</h3>
  2049. <p>These metrics directly measure how developers and teams perceive and interact with the platform, treating internal developers as customers. They focus on satisfaction and onboarding journeys. <a href="https://octopus.com/devops/metrics/platform-satisfaction/">Net promoter score (NPS)</a> offers benchmarkable sentiment data, while user satisfaction provides broader feedback. Onboarding time is critical as it represents the first impression and adoption barrier.</p>
  2050. <ul>
  2051. <li>User satisfaction</li>
  2052. <li>Net promoter score (NPS)</li>
  2053. <li>Onboarding time</li>
  2054. </ul>
  2055. <p>These metrics are vital for platforms run as products with optional adoption, indicating whether the platform is compelling enough for developers to choose and continue using voluntarily.</p>
  2056. <figure><p><img src="/blog/img/how-organizations-measure-platform-engineering/success-metrics.png" alt="Popular metrics include deployment frequency, reliability, build success rate, deployment times, and user satisfaction"></p></figure>
  2057. <p>Most organizations prioritize technical and delivery metrics, with fewer focusing on user experience or business outcomes. This suggests a risk that platform teams are measuring what’s easy to collect rather than what truly demonstrates business value. The heavy emphasis on technical metrics indicates that many organizations still measure platforms like infrastructure rather than products serving internal customers.</p>
  2058. <h2 id="how-measurement-improves-platform-performance">How measurement improves platform performance</h2>
  2059. <p>For metrics to improve platform performance, you need to measure multiple dimensions, not just one. Internal developer platforms offer many benefits, so the measurement system should cover all platform goals.</p>
  2060. <p>We found organizations that measure more dimensions were more likely to be successful. A single metric gives you a one-in-three chance of creating a successful platform, while 2 metrics make it 50/50. Organizations measuring 6 or more metrics were most likely to be successful.</p>
  2061. <p>Platform sponsors may expect you to deliver technical reliability, boost developer productivity, offer a positive user experience, manage costs, encourage adoption, and align with business objectives. Relying on just a handful of metrics cannot adequately capture the interplay of all these dimensions.</p>
  2062. <figure><p><img src="/blog/img/how-organizations-measure-platform-engineering/success-by-metric-count.png" alt="With a single metric, only a third of platforms achieve their goals, but platforms with 6 or more metrics have a 75% success rate"></p></figure>
  2063. <h2 id="breaking-the-success-illusion">Breaking the success illusion</h2>
  2064. <p>The data shows a zero-metric high-success effect. This occurs when organizations that don’t collect any concrete measures of the Platform Engineering effort report high success rates. This phenomenon comes from two very different situations masquerading as the same outcome.</p>
  2065. <p>There may be easy and obvious success criteria the platform can address, such as a critical and evident problem where success is undeniable without formal measurement. Where the effectiveness is apparent, formal measurements may be unnecessary. A more likely explanation is that there’s an illusion of success caused by a lack of measurement.</p>
  2066. <p>Without concrete metrics, platform teams can focus on outputs (e.g., features built, no outages) rather than actual outcomes (e.g., increased developer productivity, achievement of business goals). Stakeholders may mistake activity for impact, especially if the team is busy and there are no apparent failures.</p>
  2067. <p>This also helps explain the dramatic increase in success rates observed when moving from one metric to three or more. Teams relying on a single metric might choose one that doesn’t capture holistic success, leading to a false sense of achievement. However, when multiple dimensions are measured, maintaining illusions becomes difficult. For instance, you can’t claim unqualified success if deployment frequency is high but Net Promoter Score (NPS) is low.</p>
  2068. <p>The data also suggests a dangerous middle ground where minimal measurement can create more problems than no measurement. It provides false confidence without the comprehensive feedback necessary for genuine improvement.</p>
  2069. <figure><p><img src="/blog/img/how-organizations-measure-platform-engineering/success-illusion.png" alt="When organizations don&#x27;t measure their Platform Engineering initiative, they operate under the illusion of success"></p></figure>
  2070. <h2 id="using-monk-metrics">Using MONK metrics</h2>
  2071. <p><a href="https://octopus.com/devops/metrics/monk-metrics/">MONK metrics</a> offer a balanced approach to measuring Platform Engineering success, combining external validation and internal alignment. This framework is more adaptable than purely technical metrics, yet still provides a standardized basis for comparison and improvement.</p>
  2072. <p>The MONK metrics are:</p>
  2073. <ul>
  2074. <li>Market share</li>
  2075. <li>Onboarding times</li>
  2076. <li>Net Promoter Score (NPS)</li>
  2077. <li>Key customer metrics</li>
  2078. </ul>
  2079. <p>The first three, market share, onboarding times, and NPS, form a benchmarkable trio. These metrics allow for comparison against industry standards and are broadly applicable to Platform Engineering initiatives, serving as a starting point to understand high performance in other organizations. For example, if your onboarding times are slower than those of your industry peers, you can investigate their methods and implement similar improvements.</p>
  2080. <p>Including “key customer metrics” is crucial for preventing the measurement framework from losing touch with business realities. Organizations kick off Platform Engineering to solve specific organizational problems, but their success is measured using generic technical metrics that don’t reflect the original investment’s purpose. Instead, it’s essential to translate the motivations behind adopting Platform Engineering into the key customer metrics to track progress towards the platform’s objectives effectively.</p>
  2081. <p>MONK metrics address the common issue of platform teams optimizing for metrics that appear favorable but don’t contribute to actual business value. For instance, if you introduce Platform Engineering to reduce time-to-market, measures of infrastructure uptime or build failures fail to track tangible improvements in delivery flow.</p>
  2082. <h2 id="lack-of-measurement-makes-your-platform-vulnerable">Lack of measurement makes your platform vulnerable</h2>
  2083. <p>Without measurement, platform teams end up in a vulnerable position. They can’t demonstrate their value when budget discussions come up, they can’t identify what’s working versus what needs improvement, and they can’t make compelling cases for continued investment or expansion.</p>
  2084. <p>The absence of data also hinders course correction. Platform Engineering involves complex trade-offs between developer experience, operational efficiency, security, and cost. Without clear metrics, teams may prioritize visible or politically expedient solutions over those that deliver actual value.</p>
  2085. <p>MONK metrics offer a practical solution. They are accessible, enabling organizations to begin measuring with minimal tooling or data infrastructure. You can gather basic market share data through surveys, track onboarding times simply, and get NPS using lightweight feedback tools.</p>
  2086. <p>Their benchmarkable nature also helps address the common “what’s good enough?” dilemma. Instead of abstractly debating whether a two-day onboarding time is acceptable, teams can compare their performance to similar organizations.</p>
  2087. <p>Happy deployments!</p>]]></content>
  2088.    </entry>
  2089.    <entry>
  2090.      <title>Platform Engineering and woodworking</title>
  2091.      <link href="https://octopus.com/blog/platform-engineering-and-woodworking" />
  2092.      <id>https://octopus.com/blog/platform-engineering-and-woodworking</id>
  2093.      <published>2025-08-26T00:00:00.000Z</published>
  2094.      <updated>2025-08-26T00:00:00.000Z</updated>
  2095.      <summary>What is something that woodworkers, blacksmiths, and programmers have in common? One answer is that practitioners of these crafts have the unique ability to make their own tools.</summary>
  2096.      <author>
  2097.        <name>Paul Stovell, Octopus Deploy</name>
  2098.      </author>
  2099.      <content type="html"><![CDATA[<p>What is something that woodworkers, blacksmiths, and programmers have in common? One answer is that practitioners of these crafts have the unique ability to make their own tools.</p>
  2100. <p>In fact, making your own tools is so essential to these crafts that tool-making is part of their training and tradition. Hammers, tongs, and chisels are among the first items an apprentice blacksmith learns to make. The first projects for an amateur woodworker are often to build a workbench, a circular saw guide, sawhorses, or a cross-cut sled. Tool-making is so essential to these crafts that it’s not uncommon to detour midway through a production project into making a tool to assist with the project, a detour that becomes a natural part of the work process.</p>
  2101. <p>Programmers are, perhaps, the ultimate tool-makers. Every programmer has a collection of scripts and utility programs they’ve built to make small tasks more productive. Octopus Deploy started life as a collection of automation scripts I would take from project to project in my consulting days. Tool-making isn’t just a way to gain productivity; it’s also immensely satisfying. It gives us extensive control over our work environment: unlike every other profession, we don’t need to put up with bad tools, because we can always make our own.</p>
  2102. <h2 id="when-making-the-tool-consumes-the-project">When making the tool consumes the project</h2>
  2103. <p>Tool-making is never free, however. In woodworking or blacksmithing, it consumes raw materials and time. For programmers, we don’t have a raw material constraint (except perhaps coffee, the raw material that programmers turn into working code!), but we do have a time constraint. And the complexity of software and the optimistic nature of programmers mean that we often underestimate how difficult a particular tool can be to make.</p>
  2104. <p>For example, a programmer may need to complete a task that takes 5 minutes, like resetting the local test data they use in the application they are working on. And they might perform that task 5 or 6 times a month. So it’s quite common for good programmers to take an hour or so to create a script or a small utility program to automate the task. Even if the ROI calculation means the time they save automating isn’t going to be paid back for a long time, it can still be a smart thing to do if it results in less context switching, better accuracy, or is simply more satisfying.</p>
  2105. <p>As organizations scale their Continuous Delivery practices, this craft approach can lead to an unexpected problem: the tool-building starts consuming more resources than the actual work. Teams that began by automating a few deployment scenarios find themselves spending months building elaborate internal platforms to handle every edge case across dozens of applications. A deployment pipeline that started as a simple script to push a web application to production grows into a complex system supporting microservices, legacy mainframes, mobile apps, and that one critical application written in a programming language nobody wants to touch.</p>
  2106. <p>The irony is striking. The organization wants to create valuable software for its customers, but engineering teams are spending more and more time maintaining their custom-built tooling instead of creating features. Like a woodworker who spends more time perfecting their jigs than crafting furniture, these teams have lost sight of what they’re really trying to accomplish.</p>
  2107. <h2 id="platform-engineering-and-developer-experience">Platform Engineering and developer experience</h2>
  2108. <p>Platform Engineering is downstream of developer experience, even when it’s not the primary motivation for building an internal developer platform. Removing barriers to let developers improve the flow of value benefits the whole organization. Getting software into production and running that software in production is hard, and it wastes time for each team to reinvent solutions to this from first principles.</p>
  2109. <p>The best platform teams never lose sight of that developer experience. They build thin platforms that let the innovation of underlying tools accelerate their pace, so their platform can remain market-leading over the long term and at a lower cost than chasing that innovation with a custom-built platform. We often refer to internal developer platforms as the glue. What if we changed that to think of these platforms as the minimal jig that helps fit the professional-grade saws and drills to the shape the organization needs?</p>
  2110. <p>The platform, like the jig, should never become the build. When it does, it gets in the way and slows developers down instead of being their force multiplier.</p>
  2111. <h2 id="platform-hub-shifting-complexity-away-from-platform-engineers">Platform Hub: Shifting complexity away from Platform Engineers</h2>
  2112. <p>We built Platform Hub to lighten the load for platform builders. When they need to support Continuous Delivery at scale, Platform Hub tackles the complexity of template management and policy guardrails. The traditional approach is to create a template and clone it across all your applications, then face the nightmare of keeping hundreds of copies in sync as requirements evolve.</p>
  2113. <p>Platform Hub solves the cloning problem by keeping a single versioned template connected to all the places you use it. You can roll out non-breaking changes automatically and track the progress of significant updates. You can even require crucial steps like security scanning using policies, so you know all your deployments meet your standards.</p>
  2114. <p>By handling this complexity below the platform level, your teams can focus on what matters: shaping the tools to fit your organization’s specific needs while letting professional-grade tooling handle the heavy lifting. Less time building jigs; more time creating value.</p>
  2115. <p>Happy deployments!</p>]]></content>
  2116.    </entry>
  2117.    <entry>
  2118.      <title>Migrating Octopus projects to Terraform with Octoterra</title>
  2119.      <link href="https://octopus.com/blog/importing-terraform-projects" />
  2120.      <id>https://octopus.com/blog/importing-terraform-projects</id>
  2121.      <published>2025-08-25T00:00:00.000Z</published>
  2122.      <updated>2025-08-25T00:00:00.000Z</updated>
  2123.      <summary>Learn how to bring existing Octopus projects under Terraform management with Octoterra.</summary>
  2124.      <author>
  2125.        <name>Matthew Casperson, Octopus Deploy</name>
  2126.      </author>
  2127.      <content type="html"><![CDATA[<p>With the release of <a href="https://registry.terraform.io/providers/OctopusDeploy/octopusdeploy/latest/docs">version 1 of the Octopus Terraform Provider</a>, DevOps teams can now manage their Octopus resources using a fully supported Terraform based Infrastructure as Code (IaC) solution.</p>
  2128. <p>New teams populating their first Octopus spaces can take advantage of the Terraform provider from the outset. However, it is arguably established teams with existing Octopus projects that will benefit the most from IaC capabilities. But how do you migrate existing Octopus projects to Terraform?</p>
  2129. <p>In this post we will cover how the Octoterra tool can bring existing projects under Terraform management.</p>
  2130. <h2 id="what-is-octoterra">What is Octoterra?</h2>
  2131. <p><a href="https://github.com/OctopusSolutionsEngineering/OctopusTerraformExport">Octoterra</a> is a CLI tool that scans an existing Octopus space and converts the projects, runbooks, and other resources into Terraform configuration files.</p>
  2132. <p>A new feature in Octoterra generates Bash and PowerShell scripts to reimport the existing resources into the state file of the exported Terraform configuration. This allows teams to:</p>
  2133. <ol>
  2134. <li>Export existing Octopus resources into Terraform configuration files.</li>
  2135. <li>Import the configuration of existing resources into the Terraform state file associated with the exported configuration.</li>
  2136. <li>Manage the existing resources using Terraform going forward.</li>
  2137. </ol>
  2138. <h2 id="exporting-an-existing-octopus-project">Exporting an existing Octopus project</h2>
  2139. <p>Octoterra is distributed as self-contained binaries for Windows, Linux, and macOS, available from the <a href="https://github.com/OctopusSolutionsEngineering/OctopusTerraformExport/releases">GitHub releases page</a>.</p>
  2140. <p>You can also run Octoterra as a Docker container, which is often the simplest way to get started. The following command will run Octoterra in a Docker container, exporting the configuration of an existing Octopus project into the current directory:</p>
  2141. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">docker</span><span style="color:#A31515"> run</span><span style="color:#0000FF"> -v</span><span style="color:#001080"> $PWD</span><span style="color:#A31515">:/tmp/octoexport</span><span style="color:#0000FF"> --rm</span><span style="color:#A31515"> ghcr.io/octopussolutionsengineering/octoterra</span><span style="color:#EE0000"> \</span></span>
  2142. <span class="line"><span style="color:#000000">-url </span><span style="color:#A31515">https://instance.octopus.app</span><span style="color:#EE0000"> \</span></span>
  2143. <span class="line"><span style="color:#000000">-space </span><span style="color:#A31515">Spaces-##</span><span style="color:#EE0000"> \</span></span>
  2144. <span class="line"><span style="color:#000000">-apiKey </span><span style="color:#A31515">API-xxxx</span><span style="color:#EE0000"> \</span></span>
  2145. <span class="line"><span style="color:#000000">-projectName </span><span style="color:#A31515">"My Project"</span><span style="color:#EE0000"> \</span></span>
  2146. <span class="line"><span style="color:#000000">-lookupProjectDependencies </span><span style="color:#EE0000">\</span></span>
  2147. <span class="line"><span style="color:#000000">-generateImportScripts </span><span style="color:#EE0000">\</span></span>
  2148. <span class="line"><span style="color:#000000">-dest </span><span style="color:#A31515">/tmp/octoexport</span></span></code></pre>
  2149. <p>The Octoterra arguments from this example are:</p>
  2150. <ul>
  2151. <li><code>-url</code>: The URL of the Octopus server.</li>
  2152. <li><code>-space</code>: The ID of the Octopus space containing the project.</li>
  2153. <li><code>-apiKey</code>: The API key to authenticate with the Octopus server.</li>
  2154. <li><code>-projectName</code>: The name of the project to export.</li>
  2155. <li><code>-lookupProjectDependencies</code>: Enables the use of data sources to look up the IDs of space-level resources such as environments, lifecycles, and variables. This exports the project as a self-contained Terraform configuration referencing existing space-level resources by name.</li>
  2156. <li><code>-generateImportScripts</code>: Generates Bash and PowerShell scripts to locate and import the existing resources into the Terraform state file.</li>
  2157. <li><code>-dest</code>: The destination directory to write the exported Terraform configuration files and import scripts. When using a Docker container, this directory must be mounted as a volume to allow the container to write files to the host filesystem.</li>
  2158. </ul>
  2159. <p>For this post we’ll export a simple project called “My Project” that has a single deployment step running a PowerShell script and a channel called HotFix. The project also has a variable scoped to an environment and a channel.</p>
  2160. <p><img  src="/blog/_astro/process-steps.ty0Y7JtZ_1z4GNq.webp" alt="Octopus deployment project steps screenshot" loading="lazy" decoding="async" fetchpriority="auto" width="3840" height="2150"></p>
  2161. <p><img  src="/blog/_astro/project-variables.BZYnDYfU_iqGM7.webp" alt="Octopus project variables screenshot" loading="lazy" decoding="async" fetchpriority="auto" width="3840" height="2150"></p>
  2162. <p>Once the Terraform configuration files have been generated, run the following command in the <code>space_population</code> directory to initialize the directory containing the exported Terraform configuration files:</p>
  2163. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">terraform</span><span style="color:#A31515"> init</span></span></code></pre>
  2164. <p>Because we have supplied the <code>-generateImportScripts</code> argument, Octoterra will generate Bash and PowerShell scripts to reimport the project into the Terraform state file. All the script file names start with the prefix <code>import_</code>. In addition, two scripts called <code>import.sh</code> and <code>import.ps1</code> are generated which run all the other import scripts, providing a convenient way to import all the resources in the exported configuration.</p>
  2165. <p>If running on Linux or macOS, the scripts must be made executable before they can be run:</p>
  2166. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">chmod</span><span style="color:#A31515"> +x</span><span style="color:#0000FF"> *</span><span style="color:#A31515">.sh</span></span></code></pre>
  2167. <p>The import scripts can then be run to import the existing resources into the Terraform state file. The following command runs the Bash script to import the existing resources:</p>
  2168. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">./import.sh</span><span style="color:#A31515"> API-xxx</span><span style="color:#A31515"> https://instance.octopus.app</span><span style="color:#A31515"> Spaces-##</span></span></code></pre>
  2169. <p>If running on Windows, the PowerShell script can be run using the following command:</p>
  2170. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="powershell"><code><span class="line"><span style="color:#000000">.\import.ps1 API-xxx https://instance.octopus.app Spaces-#</span><span style="color:#008000">#</span></span></code></pre>
  2171. <p>You may need to allow PowerShell to run unsigned scripts by setting the <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.5">execution policy</a>.</p>
  2172. <p>The import scripts first attempt to find the matching resources in the target space by name, and if found, import the resources into the Terraform state file using the <code>terraform import</code> command.</p>
  2173. <div class="hint"><p>In this example we have used the default local state. Production environments should use <a href="https://developer.hashicorp.com/terraform/language/state/remote">remote state</a>, such as an S3 bucket or Azure Storage Account, to ensure the Terraform state is stored securely and can be accessed by all team members.</p></div>
  2174. <h2 id="checking-the-terraform-state">Checking the Terraform state</h2>
  2175. <p>Once the project is imported, you can run <code>terraform plan</code> to see any differences between the generated Terraform configuration files and the imported state:</p>
  2176. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">terraform</span><span style="color:#A31515"> plan</span><span style="color:#EE0000"> \</span></span>
  2177. <span class="line"><span style="color:#0000FF">  -var=octopus_apikey=API-xxxxx</span><span style="color:#EE0000"> \</span></span>
  2178. <span class="line"><span style="color:#0000FF">  -var=octopus_server=https://instance.octopus.app</span><span style="color:#EE0000"> \</span></span>
  2179. <span class="line"><span style="color:#0000FF">  -var=octopus_space_id=Spaces-</span><span style="color:#000000">###</span></span></code></pre>
  2180. <p>There are <a href="https://github.com/OctopusDeploy/terraform-provider-octopusdeploy/issues">open issues in the Terraform provider</a> at the time of writing that lead to the plan showing differences between the generated configuration and the imported state. For example, the <code>octopusdeploy_variable</code> resources may show fields like <code>is_editable</code>, <code>is_sensitive</code>, and <code>value</code> will be added, again when these are no-op changes.</p>
  2181. <p>Future releases of the Terraform provider and Octoterra will resolve these import differences, but for now, it is expected that the plan will show some differences between the generated configuration and the imported state.</p>
  2182. <p>That said, the primary purpose of the import scripts is to allow the exported Terraform configuration to be reapplied to the existing Octopus resources while avoiding errors about resources already existing in the target space.</p>
  2183. <p>This is an example of the plan output from my sample project showing the differences:</p>
  2184. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:</span></span>
  2185. <span class="line"><span>  ~ update in-place</span></span>
  2186. <span class="line"><span></span></span>
  2187. <span class="line"><span>Terraform will perform the following actions:</span></span>
  2188. <span class="line"><span></span></span>
  2189. <span class="line"><span>  # octopusdeploy_project.project_my_project will be updated in-place</span></span>
  2190. <span class="line"><span>  ~ resource "octopusdeploy_project" "project_my_project" {</span></span>
  2191. <span class="line"><span>        id                                   = "Projects-9441"</span></span>
  2192. <span class="line"><span>        name                                 = "My Project"</span></span>
  2193. <span class="line"><span>        # (18 unchanged attributes hidden)</span></span>
  2194. <span class="line"><span></span></span>
  2195. <span class="line"><span>      + connectivity_policy {</span></span>
  2196. <span class="line"><span>          + allow_deployments_to_no_targets = true</span></span>
  2197. <span class="line"><span>          + exclude_unhealthy_targets       = false</span></span>
  2198. <span class="line"><span>          + skip_machine_behavior           = "None"</span></span>
  2199. <span class="line"><span>          + target_roles                    = []</span></span>
  2200. <span class="line"><span>        }</span></span>
  2201. <span class="line"><span></span></span>
  2202. <span class="line"><span>      + versioning_strategy {</span></span>
  2203. <span class="line"><span>          + template = "#{Octopus.Version.LastMajor}.#{Octopus.Version.LastMinor}.#{Octopus.Version.NextPatch}"</span></span>
  2204. <span class="line"><span>        }</span></span>
  2205. <span class="line"><span>    }</span></span>
  2206. <span class="line"><span></span></span>
  2207. <span class="line"><span>  # octopusdeploy_variable.my_project_project_test_variable_1 will be updated in-place</span></span>
  2208. <span class="line"><span>  ~ resource "octopusdeploy_variable" "my_project_project_test_variable_1" {</span></span>
  2209. <span class="line"><span>        id           = "e1b1bb15-d61e-d241-316d-651e495b46e1"</span></span>
  2210. <span class="line"><span>      + is_editable  = true</span></span>
  2211. <span class="line"><span>      + is_sensitive = false</span></span>
  2212. <span class="line"><span>        name         = "Project.Test.Variable"</span></span>
  2213. <span class="line"><span>      + value        = "whatever"</span></span>
  2214. <span class="line"><span>        # (4 unchanged attributes hidden)</span></span>
  2215. <span class="line"><span></span></span>
  2216. <span class="line"><span>        # (1 unchanged block hidden)</span></span>
  2217. <span class="line"><span>    }</span></span>
  2218. <span class="line"><span></span></span>
  2219. <span class="line"><span>Plan: 0 to add, 2 to change, 0 to destroy.</span></span>
  2220. <span class="line"><span>╷</span></span>
  2221. <span class="line"><span>│ Warning: Block Deprecated</span></span>
  2222. <span class="line"><span>│ </span></span>
  2223. <span class="line"><span>│   with octopusdeploy_project.project_my_project,</span></span>
  2224. <span class="line"><span>│   on project_project_my_project.tf line 36, in resource "octopusdeploy_project" "project_my_project":</span></span>
  2225. <span class="line"><span>│   36: resource "octopusdeploy_project" "project_my_project" {</span></span>
  2226. <span class="line"><span>│ </span></span>
  2227. <span class="line"><span>│ octopusdeploy_project.versioning_strategy is deprecated in favor of resource octopusdeploy_project_versioning_strategy. See</span></span>
  2228. <span class="line"><span>│ https://oc.to/deprecation-tfp-project-versioning-strategy for more info and migration guidance.</span></span></code></pre>
  2229. <p>It is good practice to run <code>terraform plan</code> after importing the resources to ensure that the Terraform configuration files match the state of the Octopus resources, and manually review the generated Terraform configuration files if needed to confirm they are correct.</p>
  2230. <h2 id="backing-up-your-octopus-instance">Backing up your Octopus instance</h2>
  2231. <p>Before making any changes to production resources, it is highly recommended that you <a href="https://octopus.com/docs/administration/data/backup-and-restore">backup the Octopus database</a>. While changes to projects are tracked in the Octopus audit log, having a backup of the database allows you to restore the state of your Octopus instance if something goes wrong.</p>
  2232. <p>You may also consider testing the export and import process on a test instance of Octopus running a copy of your production data, or on a cloned project. This allows you to verify the export and import process works as expected before applying it to your production projects.</p>
  2233. <h2 id="applying-the-terraform-configuration">Applying the Terraform configuration</h2>
  2234. <p>Once you are satisfied that the plan does not show any unexpected changes, you can run <code>terraform apply</code> to apply the configuration:</p>
  2235. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">terraform</span><span style="color:#A31515"> apply</span><span style="color:#EE0000"> \</span></span>
  2236. <span class="line"><span style="color:#0000FF">  -var=octopus_apikey=API-xxxxx</span><span style="color:#EE0000"> \</span></span>
  2237. <span class="line"><span style="color:#0000FF">  -var=octopus_server=https://instance.octopus.app</span><span style="color:#EE0000"> \</span></span>
  2238. <span class="line"><span style="color:#0000FF">  -var=octopus_space_id=Spaces-</span><span style="color:#000000">###</span></span></code></pre>
  2239. <p>This will apply the Terraform configuration back to Octopus. We expect this operation to make no changes to the project, as the exported configuration matches the state of the project in Octopus.</p>
  2240. <p>Once the apply operation is complete, the project is managed by Terraform, and you can make any future changes to the project by editing and reapplying the Terraform configuration.</p>
  2241. <h2 id="dealing-with-sensitive-variables">Dealing with sensitive variables</h2>
  2242. <p>Octoterra reads the state of Octopus projects and variables via the API. The API does not export the values of sensitive variables, so Octoterra can not include sensitive values in the exported Terraform configuration files.</p>
  2243. <p>To manage sensitive variables, you can either:</p>
  2244. <ul>
  2245. <li>Pass the value of sensitive variables as Terraform variables when running <code>terraform apply</code>, as Octoterra creates Terraform variables to define the values of all exported sensitive variables.</li>
  2246. <li>Exclude sensitive variables from the exported configuration and manage them separately in Octopus.</li>
  2247. </ul>
  2248. <p>To exclude variables from the exported configuration, you can use the <code>-excludeProjectVariable</code> argument when running Octoterra. This argument can be passed multiple times to exclude multiple variables from the exported configuration.</p>
  2249. <p>For example, if the project had a sensitive variable called <code>Project.Test.Secret</code> that we wished to exclude from the exported configuration, we would run the following command:</p>
  2250. <pre class="astro-code light-plus" style="background-color:#FFFFFF;color:#000000; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#795E26">docker</span><span style="color:#A31515"> run</span><span style="color:#0000FF"> -v</span><span style="color:#001080"> $PWD</span><span style="color:#A31515">:/tmp/octoexport</span><span style="color:#0000FF"> --rm</span><span style="color:#A31515"> ghcr.io/octopussolutionsengineering/octoterra</span><span style="color:#EE0000"> \</span></span>
  2251. <span class="line"><span style="color:#0000FF">  -url</span><span style="color:#A31515"> https://instance.octopus.app</span><span style="color:#EE0000"> \</span></span>
  2252. <span class="line"><span style="color:#0000FF">  -space</span><span style="color:#A31515"> Spaces-##</span><span style="color:#EE0000"> \</span></span>
  2253. <span class="line"><span style="color:#0000FF">  -apiKey</span><span style="color:#A31515"> API-xxxx</span><span style="color:#EE0000"> \</span></span>
  2254. <span class="line"><span style="color:#0000FF">  -projectName</span><span style="color:#A31515"> "My Project"</span><span style="color:#EE0000"> \</span></span>
  2255. <span class="line"><span style="color:#0000FF">  -lookupProjectDependencies</span><span style="color:#EE0000"> \</span></span>
  2256. <span class="line"><span style="color:#0000FF">  -generateImportScripts</span><span style="color:#EE0000"> \</span></span>
  2257. <span class="line"><span style="color:#0000FF">  -excludeProjectVariable</span><span style="color:#A31515"> Project.Test.Secret</span><span style="color:#EE0000"> \</span></span>
  2258. <span class="line"><span style="color:#0000FF">  -dest</span><span style="color:#A31515"> /tmp/octoexport</span></span></code></pre>
  2259. <p>This will exclude the <code>Project.Test.Secret</code> variable from the exported configuration, meaning it is not managed by Terraform, allowing you to manage it separately in the Octopus UI.</p>
  2260. <h2 id="making-manual-changes-to-the-exported-configuration">Making manual changes to the exported configuration</h2>
  2261. <p>The exported Terraform configuration files can be manually edited to apply any customizations or address any issues you may find. The beauty of Terraform and the Octopus Terraform provider is that the configuration files use an open, documented, and editable format.</p>
  2262. <p>You have complete control and ownership of the configuration once it is exported, and are free to make any changes you need.</p>
  2263. <h2 id="conclusion">Conclusion</h2>
  2264. <p>Customers with existing Octopus projects can now place them under Terraform management using the Octoterra tool. By exporting the projects to their equivalent Terraform configuration, importing the state, and applying the configuration, Octopus projects can be effectively migrated in-place with minimal disruption.</p>]]></content>
  2265.    </entry>
  2266. </feed>

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid Atom 1.0" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=http%3A//feeds.feedburner.com/OctopusDeploy

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda