<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Octopus blog</title>
  <subtitle>Site description.</subtitle>
  <link href="https://octopus.com/blog/feed.xml" rel="self" />
  <link href="https://octopus.com" />
  <id>https://octopus.com/blog/feed.xml</id>
  <updated>2026-08-07</updated>

    <entry>
      <title>Octopus Easy Mode - Progressive Rollout</title>
      <link href="https://octopus.com/blog/octo-easy-mode-18-progressive-rollouts" />
      <id>https://octopus.com/blog/octo-easy-mode-18-progressive-rollouts</id>
      <published>2026-08-07</published>
      <updated>2026-08-07</updated>
      <summary>Learn how to create a progressive deployment project</summary>
      <author>
        <name>Matthew Casperson, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Progressive rollouts allow DevOps teams to deploy a release to a small subset of production users before rolling it out to the entire user base. This approach reduces risk by allowing teams to validate the release in production and catch any issues before they affect all users. Typically, the rollout automatically promotes a new version of an application to increasingly larger percentages of production users, like 10%, 50%, and finally 100%. If there is an error, the rollout is halted.</p>
<p>The <a href="https://docs.aws.amazon.com/wellarchitected/latest/devops-guidance/dl.ads.3-use-staggered-deployment-and-release-strategies.html">AWS Well-Architected framework recommends staggered deployments</a>, noting that:</p>
<blockquote>
<p>These techniques contribute to safer and more reliable software deployment and release processes.</p>
</blockquote>
<p>In the <a href="/blog/octo-easy-mode-17-claude">previous post</a>, you created a project that used a Claude agent step to categorize commits.</p>
<p>In this post, you will create a sample project that demonstrates a progressive rollout through multiple production environments.</p>
<p><a href="/blog/easymode">Return to the series index.</a></p>
<h2>Prerequisites</h2>
<ul>
<li>An <a href="https://octopus.com/start">Octopus Cloud</a> account. If you don't have one, you can sign up for a free trial.</li>
<li>The Octopus AI Assistant Chrome extension. You can install it from the <a href="https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha">Chrome Web Store</a>.</li>
</ul>
<p>:::div{.hint}
The Octopus AI Assistant will work with an on-premises Octopus instance, but it requires more configuration. The
cloud-hosted version of Octopus doesn't need extra configuration. This means the cloud-hosted version is the easiest way to get started.
:::</p>
<h2>Creating the project</h2>
<p>Paste the following prompt into the Octopus AI Assistant and run it to create a sample project with a progressive rollout:</p>
<pre><code class="language-markdown">Create a new progressive deployment project called "18. Progressive rollout".
</code></pre>
<p>The resulting project models a gradual production rollout by promoting the same release through progressively larger slices of production.</p>
<p>The AI Assistant creates a lifecycle with the environments <code>Prod 10</code>, <code>Prod 50</code>, and <code>Prod 100</code>. The lifecycle captures the different stages of the rollout as a percentage of production traffic, enforces the deployment order, and has the project deploy a release to each environment in turn.</p>
<h2>How the progressive rollout works</h2>
<p>The project creates a custom lifecycle called <code>Progressive</code> with four phases:</p>
<ul>
<li><code>Development</code></li>
<li><code>Prod 10</code></li>
<li><code>Prod 50</code></li>
<li><code>Prod 100</code></li>
</ul>
<p>Each lifecycle phase targets a single environment, and the project uses a runbook to explicitly trigger the next deployment after the current one succeeds.</p>
<p>The deployment process starts with a <code>Deploy App</code> step that simulates deploying an application by printing <code>Deploying app</code> to the task log. It is followed by a <code>Simulate Failure</code> step that acts as a validation gate. This step checks the prompted variable <code>Project.SimulateFail</code>, and if it is set to <code>True</code>, the deployment exits with an error and the rollout stops.</p>
<p>If the validation step succeeds, the process runs a community step template called <code>Run Octopus Deploy Runbook</code>. This step starts a runbook named <code>Deploy Release</code> to promote the current release to the next environment. This works around a limitation where Octopus prevents a deployment to the next environment until the current one is complete, so you cannot trigger a deployment to <code>Prod 50</code> while the <code>Prod 10</code> deployment is still running. By having a runbook trigger the deployment after a short delay, we can be sure the current deployment has completed before the next one starts.</p>
<p>The <code>Run Octopus Deploy Runbook</code> step is configured to run in the <code>Prod 10</code> and <code>Prod 50</code> environments. It dynamically chooses the next environment with the following logic:</p>
<ul>
<li>When the current environment is <code>Prod 10</code>, it triggers a deployment to <code>Prod 50</code></li>
<li>When the current environment is <code>Prod 50</code>, it triggers a deployment to <code>Prod 100</code></li>
</ul>
<p>The step also passes the current release ID into the runbook as the prompted variable <code>Project.Release.Id</code>, ensuring the same release is promoted through each stage of the rollout.</p>
<p>The runbook itself contains a single <code>Sleep</code> step that waits for 60 seconds before using the Octopus API to create the next deployment. This pause allows the current deployment to complete before the next rollout stage begins.</p>
<p>In practice, the rollout looks like this:</p>
<ul>
<li>You create a release and deploy it to <code>Development</code></li>
<li>You promote the release to <code>Prod 10</code></li>
<li>The <code>Run Octopus Deploy Runbook</code> step automatically starts the <code>Deploy Release</code> runbook</li>
<li>The runbook waits 60 seconds and then creates a deployment of the same release to <code>Prod 50</code></li>
<li>When the <code>Prod 50</code> deployment succeeds, the same pattern is used to create the final deployment to <code>Prod 100</code></li>
<li>If there are any failures, the rollout stops</li>
</ul>
<h2>Customizing the rollout</h2>
<p>The <code>Deploy Release</code> runbook initiates a deployment to the next production environment after a short delay. This may be customized to instead <a href="https://octopus.com/docs/projects/project-triggers/scheduled-deployment-trigger">schedule a deployment at a specific time</a>, which allows the rollout to be paused for a longer period of time before continuing. You could, for example, only roll out to 100% of production traffic during off-peak hours, or after the release has been validated in <code>Prod 50</code> for a full day.</p>
<p>You may also consider <a href="https://octopus.com/docs/releases/prevent-release-progression">preventing release progression</a> if a deployment fails. This ensures that a failed release cannot be promoted to the next environment until the issue is resolved. A blocked release will also prevent any scheduled deployments from taking place.</p>
<h2>Comparing tenants and environments</h2>
<p>This example used environments to represent progressive rollouts. It is also possible to use tenants to represent progressive rollouts. However, there are benefits to using environments:</p>
<ul>
<li>Environments are easier to visualize in the Octopus UI</li>
<li>Lifecycles enforce the progression of releases through environments, which in turn progressively advance the rollout</li>
<li>The ability to block release progression after a successful deployment is only available for environments, not tenants</li>
</ul>
<p>For these reasons, environments are the recommended approach for modeling progressive rollouts in Octopus.</p>
<h2>What just happened?</h2>
<p>You created a sample project with:</p>
<ul>
<li>A custom <a href="https://octopus.com/docs/releases/lifecycles">lifecycle</a> called <code>Progressive</code> that promotes releases through <code>Development</code>, <code>Prod 10</code>, <code>Prod 50</code>, and <code>Prod 100</code></li>
<li>A scripted deployment process that simulates an application deployment and then validates the result before continuing</li>
<li>A prompted variable that can intentionally fail the validation step to stop the rollout</li>
<li>A community step template that runs a <code>Deploy Release</code> runbook to promote the same release to the next production environment</li>
<li>A runbook with a delayed API call that chains the rollout from <code>Prod 10</code> to <code>Prod 50</code>, and then from <code>Prod 50</code> to <code>Prod 100</code></li>
</ul>
<h2>What's next?</h2>
<p>The <a href="/blog/octo-easy-mode-19-bluegreen">next step</a> is an example of blue/green deployments.</p>]]></content>
    </entry>
    <entry>
      <title>How to Stream Octopus Audit Logs to Grafana Cloud With OpenTelemetry</title>
      <link href="https://octopus.com/blog/stream-audit-logs-to-grafana-cloud" />
      <id>https://octopus.com/blog/stream-audit-logs-to-grafana-cloud</id>
      <published>2026-08-06</published>
      <updated>2026-08-06</updated>
      <summary>Stream Octopus audit logs to Grafana Cloud over OpenTelemetry, query them with LogQL, and get a Slack alert the moment something gets deleted.</summary>
      <author>
        <name>Jubril Oyetunji, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Your continuous deployment platform already knows exactly who changed what, and when. If someone edited a variable or deleted an environment, Octopus records all of it.</p>
<p>For most teams, centralizing audit logs in the same SIEM tool they already watch is more than an added advantage, it is a compliance requirement, and "audit log to SIEM" stays a busy search term precisely because so many tools make it harder than it should be.</p>
<p>In this guide you will stream Octopus audit events into Grafana Cloud over OpenTelemetry, query them with LogQL, and fire a Slack alert the moment something is deleted.</p>
<p>Because the transport is OpenTelemetry, you can take what you learn here and stream the same events to any OpenTelemetry destination, whether that is Datadog, Honeycomb, Elastic, or a self-hosted collector."</p>
<h2><strong>Where Octopus audit data lives</strong></h2>
<p>Octopus gives you three ways to reach audit data, and it helps to know which one you are reaching for.</p>
<ul>
<li><strong>The Audit tab</strong> (Configuration, then Audit) is the in-product view. It is great for browsing individual events, filtering by user or date, and expanding a single change to see exactly what was modified.</li>
<li><strong>The Audit Stream</strong> pushes those same events out to an external system as they happen. This is the path this article covers. It supports OpenTelemetry (OTLP) alongside direct integrations for Splunk and Sumo Logic.</li>
<li><strong>Compliance Reports</strong>, also in Platform Hub, are the purpose-built path for Governance, Risk, and Compliance (GRC) reporting. If your primary need is producing GRC evidence rather than raw log search, that is where to look. It complements the streaming setup here rather than replacing it.</li>
</ul>
<p>In other words, the Audit tab and Audit Stream give you the raw events, and Compliance Reports streamline the reporting on top.</p>
<h2><strong>Architectural overview</strong></h2>
<p>Before we hop into a demo, It is worth getting a grasp on how this will all work.</p>
<p>Fundamentally, Octopus emits audit events over OTLP. A collector receives them and forwards them to Grafana Cloud, where they land in Loki and become queryable in Explore. From there, an alert rule watches for events and notifies Slack.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/architecture-diagram.png" alt="A diagram showing Octopus using OLTP to ngrok, which fronts Grafana Alloy and Grafana Cloud." loading="lazy" }</p>
<p>::figcaption[Octopus streams over OTLP, Grafana Alloy forwards to Grafana Cloud, and Loki becomes the searchable, alertable home for your audit trail.]</p>
<p>:::</p>
<h2><strong>Prerequisites</strong></h2>
<ul>
<li>An Octopus instance with the Audit Stream available.</li>
<li>A free <a href="https://grafana.com/auth/sign-up/create-user">Grafana Cloud</a> account.</li>
<li><a href="https://grafana.com/docs/alloy/latest/set-up/install/">Grafana Alloy</a>, the OpenTelemetry Collector distribution Grafana recommends, running as the receiver and forwarder.</li>
</ul>
<p>You might ask, "<em>why put a collector in the middle instead of pointing Octopus straight at Grafana Cloud?</em>" Simple. For reliability and control.</p>
<p>The collector batches records, holds the Grafana Cloud credentials in one place, and gives you a single spot to add processing or route to a second backend later. It also keeps Octopus configuration simple: Octopus only ever talks to the collector.</p>
<h2><strong>Step 1: Set up the Grafana Cloud destination</strong></h2>
<p>A free tier Grafana Cloud account includes 50 GB of log ingestion per month with 14 days of retention, which is plenty for an audit trail demo.</p>
<p>Once your stack exists, open the connection details for the OTLP endpoint. In the Grafana Cloud portal, go to your stack and find the OpenTelemetry tile, or open the stack's OTLP connection page directly.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/grafana-otlp-connection-details.png" alt="Grafana OLTP connection details page" loading="lazy" }</p>
<p>::figcaption[The OTLP connection page gives you the gateway endpoint and your instance ID. Generate an API token here to get the third value.]</p>
<p>:::</p>
<p>You are after the following three values:</p>
<ul>
<li><strong>OTLP Endpoint</strong>, something like <code>https://otlp-gateway-prod-us-west-0.grafana.net/otlp</code>. The region in that hostname will match your stack.</li>
<li><strong>Instance ID</strong>, a numeric value such as <code>1714216</code>. This is the username half of the credentials.</li>
<li><strong>API Token</strong>. Click <strong>Generate now</strong>, give the token a name like <code>octopus-audit-stream</code>, keep the default scopes (which include <code>logs:write</code>), and create it. Copy the token, it is shown only once. This is the password half.</li>
</ul>
<p>Logs sent over OTLP land in Loki. Grafana Cloud promotes the OpenTelemetry <code>service.name</code> resource attribute to the Loki stream label <code>service_name</code>, which is how you will find the data later. Octopus sets this to <code>Octopus Deploy</code>, so your query will start with <code>{service_name="Octopus Deploy"}</code>.</p>
<h2><strong>Step 2: Configure the OpenTelemetry collector (Grafana Alloy)</strong></h2>
<p>In Grafana Alloy, create the configuration file. It does three jobs: receive OTLP from Octopus, batch the records, and export them to Grafana Cloud with basic authentication.</p>
<pre><code class="language-bash">cat > config.alloy &#x3C;&#x3C;'EOF'
// 1. Receive OTLP over HTTP (Octopus sends http/protobuf) and gRPC.
otelcol.receiver.otlp "octopus" {
  http {
    endpoint = "127.0.0.1:4318"
  }

  grpc {
    endpoint = "127.0.0.1:4317"
  }

  output {
    logs = [otelcol.processor.batch.default.input]
  }
}

// 2. Batch records before export to reduce request volume.
otelcol.processor.batch "default" {
  output {
    logs = [otelcol.exporter.otlphttp.grafana_cloud.input]
  }
}

// 3. Authenticate to Grafana Cloud with the stack instance ID and API token.
otelcol.auth.basic "grafana_cloud" {
  username = "YOUR_INSTANCE_ID"
  password = sys.env("GRAFANA_CLOUD_API_TOKEN")
}

// 4. Export to the Grafana Cloud OTLP gateway. Logs land in Loki.
otelcol.exporter.otlphttp "grafana_cloud" {
  client {
    endpoint = "https://otlp-gateway-prod-us-west-0.grafana.net/otlp"
    auth     = otelcol.auth.basic.grafana_cloud.handler
  }
}
EOF
</code></pre>
<p>Walking through the two values that matter most:</p>
<ul>
<li>The exporter <code>endpoint</code> is the OTLP gateway from your connection details</li>
<li>The <code>otelcol.auth.basic</code> block is the authentication. Grafana Cloud expects HTTP Basic auth where the username is your instance ID and the password is the API token. Replace <code>YOUR_INSTANCE_ID</code> with the numeric instance ID, and keep the token out of the file by reading it from an environment variable.</li>
</ul>
<p>Start Alloy with the token in the environment:</p>
<pre><code class="language-bash">export GRAFANA_CLOUD_API_TOKEN='glc_your_token_here'
alloy run config.alloy --storage.path=./alloy-data
</code></pre>
<p>Alloy logs that it is listening. You should see the OTLP servers come up:</p>
<pre><code class="language-bash">level=info msg="Starting GRPC server" component_id=otelcol.receiver.otlp.octopus endpoint=127.0.0.1:4317
level=info msg="Starting HTTP server" component_id=otelcol.receiver.otlp.octopus endpoint=127.0.0.1:4318
</code></pre>
<p>Confirm the receiver accepts data before wiring up Octopus:</p>
<pre><code class="language-bash">curl -sw "\nHTTP %{http_code}\n" -X POST http://127.0.0.1:4318/v1/logs \
  -H "Content-Type: application/json" \
  -d '{"resourceLogs":[{"scopeLogs":[{"logRecords":[{"body":{"stringValue":"hello"}}]}]}]}'
</code></pre>
<p>A <code>HTTP 200</code> means Alloy took the record. If it also reached Grafana Cloud, you will see it in Explore in a moment.</p>
<h3><strong>Expose the collector so Octopus can reach it</strong></h3>
<p>Octopus Cloud needs a public URL to send to. For a local test, ngrok is the quickest way to expose the Alloy HTTP receiver, the same technique the <a href="https://octopus.com/blog/elastic-otel">Octopus and Elastic walkthrough</a> uses:</p>
<pre><code class="language-bash">ngrok http 4318
</code></pre>
<p>ngrok prints a public HTTPS URL such as <code>https://4202-12-17-71-220.ngrok-free.app</code>. That, with <code>/v1/logs</code> appended, is what Octopus will target. In a permanent deployment you would run Alloy on a host Octopus can reach directly and skip the tunnel.</p>
<h2><strong>Step 3: Point Octopus at the collector</strong></h2>
<p>In Octopus, go to <strong>Configuration</strong> > <strong>Audit</strong>, and click <strong>Stream Audit Log</strong>. Choose <strong>OpenTelemetry</strong> as the provider.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/octopus-audit-stream-providers.png" alt="Octopus audit stream providers" loading="lazy" }</p>
<p>::figcaption[Octopus supports OpenTelemetry, Splunk, and Sumo Logic out of the box. OpenTelemetry is the one that keeps you vendor-neutral.]</p>
<p>:::</p>
<p>Fill in the OpenTelemetry fields:</p>
<ul>
<li><strong>OpenTelemetry Endpoint URL</strong>: Your collector's log endpoint, which is the ngrok URL with <code>/v1/logs</code> on the end, for example <code>https://4202-12-17-71-220.ngrok-free.app/v1/logs</code>.</li>
<li><strong>OTLP Protocol</strong>: <code>HTTP/protobuf</code>.</li>
<li><strong>Secret</strong>: Leave this empty. Authentication to Grafana Cloud is handled by the collector, so Octopus does not need to send any token. This is exactly why the collector sits in the middle.</li>
</ul>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/octopus-audit-stream-otel-config.png" alt="Octopus audit stream OTel config" loading="lazy" }</p>
<p>::figcaption[The endpoint points at the collector, the protocol is HTTP/protobuf, and no secret is needed because the collector authenticates to Grafana Cloud.]</p>
<p>:::</p>
<p>Click <strong>Save</strong>.</p>
<p>The <strong>Stream Audit Log</strong> button now shows a green check, which means the stream is active and new events will flow. Historical events are not backfilled, only events from this point forward are streamed.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/octopus-audit-stream-active.png" alt="Octopus audit stream active" loading="lazy" }</p>
<p>::figcaption[A green check confirms the stream is live.]</p>
<p>:::</p>
<h2><strong>Step 4: Generate events and verify in Grafana Cloud</strong></h2>
<p>Trigger a few audited actions so there is something to see. Anything that creates, modifies, or deletes a resource works. For this walkthrough, create an environment, edit it, then delete it. Each of those is a separate audit event.</p>
<p>Now open <strong>Explore</strong> in Grafana Cloud, select your logs data source, and run:</p>
<pre><code class="language-bash">{service_name="Octopus Deploy"}
</code></pre>
<p>The events arrive within seconds.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/grafana-explore-audit-events.png" alt="Explore audit events in Grafana" loading="lazy" }</p>
<p>::figcaption[Octopus audit events in Grafana Cloud. The expanded record shows a deleted environment, the Category label, and the exact field-level differences.]</p>
<p>:::</p>
<p>Expand a record and you will see the labels Octopus attaches to every event. These are what make the data useful:</p>
<ul>
<li><code>service_name</code> = <code>Octopus Deploy</code></li>
<li><code>event_name</code> = <code>octopus.audit</code></li>
<li><code>Category</code> = <code>Created</code>, <code>Modified</code>, or <code>Deleted</code></li>
<li><code>Username</code> = the account that made the change</li>
<li><code>IpAddress</code> = where the request came from</li>
<li><code>SpaceId</code> and the resource ID, such as <code>EnvironmentId</code></li>
<li><code>severity_text</code> = <code>Information</code></li>
</ul>
<p>Those labels let you slice the audit trail without parsing message text. If you want every change a specific person made? Filter on <code>Username</code>. And if you want only deletions? Filter on <code>Category</code>:</p>
<pre><code class="language-bash">{service_name="Octopus Deploy"} | Category=`Deleted`
</code></pre>
<p>That single query is the foundation of the alert you are about to build.</p>
<h2><strong>Step 5: Sending Slack alerts</strong></h2>
<p>A searchable audit trail is useful but an audit trail that pages you when something sensitive happens is better. Grafana Alerting has a native Slack integration, so no extra service is needed.</p>
<p>First, create the Slack contact point. In Slack, add an <a href="https://api.slack.com/messaging/webhooks">Incoming Webhook</a> for the channel you want, a channel like <code>#grc</code> is a natural home for this.</p>
<p>In Grafana, go to <strong>Alerts &#x26; IRM</strong> > <strong>Alerting</strong> > <strong>Contact points</strong> then add a contact point, choose <strong>Slack</strong>, and paste the webhook URL. Keep that URL secret, anyone who has it can post to your channel.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/grafana-slack-contact-point.png" alt="Grafana Slack contact point" loading="lazy" }</p>
<p>::figcaption[Creating the Slack contact point. With a webhook URL, you can leave the recipient and token fields empty.]</p>
<p>:::</p>
<p>Next, create the alert rule. Point it at your logs data source and use a query that counts delete events over a short window. Group the count by the labels you want in the alert, so they survive the aggregation and can be used in the message:</p>
<pre><code class="language-text">sum by (Category, Username, IpAddress, SpaceId, EnvironmentId) (
  count_over_time({service_name="Octopus Deploy"} | Category=`Deleted` [5m])
)
</code></pre>
<p>Add a threshold condition of <code>is above 0</code>, so the rule fires whenever a deletion shows up. Give the rule a label like <code>team = grc</code> and route that label to your Slack contact point in the notification policy.</p>
<p>Set the rule's summary annotation to reference those labels, so the Slack message names the event rather than showing <code>[no value]</code>. Grafana exposes the query's labels as <code>$labels</code>:</p>
<pre><code class="language-text">Octopus audit alert: {{ $labels.Category }} event by {{ $labels.Username }} from {{ $labels.IpAddress }}
</code></pre>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/grafana-alert-rule-detail.png" alt="Grafana alert rule detail" loading="lazy" }</p>
<p>::figcaption[The alert rule counts Octopus delete events and fires when the count goes above zero.]</p>
<p>:::</p>
<p>Delete an environment to test it. Within an evaluation cycle the rule moves to Firing, and the instance shows its destination is the Slack contact point.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/grafana-alert-instances.png" alt="Grafana alert instances" loading="lazy" }</p>
<p>::figcaption[One firing instance, labelled team=grc, routed to the Slack contact point.]</p>
<p>:::</p>
<p>A message lands in your channel naming the event, the user, and the source IP.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/stream-audit-logs-to-grafana-cloud/slack-grc-alert.png" alt="Slack GRC alert" loading="lazy" }</p>
<p>:::</p>
<h2><strong>Use any backend</strong></h2>
<p>The reason to do this over OpenTelemetry rather than a proprietary integration is portability. Nothing in the Octopus configuration is Grafana-specific. Octopus speaks OTLP to a collector, full stop.</p>
<p>To send the same audit stream to Datadog, Honeycomb, Elastic, or a self-hosted backend, you change the collector's exporter and leave everything else alone. The receiver stays the same, the Octopus Audit Stream config stays the same, additionally you can fan out to two destinations at once by listing two exporters.</p>
<p>That is the whole point of standardizing on OpenTelemetry.</p>
<h2><strong>Your audit trail, wherever you watch it</strong></h2>
<p>Octopus has been recording who did what since long before you turned this on. The work here was not generating the data, but moving it to where your team looks. Because the transport is OpenTelemetry, that move is a single endpoint away from any SIEM tool you run.</p>
<p>If you want to try it, spin up a <a href="https://grafana.com/auth/sign-up/create-user">free Grafana Cloud account</a> and read the <a href="https://octopus.com/docs/security/users-and-teams/auditing/audit-stream">Audit Stream documentation</a> for the exact fields.</p>
<p>If your focus is governance and compliance reporting, look at <a href="https://octopus.com/docs/platform-hub/compliance-reports">Compliance Reports</a>.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>MCP Easy Mode</title>
      <link href="https://octopus.com/blog/mcp-easy-mode" />
      <id>https://octopus.com/blog/mcp-easy-mode</id>
      <published>2026-08-05</published>
      <updated>2026-08-05</updated>
      <summary>Learn how to run your runbooks as MCP tools</summary>
      <author>
        <name>Matthew Casperson, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>What you'll learn in this post:</p>
<ul>
<li>The challenges of maintaining collections of MCP servers.</li>
<li>Why simple tools can lead to higher token usage.</li>
<li>Introducing the <a href="https://github.com/OctopusSolutionsEngineering/OctopusEasyModeMCP">Octopus Easy Mode MCP</a> server.</li>
<li>Demonstrating how the Octopus Easy Mode MCP server can be used to expose runbooks as MCP tools.</li>
</ul>
<p>All examples in this post are copy and paste prompts, so you will have a working MCP server executing runbooks in 30 minutes.</p>
<p>:img{ src="/blog/img/mcp-easy-mode/diagram.png" alt="Architecture diagram" }</p>
<h2>Introduction</h2>
<p><a href="https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro">Model Context Protocol</a> (MCP) servers are the new AI layer specifically designed to support agentic workflows. The protocol has gained an enormous amount of support, and it is reasonable to expect that major software vendors will provide an MCP server. Exposing a combination of MCP servers to your AI harness allows you to complete complex tasks while leaving decisions about which service to call to the AI. Agentic workflows are very much founded on the idea that LLMs can ground themselves with trusted, external sources of truth, use tools to interact with the world, and consume feedback to make decisions about how to complete a task.</p>
<h2>Challenges with MCP servers</h2>
<p>However, maintaining collections of MCP servers is a non-trivial challenge. End users have to maintain a list of servers, which is challenging to standardize across an organization, and with the added complexity of embedding credentials in the <code>mcp.json</code> file. Like any desktop software, local MCP servers also need to be kept up to date, while remote MCP servers must be hosted and maintained by a dedicated team.</p>
<p>Exposing a collection of general-purpose tools to an LLM also increases token use. LLMs are quite capable these days of reasoning about how to complete a task, but this comes at the cost of lengthy chain-of-thought reasoning as the LLM works out how to combine otherwise disparate tools.</p>
<p>And there will always be gaps in MCP server coverage. A server may not be available, network security rules may prevent access, or the task to be automated may be too complex and bespoke for an LLM to reliably complete.</p>
<h2>Octopus Easy Mode MCP server</h2>
<p>The <a href="https://github.com/OctopusSolutionsEngineering/OctopusEasyModeMCP">Octopus Easy Mode MCP server</a> is a community project that takes a different approach to traditional MCP servers by exposing runbooks as <a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools">MCP tools</a>. This allows any process that can be automated with a runbook to be executed by an LLM, which has a number of benefits:</p>
<ul>
<li>One MCP server can execute any process that can be automated with a runbook.</li>
<li>Octopus orchestrates complex processes as a series of deterministic steps.</li>
<li>Token count is reduced as the LLM can delegate the execution of complex processes to the Octopus server, rather than having to generate all the steps itself.</li>
<li>Octopus provides security and auditing features, making it possible to restrict and trace the LLMs actions.</li>
<li>There is no longer any specialized knowledge required to create an MCP server – anyone who can create a runbook can expose it as an MCP tool.</li>
</ul>
<p>In this post, you'll learn how to use the Octopus Easy Mode MCP server to expose a runbook as an MCP tool and then use that tool to automate a process.</p>
<h2>Prerequisites</h2>
<ul>
<li>An <a href="https://octopus.com/start">Octopus Cloud</a> account. If you don't have one, you can sign up for a free trial.</li>
<li>The Octopus AI Assistant Chrome extension. You can install it from the <a href="https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha">Chrome Web Store</a>.</li>
</ul>
<p>:::div{.hint}
The Octopus AI Assistant will work with an on-premises Octopus instance, but it requires more configuration. The
cloud-hosted version of Octopus doesn't need extra configuration. This means the cloud-hosted version is the easiest way to get started.
:::</p>
<h2>Creating the runbook</h2>
<p>The first step is to create a runbook that will be exposed as an MCP tool. In this example, you'll create a runbook that will print the current date and time.</p>
<p>Run the following prompt in the Octopus AI Assistant:</p>
<pre><code class="language-markdown">Create a project called "Easy Mode MCP" and then:
* Add a runbook description
* Add a runbook called "Get Current Time".
* The runbook must have a single script step that echoes the current time with the PowerShell command `Get-Date`. 
* Create a single environment called "MCP". Do not create any other environments.
* Configure the runbook to only run in the MCP environment.
</code></pre>
<p>:img{ src="/blog/img/mcp-easy-mode/ai-assistant-icon.png" alt="AI Assistant Icon" loading="lazy" }</p>
<p>:img{ src="/blog/img/mcp-easy-mode/ai-assistant.png" alt="AI Assistant Interface" loading="lazy" }</p>
<p>This prompt creates a project to host the runbooks used by the Easy Mode MCP server. It then creates a runbook that you'll call from an MCP client.</p>
<h2>Run the MCP Server</h2>
<p>The easiest way to run the Octopus Easy Mode MCP server is to use the Docker image. The following <code>mcp.json</code> file can be used to configure the server:</p>
<pre><code class="language-json">{
  "servers": {
    "easymode": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run", 
        "--rm", 
        "-i",
        "--pull=always",
        "-e", "EASY_MODE_MCP_TRANSPORT=stdio", 
        "-e", "EASY_MODE_MCP_AUTH_TYPE=none", 
        "-e", "EASY_MODE_MCP_OCTOPUS_URL=https://yourinstance.octopus.app", 
        "-e", "EASY_MODE_MCP_OCTOPUS_API_KEY=API-APIKEYGOESHERE", 
        "-e", "EASY_MODE_MCP_OCTOPUS_SPACE_ID=Spaces-##", 
        "ghcr.io/octopussolutionsengineering/octopuseasymodemcp:latest"],
      "timeout": 600000
    }
  }
}
</code></pre>
<p>Replace the following values in the <code>mcp.json</code> file:</p>
<ul>
<li><code>EASY_MODE_MCP_OCTOPUS_URL</code> with the URL of your Octopus instance.</li>
<li><code>EASY_MODE_MCP_OCTOPUS_API_KEY</code> with an <a href="https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key">API key</a> that has access to the runbook you created.</li>
<li><code>EASY_MODE_MCP_OCTOPUS_SPACE_ID</code> with the ID of the space that contains the runbook you created.</li>
</ul>
<p>Now run the prompt from your chat client:</p>
<pre><code class="language-markdown">Get the current time
</code></pre>
<p>:::div{.hint}
Your MCP client may be able to return the date and time without needing to call an MCP server. If so, you can force the LLM to call the Easy Mode MCP server with the prompt <code>Get the current time from the easymode mcp server</code>.
:::</p>
<p>The MCP server will create a new snapshot of the runbook, run it, and return the result.</p>
<p>Importantly, you did not have to instruct the LLM to run a runbook. The Easy Mode MCP server does not require the end user to know that they are running runbooks, or even that the server is backed by Octopus. The runbooks are exposed directly as tools.</p>
<p>You can see this by asking the LLM to list the available tools from your chat client:</p>
<pre><code class="language-markdown">List the available tools
</code></pre>
<h2>Passing parameters to the tool</h2>
<p>Prompted variables are treated as parameters to the tool. For example, you can create a runbook that takes a name as a parameter and returns a greeting.</p>
<p>Run the following prompt in the Octopus AI Assistant:</p>
<pre><code class="language-markdown">Create a runbook called "Greet User" in the "Easy Mode MCP" project and then:
* Add a runbook description
* Add a single prompted variable called "Name" with the description "The name of the user to greet" and set the default value to "World".
* Scope the "Name" variable to the "Greet User" runbook.
* Add a single script step that echoes "Hello, #{Name}!".
* Configure the runbook to only run in the MCP environment.
</code></pre>
<p>Restart the MCP server to pick up the new tool, and then run the following prompt from your chat client:</p>
<pre><code class="language-markdown">Greet the user "Finn"
</code></pre>
<p>:::div{.hint}
The process of restarting the MCP server is different with each client. Visual Studio Code allows you to restart the MCP server by clicking the "Restart" link above the MCP server when editing the <code>mcp.json</code> file:</p>
<p><a href="/blog/img/mcp-easy-mode/vs-code-mcp-json.png">:img{ src="/blog/img/mcp-easy-mode/vs-code-mcp-json.png" alt="VSCode mcp.json" loading="lazy" }</a>
:::</p>
<p>The MCP client is smart enough to know the name <code>Finn</code> must be passed to the <code>Name</code> variable in the <code>Greet User</code> runbook, and the MCP server will return the greeting.</p>
<h2>Adding elicitation</h2>
<p><a href="https://modelcontextprotocol.io/specification/draft/client/elicitation">Elicitation</a> is the process of asking the user for information that is required to complete a task. The Octopus Easy Mode MCP server supports elicitation by adding manual intervention steps to a runbook.</p>
<p>Run the following prompt in the Octopus AI Assistant:</p>
<pre><code class="language-markdown">Create a runbook called "Welcome User" in the "Easy Mode MCP" project and then:
* Add a runbook description
* Add a single manual intervention step called "Ask for Name" with the instruction "Please enter your name" and the prompt "What is your name?".
* Add a single script step that echoes "Hello, #{Octopus.Action[Ask for Name].Output.Manual.Notes}!".
* Configure the runbook to only run in the MCP environment.
</code></pre>
<p>Restart the MCP server to pick up the new tool, and then run the following prompt from your chat client:</p>
<pre><code class="language-markdown">Welcome the user
</code></pre>
<p>You will be asked to enter your name and The MCP server will then return the greeting.</p>
<p>:::div{.warning}
Not all MCP clients support elicitation. This post was tested with Visual Studio Code and the GitHub Copilot Chat extension.
:::</p>
<p>You can automatically add generated notes to the manual intervention by setting <code>EASY_MODE_MCP_AUTO_POPULATE_INTERVENTION_NOTES</code> to <code>True</code>:</p>
<pre><code class="language-json">{
  "easymode": {
    "type": "stdio",
    "command": "docker",
    "args": ["run",
      "--rm",
      "-i",
      "--pull=always",
      "-e", "EASY_MODE_MCP_TRANSPORT=stdio",
      "-e", "EASY_MODE_MCP_AUTH_TYPE=none",
      "-e", "EASY_MODE_MCP_OCTOPUS_URL=https://yourinstance.octopus.app",
      "-e", "EASY_MODE_MCP_OCTOPUS_API_KEY=API-APIKEY",
      "-e", "EASY_MODE_MCP_OCTOPUS_SPACE_ID=Spaces-##",
      "-e", "EASY_MODE_MCP_AUTO_POPULATE_INTERVENTION_NOTES=True",
      "ghcr.io/octopussolutionsengineering/octopuseasymodemcp:latest"],
    "timeout": 600000
  }
}
</code></pre>
<p>The content placed into the notes section can be defined with the <code>EASY_MODE_MCP_AUTO_POPULATE_INTERVENTION_NOTES_VALUE</code> environment variable:</p>
<pre><code class="language-json">{
  "easymode": {
    "type": "stdio",
    "command": "docker",
    "args": ["run",
      "--rm",
      "-i",
      "--pull=always",
      "-e", "EASY_MODE_MCP_TRANSPORT=stdio",
      "-e", "EASY_MODE_MCP_AUTH_TYPE=none",
      "-e", "EASY_MODE_MCP_OCTOPUS_URL=https://yourinstance.octopus.app",
      "-e", "EASY_MODE_MCP_OCTOPUS_API_KEY=API-APIKEY",
      "-e", "EASY_MODE_MCP_OCTOPUS_SPACE_ID=Spaces-##",
      "-e", "EASY_MODE_MCP_AUTO_POPULATE_INTERVENTION_NOTES=True",
      "-e", "EASY_MODE_MCP_AUTO_POPULATE_INTERVENTION_NOTES_VALUE=Your custom note",
      "ghcr.io/octopussolutionsengineering/octopuseasymodemcp:latest"],
    "timeout": 600000
  }
}
</code></pre>
<p>:::div{.hint}
Setting the <code>EASY_MODE_MCP_AUTO_POPULATE_INTERVENTION_NOTES</code> environment variable to <code>True</code> allows you to run runbooks that contain manual intervention steps without any user interaction. This is useful for MCP clients that do not support elicitation or for automating processes that require manual intervention.
:::</p>
<h2>Practical examples</h2>
<p>Here are some practical Runbooks that you might expose to an MCP server.</p>
<h3>Creating cloud resources</h3>
<p>A common scenario for DevOps teams is to provision new cloud resources. This is often done with a combination of Terraform and Octopus.</p>
<p>Run the following prompt in the Octopus AI Assistant:</p>
<pre><code class="language-markdown">Create a runbook called "Create EC2 Instance" in the "Easy Mode MCP" project and then:
* Create a feed called "Docker Hub" pointing to "https://index.docker.io" using anonymous authentication.
* Add a runbook description
* Add a Terraform Apply step to the runbook called "Create EC2 Instance" with the following configuration:
```
# A mocked Terraform configuration simulating the construction of an EC2 instance
output "ec2_instance_id" {
  value = "i-1234567890abcdef0"
}
```
* Use the "Hosted Ubuntu" worker pool.
* Configure the Terraform step to use the execution container image "octopusdeploy/worker-tools:6.6.4-ubuntu.22.04" from the "Docker Hub" feed
* Add a step to run a script that writes `Your instance is #{Octopus.Action[Apply a Terraform template].Output.TerraformValueOutputs[ec2_instance_id]}` as a highlight.
* Configure the runbook to only run in the MCP environment.
* The runbook must be untenanted.
</code></pre>
<p>Restart the MCP server to pick up the new tool, and then run the following prompt from your chat client:</p>
<pre><code class="language-markdown">Create a new EC2 instance
</code></pre>
<h3>Debugging a Kubernetes application</h3>
<p>In this example, we'll imagine that a support team needs to restart a Kubernetes application. The runbook will delete the pod, triggering a restart.</p>
<p>Run the following prompt in the Octopus AI Assistant:</p>
<pre><code class="language-markdown">Create a runbook called "Restart K8s Web App" in the "Easy Mode MCP" project and then:
* Define the first step as a kubectl script step to the runbook called "Restart K8s Web App"
* Use the "Hosted Ubuntu" worker pool.
* Configure the kubectl step to use the execution container image "octopusdeploy/worker-tools:6.6.4-ubuntu.22.04" from the "Docker Hub" feed
* Add a script that checks for a pod called "my-web-app-pod", and if it exists, deletes it. If the pod doesn't exist, the script should write a message to the log and exit successfully.
* Use client side apply in the Kubernetes step (the mock Kubernetes cluster only supports client side apply).
* Disable verification checks in the Kubernetes steps (the mock Kubernetes cluster doesn't support verification checks).
* Enable retries on the K8s deployment step.
* Define the second step as a Slack notification step to the runbook called "Notify Slack" that sends a message to the channel "#k8s-notifications" with the message "The K8s Web App has been restarted." using the web hook url "https://mockslackwebhook.octopusdemos.com/".
* Configure the runbook to only run in the MCP environment.
* The runbook must be untenanted.

---

Create a token account called "Mock Token".

---

Create a feed called "Docker Hub" pointing to "https://index.docker.io" using anonymous authentication.

---

Create a Kubernetes target with the tag "Kubernetes", the URL https://mockk8s.octopusdemos.com, using the health check container image "octopusdeploy/worker-tools:6.5.0-ubuntu.22.04" from the "Docker Hub" feed, using the token account, and the "Hosted Ubuntu" worker pool. Scope the target to the "MCP" environment.
</code></pre>
<p>Restart the MCP server to pick up the new tool, and then run the following prompt from your chat client:</p>
<pre><code class="language-markdown">Restart the Kubernetes Web App
</code></pre>
<p>What is neat about this example is that we have captured a lot of business logic in the runbook. The pod to be deleted is defined, eliminating the need for the end user to know which pod to delete. The Slack notification step is also defined, so the team is notified when the pod is restarted.</p>
<p>While this logic could be captured in an LLM skill, you can imagine how many tokens would be required for the LLM to reason about the steps to restart a Kubernetes application and send a Slack notification. By exposing the process as a deterministic set of steps in a runbook, the LLM only needs to know that a tool exists to restart the Kubernetes application, and it can delegate execution of that process to the Octopus server.</p>
<h3>Bootstrapping a new project</h3>
<p>Here is an example where we create a runbook to call the AI Assistant to create a new Terraform project. The <a href="https://library.octopus.com/step-templates/8ce3eb55-2c35-45c2-be8c-27e71ffbf032/actiontemplate-octopus-prompt-ai">Octopus - Prompt AI</a> step allows you to run the same prompts you have been typing into the AI Assistant directly from a runbook.</p>
<p>Run the following prompt in the Octopus AI Assistant:</p>
<pre><code class="language-markdown">Create a runbook called "Create new Terraform project" in the "Easy Mode MCP" project and then:
* Define the first step as an "Octopus - Prompt AI" step
* Set the prompt to "Create a new Terraform project with the following name: #{ProjectName} in the #{Octopus.Space.Name} space." and enable auto approve.
* Add a prompted variable called "ProjectName" with the description "The name of the new Terraform project" and scope it to the "Create new Terraform project" runbook.
* Configure the runbook to only run in the MCP environment.
* The runbook must be untenanted.
</code></pre>
<p>You will need to <a href="https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key">create an API key</a> for the "Octopus - Prompt AI" step and define it in the <code>Project.Octopus.Api.Key</code> variable.</p>
<p>Restart the MCP server to pick up the new tool, and then run the following prompt from your chat client:</p>
<pre><code class="language-markdown">Create a new Terraform project called "My New Project"
</code></pre>
<p>A new project will be created in the same space as the runbook to deploy a sample Terraform configuration.</p>
<p>You're now chaining AI agents:</p>
<ol>
<li>The MCP client calls the Easy Mode MCP server</li>
<li>The Easy Mode MCP server calls a runbook</li>
<li>The runbook calls the AI Assistant to create a new Terraform project</li>
<li>The AI Assistant calls an LLM to create a new Terraform project</li>
</ol>
<h2>Governance and compliance</h2>
<p>Because the Easy Mode MCP server is backed by Octopus, it inherits all the governance and compliance features of Octopus.</p>
<p>You get audit logs showing what was run, when, and a persistent log of all the output:</p>
<p><a href="/blog/img/mcp-easy-mode/audit-logs.png">:img{ src="/blog/img/mcp-easy-mode/audit-logs.png" alt="Audit Logs" loading="lazy" }</a></p>
<p>These logs can optionally be <a href="https://octopus.com/docs/security/users-and-teams/auditing/audit-stream">sent to an external log aggregation service</a>, such as Splunk or Datadog, for long-term retention and analysis.</p>
<p>The runbooks can be subject to <a href="https://octopus.com/docs/platform-hub/policies">Platform Hub policies</a>, and can consume <a href="https://octopus.com/docs/platform-hub/templates/process-templates">process templates</a>.</p>
<p>Runbook events can trigger <a href="https://octopus.com/docs/administration/managing-infrastructure/subscriptions/webhook-slack#configure-an-octopus-subscription-to-send-a-webhook">webhooks</a> to notify external systems of runbook execution.</p>
<p>All credentials are centrally managed and can be sourced from external secret management systems, such as <a href="https://octopus.com/blog/using-hashicorp-vault-with-octopus-deploy">HashiCorp Vault</a>.</p>
<p>Octopus provides a robust platform with proven governance and compliance features, all of which are now available to AI agents through the Easy Mode MCP server.</p>
<h2>Difference between the Easy Mode MCP server and the Octopus MCP server</h2>
<p><a href="https://octopus.com/docs/octopus-ai/mcp">Octopus provides a general purpose MCP server</a> that allows MCP clients to execute common Octopus operations, such as creating releases, deploying releases, running runbooks, getting deployment logs, etc.</p>
<p>The Octopus MCP server is more than capable of running runbooks, but it does not inherently know about the existence of runbooks, nor does it have a reason to link a runbook to a specific task.</p>
<p>For example, to run the "Get Current Time" runbook, you would write a prompt like this:</p>
<pre><code class="language-markdown">Run the runbook "Get Current Time" in the "Easy Mode MCP" project in the "MCP" environment in the "Default" space.
</code></pre>
<p>This command queries the space to get the space ID, the project to get the project ID, and the runbook to get the runbook ID. It then runs the runbook and returns the result. It then executes the runbook and returns the result.</p>
<p>In Claude Code, this was the token usage:</p>
<pre><code class="language-text">Usage by model:
    claude-haiku-4-5:  549 input, 17 output, 0 cache read, 0 cache write ($0.0006)
       claude-opus-5:  16 input, 1.5k output, 157.8k cache read, 12.7k cache write ($0.1956)
</code></pre>
<p>Running the same runbook with the Easy Mode MCP server, the token count is significantly lower:</p>
<pre><code class="language-text">Usage by model:
    claude-haiku-4-5:  521 input, 14 output, 0 cache read, 0 cache write ($0.0006)
       claude-opus-5:  4 input, 145 output, 29.0k cache read, 5.0k cache write ($0.0497)
</code></pre>
<p>By directly exposing runbooks as tools, the Easy Mode MCP server provides a more efficient way for LLMs to execute runbooks, reducing token usage and improving performance.</p>
<h2>Conclusion</h2>
<p>The Easy Mode MCP server provides a simple way for AI agents and MCP clients to execute Octopus runbooks as tools. This means AI-based workflows gain the scale, reliability, auditability, governance, and convenience of Octopus. And because most of the work is performed by Octopus, AI agents reduce their token use by offloading the execution of complex processes to Octopus.</p>]]></content>
    </entry>
    <entry>
      <title>Connecting Octopus Cloud to your internal systems — without opening a single port</title>
      <link href="https://octopus.com/blog/octopus-cloud-connection-agent" />
      <id>https://octopus.com/blog/octopus-cloud-connection-agent</id>
      <published>2026-08-03</published>
      <updated>2026-08-03</updated>
      <summary>The Octopus Connection Agent lets enterprises reach their on-premises tooling from Octopus Cloud securely, with no inbound firewall rules and no VPN.</summary>
      <author>
        <name>Mark Lamprecht, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>For many enterprise teams, the move to Octopus Cloud can run into the same limitation: their artifact repositories, source control servers, and other internal tooling live behind a network perimeter that was never designed to accept connections from the outside world. Opening inbound firewall ports isn't an option—not when security and compliance teams have spent considerable effort locking those down.</p>
<p>So teams make a pragmatic choice: stay on Octopus Server and deal with the cloud migration later. But by extension then they continue to manage Octopus upgrades and operating system patching too; and have to accept they won't have access to new Octopus features as they come out.</p>
<p>We've watched this play out with a number of large enterprises. One of them is a major global financial services institution in the middle of a significant undertaking: migrating over a decade's worth of legacy deployment infrastructure to Octopus. Thousands of pipelines and years of accumulated configuration. A migration that is touching nearly every team in the organization.</p>
<p>Going straight to Octopus Cloud is the obvious goal — less infrastructure to manage, no platform to maintain. But it's being blocked by a straightforward problem: Octopus Cloud has no way to reach the systems that matter most—their internal Artifactory instance, and their GitHub Enterprise server and other internal systems. In a regulated environment, asking the security team to open inbound ports to a third-party SaaS platform isn't a conversation that team wants to have.</p>
<p>Enter the <strong>Octopus Connection Agent</strong>, available from July 2026.</p>
<h2>How it works</h2>
<p>The Connection Agent is a lightweight Docker container you run inside your own network. When it starts, it opens a secure, outbound-only connection to your Octopus Cloud instance over port 443—the same port used for standard HTTPS traffic.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/connection-agent-architecture/img-connection-agent-architecture.png" alt="Connection Agent architecture" loading="lazy" }</p>
<p>:::</p>
<p>Because the connection is initiated from inside your network, nothing needs to change on your firewall. No inbound rules. No VPN. No publicly accessible endpoints for your internal systems. The agent authenticates using a private key, and Octopus proxies traffic through it to reach whatever internal resources you need.</p>
<p>The financial services institution in this story ran the Connection Agent as a Kubernetes pod inside their own cluster—fitting naturally into the container infrastructure they already operated. After completing the registration step, the logs confirmed what they'd been waiting for:</p>
<pre><code class="language-text">[10:55:05 INF] Registering Connection Agent with Octopus instance URL: https://{instance}.octopus.app/api/connectionagent/registrations
[10:55:05 INF] Successfully registered Connection Agent
</code></pre>
<p>Within a single maintenance window, their Octopus Cloud instance was reprovisioned, and traffic began flowing through the agent to their internal GitHub Enterprise server. Artifactory followed shortly after. The only firewall edit required: outbound port 443 access to their Octopus Cloud instance and to Octopus's authentication service.</p>
<h2>From blocker to enabler</h2>
<p>Once the Connection Agent is running, they can get on with populating Octopus Cloud, skipping an entire phase of infrastructure work—requesting and configuring firewall ports—and instead focusing on what actually matters, getting thousands of pipelines migrated and teams deploying to production.</p>
<p>That's not a small thing as every migration carries risk. And adding yet more company infrastructure configuration steps into the mix—in this case extra firewall rules—doesn't actually move the migration forwards, it only further compounds the risk. By using the Connection Agent, the company removes that risk entirely.</p>
<h2>Security by design</h2>
<p>For teams in regulated industries, security isn't a checkbox—it's a constraint that shapes every decision. The Connection Agent was designed with that in mind.</p>
<p>All connections are outbound-only. The agent initiates the connection; Octopus never reaches into your network. Authentication uses JWT-signed private keys. You can configure <code>ALLOWED_IP_RANGES</code> to lock down exactly which internal hosts the agent can access, so you define the boundary explicitly rather than leaving it open-ended.</p>
<p>At this early stage, we support the publicly accessible Certificate Authorities (CAs). Though if your internal systems use certificates issued by an internal certificate authority—common in financial services—then for now you can use those internal certificates and CAs by setting <code>--ignore-certificate-validation-errors</code> for a given internal domain.</p>
<p>So if you need to pin specific CAs or certificates, let us know!</p>
<p>What's more, if you need redundancy, you can run multiple agents across different Kubernetes clusters.</p>
<h2>What's supported</h2>
<p>The Connection Agent currently supports:</p>
<ul>
<li>Git repositories (e.g. GitHub Enterprise, Bitbucket)</li>
<li>SMTP servers</li>
<li>External feeds:
<ul>
<li>Artifactory Generic Feed</li>
<li>Azure Container Registry</li>
<li>Docker Container Registry</li>
<li>GitHub Repository Feed</li>
<li>Helm Feed</li>
<li>Maven Feed</li>
<li>NPM Feed</li>
<li>NuGet Feed</li>
<li>OCI Container Registry</li>
</ul>
</li>
</ul>
<p>Support for additional resource types is in active development. Early adopters are working directly with the team to shape what comes next, which means real customer use cases are driving the roadmap.</p>
<p><strong>Action:</strong> We would love to hear what other connectors would help. You can <a href="https://roadmap.octopus.com/c/220-connect-to-self-hosted-applications-from-octopus-cloud">share them with us here</a>.</p>
<h2>Getting started</h2>
<p>The Connection Agent is available as a Docker image on <a href="https://hub.docker.com/r/octopusdeploy/connection-agent/tags">Docker Hub</a> and is limited to a maximum of 5 connections i.e. 5 internal resources as of this writing.</p>
<p>Full documentation <a href="https://octopus.com/docs/octopus-cloud/connection-agent">can be found here</a>.</p>
<p>If you're on Octopus Server and a connectivity gap is what's been keeping you from moving to cloud, or if you're already on Octopus Cloud and working around the lack of access to your internal systems, talk to your account team as this is the piece that was missing.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Octopus Easy Mode - Claude Agent</title>
      <link href="https://octopus.com/blog/octo-easy-mode-17-claude" />
      <id>https://octopus.com/blog/octo-easy-mode-17-claude</id>
      <published>2026-07-31</published>
      <updated>2026-07-31</updated>
      <summary>Learn how to create a Claude Agent project</summary>
      <author>
        <name>Matthew Casperson, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>AI is driving a surge in code commits. <a href="https://x.com/kdaigle/status/2040164759836778878">Kyle Daigle, COO at GitHub, noted that</a>:</p>
<blockquote>
<p>There were 1 billion commits in 2025. Now, it's 275 million per week, on pace for 14 billion this year if growth remains linear (spoiler: it won't.)</p>
</blockquote>
<p>Of course, commits are only useful if they contribute to running software. The challenge for many teams is increasing deployment frequency to match commit frequency. Ideally, changes with limited risk should be deployed with as little friction as possible. But how do you determine something as abstract as a low-risk commit?</p>
<p>Code is just text, and LLMs are incredible at comprehending text. With the new <a href="https://octopus.com/docs/octopus-ai/claude-agent-step">Run Claude Agent</a> step in Octopus, combined with <a href="https://octopus.com/docs/packaging-applications/build-servers/build-information">Build Information</a>, Octopus can automatically inspect commits that contribute to a deployment and make intelligent decisions at deploy time based on the commit content.</p>
<p>In the <a href="/blog/octo-easy-mode-16-argocd-manifest-update">previous post</a>, you created a project simulating updating an <a href="https://octopus.com/docs/argo-cd/steps/update-application-manifests">Argo CD Manifest file</a>.</p>
<p>In this post, you will create a sample project that uses the <code>Run Claude Agent</code> step to determine if a commit is low risk and can be deployed automatically.</p>
<h2>Prerequisites</h2>
<ul>
<li>An <a href="https://octopus.com/start">Octopus Cloud</a> account. If you don't have one, you can sign up for a free trial.</li>
<li>The Octopus AI Assistant Chrome extension. You can install it from the <a href="https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha">Chrome Web Store</a>.</li>
</ul>
<p>:::div{.hint}
The Octopus AI Assistant will work with an on-premises Octopus instance, but it requires more configuration. The
cloud-hosted version of Octopus doesn't need extra configuration. This means the cloud-hosted version is the easiest way to get started.
:::</p>
<h2>Creating the project</h2>
<p>Paste the following prompt into the Octopus AI Assistant and run it to create a sample project using the <code>Run Claude Agent</code> step to categorize commits:</p>
<pre><code class="language-markdown">Create a Claude project called "17. Categorize Changes"
</code></pre>
<p>The resulting project uses Build Information to associate commits with a package included in the deployment.</p>
<p>You must provide two API keys for this project to work:</p>
<ol>
<li>A GitHub Personal Access Token (PAT) with <code>repo</code> scope saved in the <code>Project.GitHub.PAT</code> project variable</li>
<li>A Claude API Key saved in the <code>Project.Claude.ApiKey</code> project variable</li>
</ol>
<p>The Claude step also requires the <code>claude</code> CLI to be installed on the worker. This can be provided by an <a href="https://octopus.com/docs/projects/steps/execution-containers-for-workers">Execution Container Image</a> with the following inline <code>Dockerfile</code>:</p>
<pre><code class="language-Dockerfile">FROM python:3.11-slim

# 1. Install prerequisites (including libicu for .NET Calamari compatibility)
RUN apt-get update &#x26;&#x26; apt-get install -y --no-install-recommends \
    curl \
    git \
    libicu-dev \
    ca-certificates \
    &#x26;&#x26; rm -rf /var/lib/apt/lists/*

# 2. Install the native Claude Code CLI tool 
RUN curl -fsSL https://claude.ai/install.sh | bash

# 3. Create a global symlink using the exact path from your install log
# This bypasses the $PATH profile restriction for non-interactive runners
RUN ln -sf /root/.local/bin/claude /usr/local/bin/claude

WORKDIR /app
CMD ["/bin/bash"]
</code></pre>
<p><a href="/blog/img/octo-easy-mode-17-claude/inline-dockerfile.png">:img{ src="/blog/img/octo-easy-mode-17-claude/inline-dockerfile.png" alt="Inline Dockerfile" loading="lazy" }</a></p>
<h2>Categorizing commits</h2>
<p>The prompt defined in the <code>Run Claude Agent</code> step is designed to categorize commits:</p>
<pre><code class="language-markdown">Your task is to rate the impact of the Git commits that contribute to the new version of the application being deployed.

The following is the list of Git commits:

#{each change in Octopus.Deployment.Changes}
#{each commit in change.Commits}
#{commit.LinkUrl}
#{/each}
#{/each}

Output a value between 1 and 10 based on the impact of the changes in the following categories:

* Security
* User Interface
* Documentation
* Business Logic
* Performance
* Code dependencies
* Code refactoring

The result must be a plain JSON blob like this:

```
{
"security": 1,
"userInterface": 4,
"documentation": 7,
"businessLogic": 3,
"performance": 1,
"dependencies": 9,
"refactoring": 5
}
```
</code></pre>
<p>The key to linking Build Information with the <code>Run Claude Agent</code> step is the <code>Octopus.Deployment.Changes</code> variable. You loop over every change, then over every commit in that change. The <code>LinkUrl</code> property is used to embed a link to the commit in the prompt.</p>
<p>You then use the GitHub MCP server to access the commit content:</p>
<p><a href="/blog/img/octo-easy-mode-17-claude/github-mcp.png">:img{ src="/blog/img/octo-easy-mode-17-claude/github-mcp.png" alt="GitHub MCP Server" loading="lazy" }</a></p>
<p>:::div{.hint}
It is best practice to use MCP servers to interact with external services over general CLI tools like <code>curl</code>. The tools exposed by MCP servers have limited scope, are tested, and are constrained by the provided credentials. General tools like <code>curl</code> can initiate literally any web request, and LLMs will often go to great lengths constructing web requests to achieve their goals.</p>
<p>The step also includes a range of <a href="https://octopus.com/docs/octopus-ai/claude-agent-step/security-and-compliance">security and compliance features</a> to restrict the agent.
:::</p>
<h2>Demonstrating a low-risk change</h2>
<p>Start by pushing a Build Information package to Octopus that links to a number of low-risk commits. Save the JSON blob below to a file called <code>buildinfo.json</code>:</p>
<pre><code class="language-json">{
  "BuildEnvironment": "GitHub Actions",
  "Branch": "main",
  "BuildNumber": "658",
  "BuildUrl": "https://github.com/OctopusSolutionsEngineering/Octopub/actions/runs/29776705931",
  "VcsType": "Git",
  "VcsRoot": "https://github.com/OctopusSolutionsEngineering/Octopub",
  "VcsCommitNumber": "a84a77fd046e329bb10405480486ce9c11db6074",
  "Commits": [
    {
      "Id": "a84a77fd046e329bb10405480486ce9c11db6074",
      "LinkUrl": "https://github.com/OctopusSolutionsEngineering/Octopub/commit/a84a77fd046e329bb10405480486ce9c11db6074",
      "Comment": "Accidentally improved the intern with zero tests"
    },
    {
      "Id": "bb8754a163ec1b278c4e2a3e31bcb868d5d0eb70",
      "LinkUrl": "https://github.com/OctopusSolutionsEngineering/Octopub/commit/bb8754a163ec1b278c4e2a3e31bcb868d5d0eb70",
      "Comment": "Accidentally improved Schrödinger's bug against my better judgment"
    },
    {
      "Id": "1e9e34564e7e5352e4fc168377f5dd2585a6069f",
      "LinkUrl": "https://github.com/OctopusSolutionsEngineering/Octopub/commit/1e9e34564e7e5352e4fc168377f5dd2585a6069f",
      "Comment": "Stared menacingly at the dark arts with zero tests"
    }
  ]
}
</code></pre>
<p>If you open the links in the <code>LinkUrl</code> fields, you will see that these commits are gibberish changes to a text file. They are low risk because they don't change any code that is executed in the application.</p>
<p>Push the Build Information package to Octopus using the <code>octopus</code> CLI, replacing the space name with your actual space name.</p>
<p>This is the Bash command:</p>
<pre><code class="language-bash">octopus build-information upload \
  --space "Your Space Name" \
  --package-id "com.octopus:octopub-frontend" \
  --version "20260721.659.1" \
  --file "buildinfo.json" \
  --overwrite-mode "overwrite"
</code></pre>
<p>This is the PowerShell command:</p>
<pre><code class="language-powershell">octopus build-information upload `
  --space "Your Space Name" `
  --package-id "com.octopus:octopub-frontend" `
  --version "20260721.659.1" `
  --file "buildinfo.json" `
  --overwrite-mode "overwrite"
</code></pre>
<p>:::div{.hint}
In a production scenario, you would typically automate the generation of Build Information as part of your CI/CD pipeline. This ensures that the information is always up to date and accurately reflects the state of your codebase.</p>
<p>For the purposes of this demonstration, we are manually constructing and pushing mock Build Information to Octopus to simulate different commit scenarios.
:::</p>
<p>When you deploy a release of the project, the steps will:</p>
<ol>
<li>Print a list of the commit links</li>
<li>Run the <code>Run Claude Agent</code> step to categorize the commits</li>
<li>Extract the JSON blob generated by the Claude agent, and determine if any high-risk categories have a value greater than 5</li>
<li>If any high-risk categories are detected, a manual intervention step will be triggered, requiring a human to approve the deployment</li>
<li>If all categories are low risk, the manual intervention step will be skipped</li>
<li>Proceed to a mock deployment step that simulates deploying the application</li>
</ol>
<p>Since the commits in this example are low risk, each category gets a low score:</p>
<p><a href="/blog/img/octo-easy-mode-17-claude/claude-output.png">:img{ src="/blog/img/octo-easy-mode-17-claude/claude-output.png" alt="Claude Output" loading="lazy" }</a></p>
<p>Based on these scores, the manual intervention step is skipped, and the deployment proceeds automatically.</p>
<h2>Demonstrating a high-risk change</h2>
<p>Save the following code to a file called <code>buildinfo.json</code>:</p>
<pre><code class="language-json">{
  "BuildEnvironment": "GitHub Actions",
  "Branch": "main",
  "BuildNumber": "658",
  "BuildUrl": "https://github.com/OctopusSolutionsEngineering/Octopub/actions/runs/29776705931",
  "VcsType": "Git",
  "VcsRoot": "https://github.com/OctopusSolutionsEngineering/Octopub",
  "VcsCommitNumber": "982860ff9295c75ea3f3f0f963b09f0db3138e4e",
  "Commits": [
    {
      "Id": "7ec4ef85a5aac729b2d3e823307cb4c4caa63d58",
      "LinkUrl": "https://github.com/OctopusSolutionsEngineering/Octopub/commit/7ec4ef85a5aac729b2d3e823307cb4c4caa63d58",
      "Comment": "Enhance URL safety and update footer links for improved security"
    },
    {
      "Id": "982860ff9295c75ea3f3f0f963b09f0db3138e4e",
      "LinkUrl": "https://github.com/OctopusSolutionsEngineering/Octopub/commit/982860ff9295c75ea3f3f0f963b09f0db3138e4e",
      "Comment": "Update dependencies and enhance project configuration for improved compatibility"
    }
  ]
}
</code></pre>
<p>Push the new Build Information package to Octopus using the <code>octopus</code> CLI commands provided earlier, replacing the space name with your actual space name.</p>
<p>These commits represent realistic changes that update dependencies and improve security. When you deploy a release of the project with this Build Information, the <code>Run Claude Agent</code> step will categorize the commits and produce a JSON blob with higher scores in the security and dependencies categories:</p>
<p><a href="/blog/img/octo-easy-mode-17-claude/claude-output-high-risk.png">:img{ src="/blog/img/octo-easy-mode-17-claude/claude-output-high-risk.png" alt="Claude Output High Risk" loading="lazy" }</a></p>
<p>These score values indicate that the changes are high risk, and the manual intervention step will be triggered, requiring a human to approve the deployment before it can proceed.</p>
<h2>What just happened?</h2>
<p>You created a sample project with:</p>
<ul>
<li>Associated Build Information that links to commits in a GitHub repository</li>
<li>A <code>Run Claude Agent</code> step that categorizes commits based on their impact</li>
<li>A step that parses the JSON output from the Claude agent and determines if any high-risk categories are present</li>
<li>A manual intervention step that is conditionally triggered based on the risk assessment</li>
</ul>
<h2>What's next?</h2>
<p>The <a href="/blog/octo-easy-mode-18-progressive-rollouts">next step</a> is an example of progressive rollouts through multiple production environments.</p>]]></content>
    </entry>
    <entry>
      <title>How to promote a release from Development to Production With Argo CD and Octopus Deploy</title>
      <link href="https://octopus.com/blog/promote-release-with-argo-cd-and-octopus" />
      <id>https://octopus.com/blog/promote-release-with-argo-cd-and-octopus</id>
      <published>2026-07-30</published>
      <updated>2026-07-30</updated>
      <summary>Connect Argo CD to Octopus Deploy and promote a single, immutable release from Development to Production with approval gates and a full audit trail.</summary>
      <author>
        <name>Jubril Oyetunji, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>In vanilla Argo CD, "promoting to production" is really just editing a YAML file in a different folder and hoping you got it right. You bump an image tag in a production overlay, commit, and trust that what you just wrote matches what you verified in Development.</p>
<p>This is great until an auditor asks, "Who promoted this, and when?" or an incident traces back to a tag nobody meant to change.</p>
<p>Argo CD is excellent at keeping a cluster in sync with Git, but it has no concept of a release, i.e, no single, frozen artifact that moves from one environment to the next under policy.</p>
<p>In this guide, you will connect Argo CD to Octopus Deploy and turn promotion into a governed release, using the same immutable snapshot to move from Development to Production, gated by approval.</p>
<p>The Audit Stream and connection reuse the setup from our <a href="https://octopus.com/blog/connecting-aws-eks-argo-cd-to-octopus-cloud">EKS connection walkthrough</a>, so this article stays focused on promotion.</p>
<h2>Why "promotion" is hard in vanilla Argo CD</h2>
<p>Argo CD treats each Application as an independent unit. The dev install of your app and the production install are two separate Applications with no codified relationship between them. Nothing in Argo CD knows that "web in production" should receive exactly what "web in dev" was verified with.</p>
<p>Similarly, depending on your organization or team, promoting to an environment could mean a separate namespace or an entirely new cluster, both of which Octopus Deploy can handle.</p>
<p>That fragmented trail is slow and painful to reassemble at exactly the moments you need it most. Like when an auditor asks who promoted what and when, or when you are mid-incident trying to work out what changed.</p>
<p>Whereas, what you want is a single, frozen release that moves through environments under governance: verified once in Development, promoted unchanged to Production, with the who and when captured automatically.</p>
<p>That leaves two do-it-yourself options for promotion, and both are ad-hoc:</p>
<ul>
<li><strong>Hand-edit the image tag</strong> in each environment's overlay folder, commit, and let Argo sync. This is fast, but there is no record of intent, no gate, and nothing stopping a typo from shipping a different tag to Production than the one you tested.</li>
<li><strong>Script a pull request per environment.</strong> This is more controlled, but now your promotion logic lives in CI YAML and shell, reinvented per team, drifting as the estate grows.</li>
</ul>
<p>Whereas, what you want is a single, frozen release that moves through environments under governance: verified once in Development, promoted unchanged to Production, with the who and when captured automatically.</p>
<h2>Prerequisites</h2>
<p>This walkthrough builds on the cluster and Octopus connection from the <a href="https://octopus.com/blog/connecting-aws-eks-argo-cd-to-octopus-cloud">EKS connection post</a>. You do not need EKS specifically, but you do need these pieces in place before the promotion steps make sense:</p>
<ul>
<li><strong>An Octopus Deploy instance with the Argo CD integration</strong> (Octopus Cloud or self-hosted). This is where the project, lifecycle, and release live.</li>
<li><strong>A Kubernetes cluster you can install into.</strong> A local <a href="https://kind.sigs.k8s.io/">kind</a> cluster is enough. Because the Octopus gateway dials outbound, no ingress or public address is required.</li>
<li><strong>Argo CD running in that cluster, connected to Octopus through the gateway.</strong> If you followed the EKS connection post, reuse that same cluster and its gateway connection. If you are starting fresh, the next section installs Argo CD and registers the gateway from scratch.</li>
<li><strong><code>kubectl</code>, <code>helm</code>, and the <code>argocd</code> CLI</strong> installed locally.</li>
<li><strong>A Git repository for your manifests</strong> with Kustomize overlays per environment (the demo uses a public GitHub repo), plus a Git credential in Octopus that can push to it.</li>
</ul>
<h2>The architecture setup</h2>
<p>For this demo, we're aiming for a single Kubernetes cluster with two namespaces that serve as environments, <code>dev</code> and <code>production</code>, each with its own Argo CD Application.</p>
<p>Octopus owns the release and promotion process, and Git remains the source of truth, while Argo CD applies manifests to the cluster.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/architecture.png" alt="How Octopus, Git, and Argo CD interact through commits." loading="lazy" }</p>
<p>::figcaption[Octopus commits the new image tag to the right overlay and triggers a sync through an in-cluster gateway. Argo CD pulls from Git and reconciles each namespace and Octopus never needs inbound access to your cluster.]</p>
<p>:::</p>
<p>The <strong>Octopus gateway</strong> is a small component you install in the cluster with Helm; it dials <strong>outbound</strong> to Octopus over gRPC, so nothing in your cluster needs a public address. That means this entire demo can run on a local <a href="https://kind.sigs.k8s.io/">kind</a> cluster with no ingress.</p>
<p>For an in-depth look at the cluster and gateway connection, see the <a href="https://octopus.com/blog/connecting-aws-eks-argo-cd-to-octopus-cloud">EKS connection post</a>; here, we install Argo CD, register the gateway, and proceed to promotion.</p>
<p>Install Argo CD with a dedicated <code>octopus</code> account so the gateway has its own scoped identity rather than piggybacking on <code>admin</code>:</p>
<pre><code class="language-bash">helm install argocd argo-cd \
 --repo https://argoproj.github.io/argo-helm \
  --create-namespace --namespace argocd --wait --timeout 10m \
 --values - &#x3C;&#x3C; 'EOF'
configs:
  cm:
    accounts.octopus: apiKey
  rbac:
    policy.default: "role:readonly"
    policy.csv: |
      g, admin, role:admin
      p, octopus, applications, get, *, allow
      p, octopus, applications, sync, *, allow
      p, octopus, clusters, get, *, allow
      p, octopus, logs, get, */*, allow
EOF
</code></pre>
<p>With Argo CD running, register the instance in Octopus (<strong>Infrastructure</strong>, then <strong>Argo CD Instances</strong>, then <strong>Add Argo CD Instance</strong>), paste an auth token for the <code>octopus</code> account, and Octopus generates a Helm command for the gateway.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/register-argo-instance.png" alt="Register an Argo CD instance" loading="lazy" }</p>
<p>::figcaption[Registering the Argo CD instance. The service DNS name is the in-cluster address of the Argo CD API server.]</p>
<p>:::</p>
<p>Run the generated Helm command against your cluster, and Octopus confirms the connection: the gateway registers, connects to Octopus, and connects to Argo CD.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/install-gateway.png" alt="Install gateway" loading="lazy" }</p>
<p>::figcaption[The gateway bridges Octopus and Argo CD over an outbound connection. No inbound firewall rules required.]</p>
<p>:::</p>
<p><em>The gateway bridges Octopus and Argo CD over an outbound connection. No inbound firewall rules required.</em></p>
<h3>Map the Applications with annotations</h3>
<p>Octopus needs to know which Argo CD Applications belong to which project and environment. You declare that with two annotations on each Application manifest. No per-application configuration is needed in Octopus; the annotations handle the mapping.</p>
<pre><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web-dev
  namespace: argocd
  annotations:
    argo.octopus.com/project: argo-web-promotion
    argo.octopus.com/environment: development
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/gitops-web-promotion
    targetRevision: main
    path: overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: dev
  syncPolicy:
    automated: { prune: true, selfHeal: true }
    syncOptions: [ CreateNamespace=true ]
</code></pre>
<p>The <code>argo.octopus.com/project</code> annotation ties the Application to the Octopus project, and <code>argo.octopus.com/environment</code> ties it to an Octopus environment. The production Application is identical except <code>name: web-production</code>, <code>argo.octopus.com/environment: production</code>, <code>path: overlays/production</code>, and <code>namespace: production</code>.</p>
<p>When Octopus deploys <code>argo-web-promotion</code> to Development, it now knows <code>web-dev</code> is the Application to update; when it deploys to Production, it updates <code>web-production</code>.</p>
<p>Both overlays are simple Kustomize folders that set the image tag. This is the field Octopus will rewrite:</p>
<pre><code class="language-yaml"># overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
resources:
 - ../../base
images:
 - name: nginx
    newTag: "1.27.0"
</code></pre>
<h2>Building the Octopus project with a Dev to Production lifecycle</h2>
<p>Create an Octopus project and give it a lifecycle with two phases, Development and Production. The lifecycle is what makes promotion ordered, which simply means a release must pass through Development before it can reach Production.</p>
<p>Then add the built-in <strong>Update Argo CD Application Image Tags</strong> step to the deployment process. For each Application matched by annotation, this step retrieves the Git location from the Application, updates the image tag in the manifests, commits the change, and triggers Argo CD to sync. Add a container image reference (the <code>nginx</code> image, from a Docker Hub feed) so the release knows which image to update and what version to pin.</p>
<p>To make the governance visible, add one more step before it: a <strong>Manual intervention</strong> step scoped to the Production environment only. That is your approval gate. It runs when promoting to Production and is skipped for Development, so it stays fast while Production stays governed.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/argo-web-promotion.png" alt="Argo web promotion" loading="lazy" }</p>
<p>::figcaption[Two steps, one governed process. The approval runs only for Production; the image-tag update runs for any environment.]</p>
<p>:::</p>
<h2>Create a release and deploy to Development</h2>
<p>Create a release in Octopus and select the image version to promote, for example <code>nginx:1.27.2</code> (a bump from the <code>1.27.0</code> currently in the overlays). This release is a frozen snapshot of the process, variables, and package versions. Once created, it is immutable: the version that goes to Production later is the exact version you are about to verify in Development, not whatever happens to sit at Git HEAD.</p>
<p>Deploy the release to Development. Octopus commits the new tag to the dev overlay, and Argo CD syncs the <code>dev</code> namespace:</p>
<pre><code class="language-bash">Credential 'gitops-web-promotion' will be used to access the repository
Committing directly to branch for changes in this environment
Cloning repository https://github.com/your-org/gitops-web-promotion
</code></pre>
<p>Within seconds, the dev Application is synced and Healthy on the new tag, while Production is untouched:</p>
<pre><code class="language-bash">$ kubectl get deploy web -n dev -o jsonpath='{..image}'
nginx:1.27.2
$ kubectl get deploy web -n production -o jsonpath='{..image}'
nginx:1.27.0
</code></pre>
<p>That contrast is the whole point: the release moved dev to <code>1.27.2</code>, and Production still runs <code>1.27.0</code> because nothing has promoted it there yet.</p>
<h2>Promote the same release to Production</h2>
<p>Now promote the same release to Production. Because the process has a Production-scoped approval step, the deployment pauses and waits for a human before it touches anything.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/deploy-to-production.png" alt="Promoting the release to Production with an approval step." loading="lazy" }</p>
<p>:::</p>
<p>Production promotion stops at the approval gate; the image update and sync below it are queued, not run.</p>
<p>Approve it, and the same flow runs against the production overlay: Octopus commits the tag to <code>overlays/production</code>, and Argo CD syncs the <code>production</code> namespace. Production now gets exactly what was verified in dev, not a freshly hand-edited value.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/promotion-result.png" alt="Promotion result" loading="lazy" }</p>
<p>::figcaption[Promotion complete. The same release 1.27.2 that ran in Development is now live in Production.]</p>
<p>:::</p>
<p>The project dashboard shows the end state at a glance: one release, both environments, both healthy, with the live status pulled from Argo CD.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/dashboard.png" alt="Project dashboard" loading="lazy" }</p>
<p>::figcaption[Development at 9:06 PM, Production at 9:43 PM after approval. Same release, one predictable shape.]</p>
<p>:::</p>
<h2>The governance you got for free</h2>
<p>Taking a step back, there are a few things this approach has saved you from:</p>
<ul>
<li>
<p><strong>An immutable release snapshot.</strong> Release <code>1.27.2</code> pinned the exact image version. Production could only ever receive what dev verified.</p>
</li>
<li>
<p><strong>A Git commit per environment.</strong> Each promotion is a commit in your history, attributable and reversible:</p>
</li>
</ul>
<pre><code class="language-bash">07d62d8  Octopus Deploy promoted image 1.27.2   (production overlay)
28e2ace  Octopus Deploy promoted image 1.27.2   (dev overlay)
dfdadc8  Initial GitOps repo
</code></pre>
<ul>
<li>
<p><strong>An approval record.</strong> Production promotion required a named human to take responsibility and proceed, captured in the deployment history.</p>
</li>
<li>
<p><strong>One view of what is running where.</strong> The project dashboard shows every environment and the release it holds, with live health from Argo CD, and you can click into any deployment to see who promoted it and when. That single pane matters more as you scale because your Argo CD Applications might be spread across many instances in different clusters, regions, or accounts, and Octopus gives you one place to see and govern all of them instead of tab-hopping between Argo CD UIs.</p>
</li>
</ul>
<p>None of this is captured by default in the hand-edited-overlay approach. Because the Octopus release is a standard, predictable object, the same governance and policy apply no matter what sits underneath</p>
<p>This ties into the core Platform Hub idea: a single deployment shape and consistent governance across every stack you run.</p>
<h2>Going from overlay edits to audited releases</h2>
<p>Promotion should not be a YAML edit you hope you got right. With Argo CD connected to Octopus, it becomes a release you can govern.</p>
<p>Argo CD keeps doing what it does best: reconciling Git with your cluster, while Octopus adds a release model and an audit trail to that flow.</p>
<p>The bigger idea here is <a href="https://octopus.com/use-case/platform-hub">Platform Hub</a>, which offers you one place to see what is running where, and the same governance and audit across every environment, cluster, and Argo CD instance you run, not just the one in this walkthrough.</p>
<p>If you promote Argo CD deployments by hand today, that is the gap it closes. See how <a href="https://octopus.com/use-case/platform-hub">Platform Hub</a> brings your GitOps deployments under one governed roof, read <a href="https://octopus.com/blog/manage-releases-rollbacks-argo-cd">Manage releases and rollbacks with Argo CD</a> for the release mechanics, and <a href="https://octopus.com/start">start for free</a>!</p>]]></content>
    </entry>
    <entry>
      <title>Inside Platform Engineering with Joep Piscaer</title>
      <link href="https://octopus.com/blog/inside-platform-engineering-joep-piscaer" />
      <id>https://octopus.com/blog/inside-platform-engineering-joep-piscaer</id>
      <published>2026-07-23</published>
      <updated>2026-07-23</updated>
      <summary></summary>
      <author>
        <name>Matthew Allford, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>The CNCF landscape has hundreds of logos, dozens of categories, and no shortage of people telling you what belongs in your platform. Joep Piscaer, Field CTO at Portainer, joined me on Inside Platform Engineering with a take I don't hear often enough: that the best platform decision is frequently to add nothing at all.</p>
<p>Joep calls the CNCF landscape a candy shop, and it's a comparison that stuck with me. Just because something's on the shelf doesn't mean it belongs in your cart, and the cost of a bad choice doesn't show up at checkout, it shows up months or years later when someone has to support it.</p>
<h2>Watch the episode</h2>
<p>You can watch the episode with Joep below.</p>
<p><a href="https://www.youtube.com/watch?v=uMn978s5FkE">Inside Platform Engineering with Joep Piscaer</a></p>
<h2>The candy shop problem</h2>
<p>Joep's argument is simple but easy to forget in practice, which is that every tool you add to your platform is a tool you now have to operate, secure, and explain to whoever inherits it. He's not against new tooling on principle, he's against choosing it reflexively because it's popular or well-marketed. His rule of thumb is that the best choice is often no choice at all, and that resisting the landscape is itself a skill worth developing, not a sign you're falling behind.</p>
<h2>Small teams will build lean platforms</h2>
<p>One of the more provocative points Joep made was that a platform team with a budget and an SLA will naturally start building for its own survival, not just for its users. His suggested fix leans further than most people may be comfortable with, keep the team small, ideally under eight people (this is very contextual to the organization), so there's only time for the basics. I liked how he framed a bloated platform as a freight ship rather than a speedboat. Once it's big, you can only change course by a single degree at a time, no matter how good your intentions are.</p>
<h2>Talk to your users before you build</h2>
<p>Joep was adamant that understanding why you're building something matters more than the build itself, going as far as to say a good developer might spend as little as 20% of their time actually writing code. The rest goes into figuring out what's actually needed. This came up multiple times throughout our conversation. His advice for platform teams is to get out of meetings and sit next to the people doing the work, which he only half-jokingly compared to Fisher-Price's old "soul-crushing meeting" toy.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/inside-platform-engineering-joep-piscaer/soul-crushing-meeting.png" alt="An image of a satirical Fisher-Price toy box parody designed by Daniel Picard." loading="lazy" }</p>
<p>:::</p>
<h2>Vibe coding is changing who the platform needs to support</h2>
<p>We spent time on how AI-assisted coding is reshaping who your platform needs to serve. Joep's read is that business users are increasingly vibe-coding their own tools because commodity software rarely fits the way their teams actually work. Once they've got something that works, they just want a URL, not a ticket in your backlog or a crash course in Kubernetes. What struck me was Joep's parallel back to Platform Engineering itself. Just as we're told to go and understand what our users actually need rather than guessing, these business users are doing exactly the same thing for themselves, they just build it rather than ask for it. The job for a platform then becomes hiding all of that complexity so those tools can be deployed simply, while staying lean, secure, and compliant underneath. Whether that's a threat to platform teams or an opportunity probably depends on how ready your platform already is to support something it didn't design.</p>
<p>Happy deployments!</p>
<p>:::div{.hint}</p>
<p>Inside Platform Engineering is a series of conversations with Matt Allford and a guest, bringing their own experience and perspective from the world of Platform Engineering.</p>
<p>You can find more episodes on <a href="https://www.youtube.com/playlist?list=PLAGskdGvlaw24Y-7jTcw09jbzsLw5uL9X">YouTube</a>.</p>
<p>:::</p>]]></content>
    </entry>
    <entry>
      <title>Continuous Delivery Office Hours Ep.7: Modern multi-tenancy</title>
      <link href="https://octopus.com/blog/continuous-delivery-office-hours-e7" />
      <id>https://octopus.com/blog/continuous-delivery-office-hours-e7</id>
      <published>2026-07-22</published>
      <updated>2026-07-22</updated>
      <summary>Find out why traditional SaaS multi-tenancy has been replaced with a superior approach.</summary>
      <author>
        <name>Steve Fenton, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Multi-tenancy is an old concept that dates back to machine sharing. It got a new life with the rise of SaaS and the need to share infrastructure and databases across many tenants. Still, with the rise of lightweight virtualization, the idea of managing multi-tenancy with high code complexity has lost its appeal.</p>
<p>Modern multi-tenancy leans into the ease of allocating dedicated instances to customers, improving isolation, reducing the risk of data leaks, and making it trivial to charge a fair price based on use (instead of subsidizing the noisy neighbors with a volume of under-utilizers).</p>
<h2>Watch the episode</h2>
<p>You can watch the episode below, or read on to find some of the key discussion points.</p>
<p><a href="https://www.youtube.com/watch?v=0whxP3T6U5A">Watch Continuous Delivery Office Hours Ep.7</a></p>
<h2>Moving away from tenanted applications</h2>
<p>There are 3 primary architectural approaches to multi-tenancy.</p>
<ul>
<li>A shared application instance and database: Many customers are using the same running instance, with the application taking care of isolating their use and data.</li>
<li>A shared application instance and a dedicated database: While customers all use the same running instance, their data is stored in a dedicated database, which the application connects to based on the tenant.</li>
<li>Fully isolated infrastructure: Every tenant has a dedicated instance and database running on an allocation of compute.</li>
</ul>
<p>The shared approach comes with many drawbacks. The application's code is more complex and requires more testing to reduce the risk of data being displayed to someone who shouldn't see it. Having a dedicated database limits the complexity and risk to the mechanism that selects the appropriate connection string.</p>
<p>When tenants share application instances, databases, or database servers, one tenant can disrupt service for others, for example, by running a resource-intensive operation. It's difficult to pinpoint where the increased load is coming from, and mechanisms for charging based on use often rely on proxy metrics that don't reflect actual use.</p>
<p>With modern hosting options, like containers, it becomes far easier to achieve high resource use at the infrastructure level, removing the need for applications to be made tenant aware. You can avoid all the complexity by giving each tenant their own application and database.</p>
<p>If you have a customer with high resource needs, their use doesn't slow down other customers or cause an outage. If they need a more powerful instance, they can pay to have one.</p>
<h2>Shifting tenants to deployment-time</h2>
<p>To make modern multi-tenancy work, you need to be able to deploy many more instances and apply the correct configuration when you do so. Modern CD tools take care of this, so you can share a single deployment process with hundreds or thousands of tenants.</p>
<p>When CD tools push a software version out, they can install tenant-specific instances by applying configuration variables. If you have 100 tenants, 3 environments, and 5 settings, the CD tool eliminates the need to manage 1,500 configuration files. They can also help you progressively roll out a new version based on tags so that customers can opt for early access or only the most stable versions.</p>
<p>Multi-tenancy has effectively shifted out of your code and is now managed by your deployment pipeline.</p>
<h2>A tenant isn't always a customer</h2>
<p>We often think of tenants as "customers," but they can just as easily be physical locations, like a hospital, restaurant, or retail store, that need a dedicated instance. Any time you need a dedicated instance, a tenanted deployment can provide it.</p>
<p>If you want to learn more, we also have a <a href="https://octopus.com/whitepapers/modern-view-of-multi-tenancy">white paper on modern multi-tenancy</a> available for download.</p>
<p>Happy deployments!</p>
<p>:::div{.hint}</p>
<p>Continuous Delivery Office Hours is a series of conversations about software delivery, with Tony Kelly, Bob Walker, and Steve Fenton.</p>
<p>You can find more episodes on <a href="https://www.youtube.com/playlist?list=PLAGskdGvlaw3CrxkUOAMmiy928lr5D4oh">YouTube</a>, <a href="https://podcasts.apple.com/us/podcast/continuous-delivery-office-hours/id1872101651">Apple Podcasts</a>, and <a href="https://pca.st/hwjaox59">Pocket Casts</a>.</p>
<p>:::</p>]]></content>
    </entry>
    <entry>
      <title>Code review isn't your bottleneck, even with AI</title>
      <link href="https://octopus.com/blog/code-review-not-bottleneck-even-with-ai" />
      <id>https://octopus.com/blog/code-review-not-bottleneck-even-with-ai</id>
      <published>2026-07-21</published>
      <updated>2026-07-21</updated>
      <summary>Find out why the visibility gap is getting in the way of your continuous improvement process.</summary>
      <author>
        <name>Steve Fenton, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>There are lots of reasons to believe AI has shifted the bottleneck from coding to code review. That's wrong for two reasons, and we can prove it by answering one question.</p>
<blockquote>
<p>For the system you currently work on, how many changes have passed code review but haven't been deployed and enabled for users?</p>
</blockquote>
<p>The further this number is from zero, the less likely it is that coding or code review is your constraint.</p>
<p>The difficulty is that, within the software delivery industry, we've grown so used to certain practices that they now look like they belong. When you search for ways to improve software delivery capability, you don't see them. These practices are so deeply embedded that they are overgrown with moss and are indistinguishable from the hills.</p>
<p>When changes are collected in batches after code review, it proves that code review isn't your bottleneck, and coding wasn't either.</p>
<h2>The desire for fast software delivery</h2>
<p>Writing the code is a very small part of a long value stream. A value stream begins with an opportunity to provide something people want and ends when they get it.</p>
<p>Few organizations are building completely unique software. If the primary factor were speed, they would use something off-the-shelf that's "good enough". As someone has decided to spend vast sums of money creating something bespoke, you need to understand those other needs. What is it that makes it valuable enough to spend so much money on it?</p>
<p>To answer this question, you build the software, make sure it's releasable, and get it into the hands of the people who need to use it. Only then can you see if you're building the right thing.</p>
<h2>The two errors</h2>
<p>Since AI arrived, many people have proclaimed that the bottleneck has shifted from coding to code review. This isn't quite right, as coding wasn't the bottleneck in the first place (error 1), and it's not code review now (error 2). The reason we think either of these things is constraining the flow of value is that mossy hill we all stare past when we look at the mountains.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/code-review-not-bottleneck-even-with-ai/deployment-batch-size.png" alt="Chart showing 92% of teams have deployment batch sizes of more than 1 change." loading="lazy" }</p>
<p>::figcaption[Number of changes per deployment batch]</p>
<p>:::</p>
<p>Just 8% of teams deploy changes independently, while 92% deploy in batches. Only 8% are correct when they say the bottleneck moved from coding to code review. For the vast majority, this isn't true.</p>
<h2>Batches are signposts</h2>
<p>When you ask this batch size question, you peel back the moss and uncover what's beneath. This is how you find the true constraint in your value stream. You might have manual verification steps, a cumbersome change approval board meeting, or no easy way to deploy changes to development, test, and production. These are the things holding you back. Not coding and not code review.</p>
<p>You were likely working in batches before your AI initiative, and AI will certainly result in larger change size and larger batches if you don't pay attention to your constraints. Speeding up the coding stage only adds pressure to the real bottleneck.</p>
<p>Using the constraint to set the pace of your value stream will lead you to invest improvement efforts where they matter most. If you find your retrospectives fail to provide noticeable improvements, it's likely because you're missing that mossy mound. It's the reason some AI initiatives fail to deliver a return on investment, while others succeed.</p>
<h2>But… the data</h2>
<p>Some studies, like <a href="https://about.gitlab.com/resources/ai-accountability-survey-2026/">GitLab's 2026 AI Accountability Report</a>, also say that bottlenecks have shifted from coding to code review, but they also overlook the mossy mound. Where they haven't captured the size or number of changes that haven't reached production users, they can't observe the true bottleneck.</p>
<p>Having invested in AI to increase coding speed, you'll now be tempted to invest in solving the code review problem, or to abandon it. If you're working in batches, you'll discover this makes little difference to your ability to deliver valuable software.</p>
<p>The reasons your organization is resisting solving the batch problem are the real problems you need to fix.</p>
<p>Look for the accumulation of changes throughout your process. Identify the true constraint, and apply the <a href="https://thenewstack.io/2-ways-to-reduce-bottlenecks-with-the-theory-of-constraints/">five focusing steps</a> to manage it; and that includes making all other stages march at the pace of the constraint.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Octopus Easy Mode - Argo CD Manifest Update</title>
      <link href="https://octopus.com/blog/octo-easy-mode-16-argocd-manifest-update" />
      <id>https://octopus.com/blog/octo-easy-mode-16-argocd-manifest-update</id>
      <published>2026-07-17</published>
      <updated>2026-07-17</updated>
      <summary>Learn how to create an Argo CD Manifest Update project</summary>
      <author>
        <name>Matthew Casperson, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>In the <a href="/blog/octo-easy-mode-15-ephemeral-environments">previous post</a>, you created ephemeral environments to simulate the deployment of feature branches. In this post, you'll create a project simulating updating an <a href="https://octopus.com/docs/argo-cd/steps/update-application-manifests">Argo CD Manifest file</a>.</p>
<h2>Prerequisites</h2>
<ul>
<li>An <a href="https://octopus.com/start">Octopus Cloud</a> account. If you don't have one, you can sign up for a free trial.</li>
<li>The Octopus AI Assistant Chrome extension. You can install it from the <a href="https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha">Chrome Web Store</a>.</li>
</ul>
<p>:::div{.hint}
The Octopus AI Assistant will work with an on-premises Octopus instance, but it requires more configuration. The
cloud-hosted version of Octopus doesn't need extra configuration. This means the cloud-hosted version is the easiest way to get started.
:::</p>
<h2>Creating the project</h2>
<p>Teams that have adopted GitOps workflows with Argo CD can use Octopus to commit changes to a Git repository to promote changes through environments. The <a href="https://octopus.com/docs/argo-cd/steps/update-application-manifests">Update Argo CD Application Manifests</a> step in Octopus commits changes to the files referenced by an Argo CD Application.</p>
<p>Paste the following prompt into the Octopus AI Assistant and run it to create a sample project using the <code>Update Argo CD Application Manifest</code> step:</p>
<pre><code class="language-markdown">Create an Argo CD Manifest Update project called "16. Argo CD Manifest Update" with the slug "argo-cd-octopub-manifest" using a Git Connection called "Mock" with a random username and the allowed repository "https://mockgit.octopusdemos.com/*"
</code></pre>
<p>This prompt creates an example project called <code>16. Argo CD Manifest Update</code> with the project slug <code>argo-cd-octopub-manifest</code>. The slug is important because it links an Argo CD Application to an Octopus project.</p>
<p>We then create a <a href="https://octopus.com/docs/infrastructure/git-credentials">Git Credentials</a> defining the credentials required to interact with a Git repository. In this example, we are using a mocked Git repository hosted at <code>https://mockgit.octopusdemos.com</code>. This Git repository lets us use Argo CD steps in Octopus without providing credentials or creating a repository on a platform like GitHub.</p>
<p>Behind the scenes, we also create a mock <a href="https://octopus.com/docs/argo-cd/instances">Argo CD Instance</a> called <code>Mocked Argo CD Instance</code>:</p>
<p><a href="/blog/img/octo-easy-mode-16-argocd-manifest-update/argo-cd-instance.png">:img{ src="/blog/img/octo-easy-mode-16-argocd-manifest-update/argo-cd-instance.png" alt="Argo CD Instance" loading="lazy" }</a></p>
<p>Typically, to create an Argo CD Instance, you must install the <a href="https://octopus.com/docs/argo-cd/instances#installing-the-octopus-argo-cd-gateway">Octopus Argo CD Gateway</a> in the Kubernetes cluster where Argo CD is running. This gateway then registers Argo CD Applications with Octopus and monitors the cluster for any changes.</p>
<p>For this demonstration, however, we register a mock Argo CD Instance and several mock Argo CD Applications. The mock Applications contain the information required for Octopus to modify the correct files in the mock Git repository during a deployment, without requiring an Argo CD cluster.</p>
<p>:::div{.warning}
Some features of the mocked Argo CD Instance will always report errors or warnings. For example, the <code>Gateway Connectivity</code> tab will always report an error, because there was never a real Argo CD cluster to connect to. The manifests deployed by Octopus will always report Git drift because the Argo CD Applications are never updated to reflect the changes. And any attempt to sync Applications in Argo CD as part of the <code>Update Argo CD Application Manifests</code> step will fail.</p>
<p>These errors can be ignored or the features disabled without preventing Octopus from completing a deployment.
:::</p>
<h2>The sample step configuration</h2>
<p>The <code>Repository URL</code> setting in the <code>Update Argo CD Application Manifests</code> step defines the Git repository that Octopus will commit to as part of the deployment. The value <code>https://mockgit.octopusdemos.com/repo/argocd</code> is matched to the <code>Mock</code> <code>Git Credentials</code>, providing the step with the credentials required to commit to the repository.</p>
<p>The <code>Path</code> setting, set to <code>octopub-manifest/template/octopub.yml</code>, defines the template file that will be read, have any <a href="https://octopus.com/docs/projects/variables/variable-substitutions">binding syntax</a> replaced, and persisted to the path defined in the Argo CD Application linked to the project and environment (how these Applications are linked is described later).</p>
<p>To see the contents of these files, check out the mock Git repository:</p>
<pre><code class="language-bash">git clone https://somerandomusername@mockgit.octopusdemos.com/repo/argocd
</code></pre>
<p>:::div{.hint}
The mocked Git repository accepts literally any username. However, commits made by unrecognized usernames are ignored. The <code>Mock</code> <code>Git Connection</code> created by the AI Assistant has unique and recognized credentials that allow Octopus to persist commits. However, the repository contents are reset periodically, so all commits are eventually reverted.</p>
<p>The contents of the Git repository cloned with the credentials above do not include the commits made by Octopus, as the mock Git server treats them as two separate repositories.
:::</p>
<p>View the contents of the sample Argo CD Application:</p>
<pre><code class="language-bash">cat argocd/octopub-manifest/octopub-development.yml
</code></pre>
<p>This is the example Argo CD Application manifest.</p>
<p>Note the <code>repoURL</code> field matches the <code>Repository URL</code> setting. The <code>path</code> field, set to <code>octopub-manifest/application/development</code>, specifies the location in the Git repository where Argo CD finds the manifest files to apply to the cluster.</p>
<p>Also note the <a href="https://octopus.com/docs/argo-cd/annotations">annotations</a> <code>argo.octopus.com/project.&#x3C;application name></code> and <code>argo.octopus.com/environment.&#x3C;application name></code>. These annotations link an Argo CD Application to an Octopus project and environment. The step references only Argo CD Applications that match the project's and environment's slugs. The prompt to create the project specified the slug <code>argo-cd-octopub-manifest</code> because this value is hard-coded in the example YAML:</p>
<pre><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: octopub-manifest-parent-development
  namespace: argocd
  annotations:
    argo.octopus.com/project.octopub-manifest-parent-development: "argo-cd-octopub-manifest"
    argo.octopus.com/environment.octopub-manifest-parent-development: "development"
spec:
  project: default
  sources:
    - name: octopub-manifest-parent-development
      repoURL: https://mockgit.octopus.com/repo/argocd
      # This is the destination folder where the template manifest files are placed
      path: "octopub-manifest/application/development"
      targetRevision: main

  destination:
    server: https://kubernetes.default.svc
    namespace: octopub-manifest-parent-development
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
</code></pre>
<p>Display the <code>octopub.yml</code> file in the directory specified by the <code>path</code> field:</p>
<pre><code class="language-bash">cat argocd/octopub-manifest/application/development/octopub.yml
</code></pre>
<p>This is the file applied by Argo CD:</p>
<pre><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: octopub-manifest-development
  namespace: argocd
spec:
  project: default
  sources:
    - name: octopub-development
      repoURL: https://mockgit.octopus.com/repo/argocd
      path: "octopub/octopub-frontend"
      targetRevision: main
      helm:
        values: |
          image:
            repository: ghcrfacade-a6awccayfpcpg4cg.eastus-01.azurewebsites.net/octopussolutionsengineering/octopub-frontend
            tag: latest
          mockBackend: true
          overrideTheme: blue

  destination:
    server: https://kubernetes.default.svc
    namespace: octopub-manifest-development
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
</code></pre>
<p>Now display the template file defined by <code>Path</code> field on the Octopus step:</p>
<pre><code class="language-bash">cat argocd/octopub-manifest/template/octopub.yml
</code></pre>
<p>Note that it includes the binding syntax <code>#{Octopus.Environment.Name | ToLower}</code> and <code>#{Project.Frontend.Theme}</code>:</p>
<pre><code class="language-yaml">apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: octopub-manifest-#{Octopus.Environment.Name | ToLower}
  namespace: argocd
spec:
  project: default
  sources:
    - name: octopub-manifest-#{Octopus.Environment.Name | ToLower}
      repoURL: https://mockgit.octopus.com/repo/argocd
      path: "octopub/octopub-frontend"
      targetRevision: main
      helm:
        # Octopus will replace the value for overrideTheme during deployment
        values: |
          image:
            repository: ghcrfacade-a6awccayfpcpg4cg.eastus-01.azurewebsites.net/octopussolutionsengineering/octopub-frontend
            tag: latest
          mockBackend: true
          overrideTheme: #{Project.Frontend.Theme}

  destination:
    server: https://kubernetes.default.svc
    namespace: octopub-manifest-#{Octopus.Environment.Name | ToLower}
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
</code></pre>
<p>:::div{.hint}
The <code>Project.Frontend.Theme</code> variable is defined on the project. The <code>Octopus.Environment.Name</code> variable is provided as a <a href="https://octopus.com/docs/projects/variables/system-variables">system variable</a>.
:::</p>
<p>:::div{.hint}
The pipe in the binding syntax <code>#{Octopus.Environment.Name | ToLower}</code> implements the <code>ToLower</code> <a href="https://octopus.com/docs/projects/variables/variable-substitutions#filters">filter</a>.
:::</p>
<p>The purpose of the <code>Update Argo CD Application Manifests</code> step is to:</p>
<ul>
<li>Read the template file (<code>argocd/octopub-manifest/template/octopub.yml</code>)</li>
<li>Replace any binding syntax with the value of the associated Octopus variable (<code>Project.Frontend.Theme</code> and <code>Octopus.Environment.Name</code>)</li>
<li>Commit the new file to the file of the same name (<code>argocd/octopub-manifest/application/development/octopub.yml</code>) referenced by the Argo CD Application linked via the <code>argo.octopus.com/project.&#x3C;application name></code> and <code>argo.octopus.com/environment.&#x3C;application name></code> annotations</li>
</ul>
<p>This allows Octopus to inject environment-specific configuration into the files referenced by an Argo CD Application as a deployment progresses through environments:</p>
<p>[<a href="/blog/img/octo-easy-mode-16-argocd-manifest-update/diagram.png">:img{ src="/blog/img/octo-easy-mode-16-argocd-manifest-update/diagram.png" alt="Argo CD Workflow Diagram" loading="lazy" }</a>](/blog/img/octo-easy-mode-16-argocd-manifest-update/diagram.png)</p>
<p>:::div{.hint}</p>
<p>The Argo CD Application linked to a project and environment is not modified by Octopus. Only the files referenced by the Application are modified. Typically, these files will be plain Kubernetes manifests.</p>
<p>However, using the Argo CD <a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/cluster-bootstrapping/">App of Apps pattern</a>, it is possible to modify a child Application referenced by a parent Application. This is the pattern used in this post.</p>
<p>:::</p>
<h2>Performing a deployment</h2>
<p>Create a new release for the project and deploy it to the <code>Development</code> environment. Note that Octopus commits a change reflecting the new value of the <code>Project.Frontend.Theme</code> and <code>Octopus.Environment.Name</code> variables replaced in the template YAML file:</p>
<p><a href="/blog/img/octo-easy-mode-16-argocd-manifest-update/task-log.png">:img{ src="/blog/img/octo-easy-mode-16-argocd-manifest-update/task-log.png" alt="Octopus Task Logs" loading="lazy" }</a></p>
<p>As you promote the deployment through environments, each new environment will inject a unique value for the <code>Project.Frontend.Theme</code> and <code>Octopus.Environment.Name</code> variables into the child Argo CD Application manifest.</p>
<p>In a production scenario, Argo CD will detect the changes and apply them to the Kubernetes cluster. Of course, in this example, there was no Kubernetes cluster, since everything is mocked, but you can still observe commits being made to the Git repository.</p>
<h2>What just happened?</h2>
<p>You created a sample project with:</p>
<ul>
<li>A mock <code>Argo CD Instance</code> and a number of mock Applications</li>
<li>A mock <code>Git Credentials</code> pointing to a mock Git server</li>
<li>An <code>Update Argo CD Application Manifests</code> step that commits a processed template file back to the mock Git repo</li>
</ul>
<h2>What's next?</h2>
<p>The <a href="/blog/octo-easy-mode-17-claude">next step</a> is an example of categorizing and summarizing Octopus deployments using AI.</p>]]></content>
    </entry>
    <entry>
      <title>The compliance ratchet</title>
      <link href="https://octopus.com/blog/the-compliance-ratchet" />
      <id>https://octopus.com/blog/the-compliance-ratchet</id>
      <published>2026-07-15</published>
      <updated>2026-07-15</updated>
      <summary>Find out why AI creates an equal-and-opposite compliance reaction, and why it's hard to sustain the productivity when this happens.</summary>
      <author>
        <name>Paul Stovell, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Here's a thought experiment. Take the best, most productive engineering team you can imagine, and parachute them into a large, heavily regulated enterprise. What happens to their output?</p>
<p>It drops. Not because they forgot how to write code, but because the organization around them has a different risk tolerance. That's the part of the AI productivity conversation we're missing. We keep talking about developer velocity, but rarely talk about compliance velocity. But if you want to increase one, you have to increase the other.</p>
<h2>It's not a code-writing problem</h2>
<p>The constraint on large software teams hasn't been how fast engineers can write code. Most engineers could ship a weekend project to production in an afternoon. Put those same engineers inside a mid-sized or large company, and the same change takes days or weeks. That's not because the tooling is worse, but because the risk and compliance process around every change is doing exactly what it was built to do.</p>
<p>That's why an initiative that seeks to "generate changes faster with AI" fails to solve our problem. It was already possible to write code quickly; what we struggled with was managing the risk of shipping changes.</p>
<h2>Every action has a reaction</h2>
<p>This is where physics is a useful metaphor. For every increase in the volume of change, especially in a risk-averse environment, there's an equal and opposite reaction from the organization's risk and compliance functions.</p>
<p>Here's an example. A team is making 100 changes a day. They introduce AI and increase this to 200 changes per day, but production starts falling over twice as often, even though the change failure rate is the same. That causes risk and compliance to take an interest and introduce processes and policies to reduce the failures.</p>
<p>Within a few weeks, the team is back to 100 changes a day. Half of them are AI-authored, but there's no productivity gain to show for it. The system has found its new equilibrium, and it looks a lot like the old one, except for all the new processes that compliance wrapped around it.</p>
<p>There's a second version of this reaction, and it's less about infrastructure and more about experience. Small teams build up deep, shared context about the problem they're solving and the customer they're solving it for. Most software has many authors but one user at a time living through the whole journey. As a team grows, holding that cohesion gets harder, but at least the humans on the team still talk to each other.</p>
<p>Now give every one of those engineers their own AI coding assistant. Each engineer becomes far more productive individually, but each assistant has even less shared context than the humans did. The result is the same pattern: a high volume of change, but a more disjointed product experience, with the end-to-end story getting lost.</p>
<p>The equal-and-opposite reaction to that fragmentation is usually a heavy-handed swing back toward centralization. We saw that a decade ago, when "let every team choose its own tools" gave way to standardized platforms, because nobody could move between teams without relearning their entire stack.</p>
<p>These reactions are rational. They are the result of a system correcting for risk that increases faster than anyone can account for.</p>
<h2>The one-way ratchet</h2>
<p>There's something even more crucial than the equilibrium problem. Compliance only ever ratchets up; it never ratchets down.</p>
<p>Once a new rule shows up, like a mandatory review step, an extra test suite, or a new sign-off, it rarely gets removed, especially once it's on a regulator's radar. Velocity can go up and down as the team changes. Compliance doesn't work that way. It accumulates.</p>
<p>Imagine a team gets excited about AI and starts generating twenty changes a day instead of one per engineer. Production instability goes up. A data leak that used to happen once a year now happens twenty times. New compliance requirements get bolted on to stop the bleeding. Eventually, the team realizes the strategy isn't working and reverts to its old habits, but the compliance burden doesn't go away with them. They're now doing less, with more overhead, than when they started.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/the-compliance-ratchet/equal-and-opposite-reaction-diagram.png" alt="Diagram shows change volume increasing, then compliance overhead ratcheting up in response. This results in change volume returning to a lower volume, while the compliance overhead remains." loading="lazy" }</p>
<p>::figcaption[The compliance ratchet in action.]</p>
<p>:::</p>
<p>This is also how bad global solutions get applied to local problems. If one engineer had production access they shouldn't have had and made a mistake, the right fix is local: work out why they had that access and fix it. The wrong fix (but the one organizations reach for when they're not being deliberate) is global: nobody gets production access, ever, for anything. It solves the immediate problem and creates a dozen new ones.</p>
<p>Sometimes it goes even further than the company. A handful of businesses misuse a technology, a regulator responds with a blanket rule, and now everyone on the internet has to click through a cookie banner. That's what happens when a few actors are irresponsible with something new, whether that's self-driving cars, AI, or otherwise. Regulators aren't looking for a reason to step in, but companies force them to through irresponsible decisions.</p>
<h2>Ship safer, not just faster</h2>
<p>If your company has a new initiative to boost AI-driven productivity, here's the thing worth saying out loud before it starts: We already know risk and compliance will find equilibrium with whatever volume of change you produce. So plan for that from day one.</p>
<p>If every engineer on the team is using AI purely to generate more code, faster, you will accelerate the compliance ratchet. The healthier split is roughly half and half. Half the team should focus on how to make changes faster, and the other half on how to make those changes safer, more compliant, and less risky, with AI doing some of that work, too.</p>
<p>That second half of the work is genuinely interesting, and it's underrated. AI is well-suited to reasoning about risk, not just generating change. It could review a pull request and decide whether a one-line CSS tweak needs a human reviewer at all, while flagging any change that touches the payments pipeline for real scrutiny. It could review the build and decide which of the three hours of automated UI tests are relevant to a README update, rather than running the whole suite, which is what the pipeline does.</p>
<p>None of that reduces the volume of change getting shipped. It reduces the risk associated with it, which is what's actually been holding teams back.</p>
<p>The foundation of every software business is customer trust, and trust is not built by shipping more. If AI makes you ship faster without making you ship safer, you're not getting more productive, you're just winding the compliance ratchet a little tighter, and that one doesn't wind back.</p>]]></content>
    </entry>
    <entry>
      <title>Feature Flags (Public Preview) in Octopus Deploy</title>
      <link href="https://octopus.com/blog/feature-flags-public-preview" />
      <id>https://octopus.com/blog/feature-flags-public-preview</id>
      <published>2026-07-13</published>
      <updated>2026-07-13</updated>
      <summary>Feature Flags are now available in Public Preview for Octopus Cloud. Toggle features per environment and tenant, roll out progressively, and roll back instantly, without redeploying.</summary>
      <author>
        <name>Michael Richardson, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Do you ever wish you could release a change to just a few users, to ensure it works and get feedback, before rolling it out to everyone? Have you ever deployed a change to production, only to see your logs start filling with errors, and wished you could instantly revert while you corrected the problem?</p>
<p>If so, we're excited to share a feature we've been incubating — Feature Flags in Octopus.</p>
<p>Feature flags let teams control when and where new features are enabled, without needing to redeploy. You can experiment, test safely, roll back instantly, and ship continuously with confidence.</p>
<p>With Octopus Feature Flags you can:</p>
<ul>
<li>Toggle features on or off instantly — no redeployment required.</li>
<li>Progressively roll out changes — deliver an upgrade to a single environment, to 10% of your tenants, or to a specific cohort of users.</li>
<li>Develop on your main branch, keeping unfinished features safely hidden behind flags.</li>
</ul>
<h2>Built on OpenFeature</h2>
<p><a href="https://openfeature.dev/">OpenFeature</a> is an open standard that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool or in-house solution.</p>
<p>When we came to build Octopus Feature Flags, we chose OpenFeature as the client SDK.</p>
<p>This means you get a robust, battle-tested SDK designed by the best minds in the feature flag business. More importantly, it means no vendor lock-in. If at some point you want to switch feature flag providers (we hope you never do), it's a single line of code to change the registered provider. You can even register multiple providers. And we get to contribute to a thriving project.</p>
<p>As OpenFeature offers <a href="https://openfeature.dev/docs/reference/sdks">SDKs for all popular programming languages</a>, we only need to create an Octopus OpenFeature provider for each language.</p>
<p>Here's an example of configuring OpenFeature to use the Octopus Provider and evaluate a flag:</p>
<pre><code class="language-typescript">// configure OpenFeature to use Octopus as the feature flag provider
OpenFeature.setProvider(new OctopusFeatureProvider({ clientIdentifier: 'my-client-id' }));
const featureFlags = OpenFeature.getClient(); 

// evaluate a feature flag
const withDarkMode = await featureFlags.getBooleanValue('dark-mode', false); 

if (withDarkMode) {
    // enable dark mode option
} else {
    // light mode only 
}
</code></pre>
<h2>Built into Octopus for seamless Continuous Delivery</h2>
<p>Octopus Feature Flags live in Octopus projects. They're not a stand-alone product — they integrate deeply with the Octopus features you already use, like environments, tenants, and releases, and they're designed for releasing changes.</p>
<h3>Environment targeting</h3>
<p>Deliver a different experience in each environment. Each environment has its own feature flag settings, so you can turn a feature on in Development and Test for your team to trial, while it stays safely off in Production.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/feature-flags-public-preview/feature-flags-list.png" alt="The feature flags list in Octopus, showing flags toggled on and off per environment" loading="lazy" }</p>
<p>:::</p>
<h3>Tenant rollout</h3>
<p>Deliver an upgrade to 10% of your tenants, and dial it up from there. You can enable a flag for specific tenants, for tenants matching a tenant tag, or for a percentage of tenants. You can even <em>exclude</em> specific tenants; maybe your tenants represent your customers, and there are select customers you'd prefer not to include as early adopters.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/feature-flags-public-preview/feature-flag-tenants.png" alt="The tenant rollout configuration for a feature flag, with included, excluded, and percentage rollout options" loading="lazy" }</p>
<p>:::</p>
<h3>Minimum version targeting</h3>
<p>Because Octopus knows which versions of your project are deployed to each environment, you can configure a minimum version for a feature flag. The flag is enabled only after that release version (or a later one) has been deployed to the environment.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/feature-flags-public-preview/feature-flag-min-version.png" alt="The minimum version setting for a feature flag, which enables the flag only after that version is deployed" loading="lazy" }</p>
<p>:::</p>
<h3>Client rollout and Segments</h3>
<p>Release a feature to 5% of your users, or only to a chosen cohort — your staff, a geographic region, or a license type. Octopus Feature Flags support enabling a change for a percentage of your users (<em>client rollout</em>), or for a specific cohort via <em>Segments</em>.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/feature-flags-public-preview/feature-flag-segments.png" alt="The client rollout configuration for a feature flag, with a rollout percentage and key/value segments" loading="lazy" }</p>
<p>:::</p>
<p>Segments are built on top of OpenFeature's <a href="https://openfeature.dev/docs/reference/concepts/evaluation-context/">Evaluation Context concept</a>. This lets your application pass context (for example information about the current user like geography, or license type) that feature flag providers, Octopus in this case, can use when evaluating a flag.</p>
<p>The following code sample demonstrates adding context values from your application code:</p>
<pre><code class="language-typescript">// values can be added at one of three different levels: global, client, or invocation

// add a value to the global context
// an example of a global context value might be if you have multiple sites, for different geographic regions
OpenFeature.setContext({ site: 'octopetshop.com.au' });

// add a value to the client context
// an example of values typically added to the client context are those associated with a specific web request, such as the user details
const client = OpenFeature.getClient();
client.setContext({ 
    email: webRequest.user.email,
    licenseType: webRequest.user.license.type 
    });

// add a value to the invocation context
// the invocation context may be used to add values specific to the flag being evaluated
// for example a pet shop may have a feature specifically for dogs, and add a context 
// value to indicate if dog food has been added to a shopping cart 
const context: EvaluationContext = {
  cartContainsDogFood: 'true',
};

const flagValue = await client.getBooleanValue('dog-profile', false, context);
</code></pre>
<p>Read more about <a href="https://octopus.com/docs/feature-flags/targeting#segments">how Segments are evaluated</a> in our docs.</p>
<h2>Supported languages</h2>
<p>We currently have OpenFeature providers for the following languages:</p>
<ul>
<li><a href="https://github.com/OctopusDeploy/openfeature-provider-ts-web">TypeScript/JavaScript</a> (web client)</li>
<li><a href="https://github.com/OctopusDeploy/openfeature-provider-dotnet">.NET</a> (server)</li>
<li><a href="https://github.com/OctopusDeploy/openfeature-provider-java">Java</a> (server)</li>
</ul>
<p>If you're interested in using Octopus Feature Flags, and your language isn't currently supported, please register your interest below and tell us which language you need.</p>
<h2>Try Octopus Feature Flags</h2>
<p>Octopus Feature Flags are now available to Octopus Cloud customers as a Public Preview. To get access, <a href="https://survey.octopus.com/t/piv3LpVWWmus">register your interest</a>.</p>
<p>Feature Flags are available in all license tiers, including the Free tier. They're currently available for Octopus Cloud only; we plan to make them available to self-hosted Octopus Server customers soon.</p>
<p>We've been releasing features in Octopus Deploy using Octopus Feature Flags for the past 12 months, and they've improved our ability to release progressively, and to roll back instantly, meaning our customers — that's you — experience a more stable product than ever. We would love to help you do the same for <em>your</em> customers.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Target tags are now managed with tag sets</title>
      <link href="https://octopus.com/blog/tag-sets-new-functionality" />
      <id>https://octopus.com/blog/tag-sets-new-functionality</id>
      <published>2026-07-07</published>
      <updated>2026-07-07</updated>
      <summary>More control over your target tags and how you can use them at deployment time.</summary>
      <author>
        <name>Michelle O'Brien, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>If you've been using target tags (the artist formerly known as target roles) in Octopus Deploy, you've probably run into the limitation that once a tag is created, there's no way of maintaining it. It's an all-or-nothing system; if you find a typo or need to update a team name, you either have to live with it or delete that tag and start again.</p>
<p>We've migrated target tags to be managed with tag sets, which means you can now create, edit, and delete tags on your deployment targets the same way you manage tag sets elsewhere in Octopus.</p>
<h2>What this means for those already using target tags</h2>
<p>Target tags still work the same way for filtering deployments and scoping steps to specific targets but they're now backed by the tag set system, giving you improved tag maintenance. You can now:</p>
<ul>
<li>Rename a tag when your naming conventions change</li>
<li>Use SingleSelect or MultiSelect tag set types to enforce data integrity.</li>
<li>Improve the organization of target tags by splitting these out into different sets.</li>
</ul>
<p>:::figure</p>
<p>:img{ src="/blog/img/tag-sets-new-functionality/deploy-to-tag.png" alt="Screenshot showing how to deploy to target tag" loading="lazy" }</p>
<p>:::</p>
<h2>Use cases</h2>
<p><strong>Organize and provide context</strong> - for teams running multi-cloud infrastructure, it's common to need both the provider and the workload type when targeting deployments. For example:</p>
<ul>
<li>Cloud Provider (SingleSelect): <code>aws, azure, gcp, on-premises</code></li>
<li>Workload (MultiSelect): <code>containerized, vm, serverless</code></li>
</ul>
<p><strong>Deploy to specific tags</strong> - trigger a runbook that restarts services only on targets tagged containerized, leaving VM-based workloads alone during an incident.</p>
<p><strong>Exclude specific tags</strong> - exclude targets that are currently in use to ensure deployments don't result in downtime during high traffic periods</p>
<p><strong>Deploy to tags across multiple tag sets</strong> - Deploy to targets tagged production (Tier) OR regulated (Compliance) to make sure a critical security patch reaches everything that's either customer-facing or subject to compliance requirements.</p>
<h2>Getting started</h2>
<p>If you're already using target tags, they've already been migrated to a new tag set 'Default Target Tags'. Head to Library > Tag Sets to see them and start managing them alongside your other tag sets. If you're setting up target tags for the first time, take a moment to think about what dimensions matter to your infrastructure; role, cloud provider, team ownership, compliance scope, and model those as separate tag sets rather than trying to pack everything into one.</p>
<h3>Learn more</h3>
<p>For guidance on designing tag sets, check out our <a href="https://octopus.com/docs/tenants/tag-sets#design-tag-sets-carefully">documentation on tag set best practices</a>.</p>
<p>For more information on deploying to, or excluding tags read more <a href="https://octopus.com/docs/releases/creating-a-release#deploy-to-a-specific-subset-of-deployment-targets">our docs</a>.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Why keeping Octopus up to date matters</title>
      <link href="https://octopus.com/blog/why-keeping-octopus-up-to-date-matters" />
      <id>https://octopus.com/blog/why-keeping-octopus-up-to-date-matters</id>
      <published>2026-07-07</published>
      <updated>2026-07-07</updated>
      <summary>The upgrade you keep skipping is costing you more than you think.</summary>
      <author>
        <name>Chris Fraser, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Octopus Deploy is purpose-built for deployment orchestration: tenants, runbooks, environments, lifecycle gates, and variable scoping, and is designed to work with whatever CI you already have. It's infrastructure-agnostic by design, built for enterprise environments where teams run multiple clouds, target types, and toolchains at the same time.</p>
<p>Octopus comes in two flavours: Octopus Cloud, where we host and manage everything for you, or Octopus Server, where you install and run it on your own infrastructure.</p>
<p>Running an outdated Octopus Server is a quiet risk. This post covers why staying current matters, and why Octopus Cloud is worth considering if you'd rather focus on shipping software than maintaining the tool that ships it.</p>
<p>Below, I cover what Octopus updates actually contain, because security patches, bug fixes and more, matter just as much as new features.</p>
<h2>Performance improvements</h2>
<p>Performance is key not only for our customers but also for us. Every time we bake a new build internally, we test it to ensure it's not worse than the previous version. We run tests against our software, and our staff get to try it out first before it propagates upwards.</p>
<p>You might be asking yourself, "What does this look like?" Bob Walker, Field CTO at Octopus, covers this in a great talk, <a href="https://www.youtube.com/watch?v=zZ7bDPZMCqY">available on YouTube</a>. The talk covers how we ship changes, who gets to test them, and when these changes land for our self-hosted customers.</p>
<p>If you don't already know, Octopus Cloud is one quarter ahead of Octopus Server.</p>
<p>So what does it look like? If the build is happy against automated tests:</p>
<ul>
<li>Staff - We dog food our own app, our main deploy instance is the first one that's updated</li>
<li>Canary Customers (+3 days) - We deploy to a random subset of Octopus Cloud customers, about 5% of active instances, which is randomized each time</li>
<li>Stable (+2 days) - We then deliver to the majority of our Cloud customers' instances</li>
<li>Laggards - By this point, the release has already been running in production across thousands of instances, and we update the remaining Octopus Cloud instances</li>
<li>Octopus Server - Eventually, self-hosted Octopus customers will be able to download the latest version of our software</li>
</ul>
<p>Should you want to learn more about performance for your self-hosted instance of Octopus Server, be sure to check out the <a href="https://octopus.com/docs/administration/managing-infrastructure/performance">documentation on performance</a>. We handle performance for you when you're on Octopus Cloud.</p>
<h2>Improved compatibility &#x26; integrations</h2>
<p>We're always ensuring Octopus remains compatible with the most popular software tools and integrations. We have documentation on <a href="https://octopus.com/docs/support/compatibility">compatibility</a> that goes into detail, as well as our <a href="https://octopus.com/integrations">integrations page</a> on our website.</p>
<p>By staying up to date, you ensure you are ready for whatever the world throws at you and can adopt and implement new integrations as they become available.</p>
<h2>Bug fixes</h2>
<p>No one likes bugs; these can be nuanced and overlooked. You can view our <a href="https://octopus.com/downloads">release notes</a> on our website, and you can also track which version you are on and which target version you plan to upgrade to using the <a href="https://octopus.com/downloads/compare">compare versions</a> option.</p>
<p>We present information clearly about breaking changes, bugs, and more.</p>
<h2>Quality of life tweaks</h2>
<p>We believe in listening to our customers' feedback; it's quite important to us and one of our core values for every Octonaut at Octopus.</p>
<p>Our customers help us sharpen our product even further, and we often revisit features, integrations, and more to refine them and make their lives easier.</p>
<p>You can learn more about Octopus's core values in <a href="https://handbook.octopus.com/getting-oriented/values">our handbook</a>.</p>
<h2>Security patches for CVEs</h2>
<p>Everyone knows that a core part of keeping your software up to date is addressing security issues, and we publish this information in various places:</p>
<ul>
<li>Release notes</li>
<li><a href="https://advisories.octopus.com/">Security Advisories page</a></li>
</ul>
<p>We also have a <a href="https://octopus.com/security/disclosure">Security Disclosure Policy</a>.</p>
<h2>Compliance</h2>
<p>Building on security patching, you have to ask yourself: how do you remain compliant if you don't update your software regularly?</p>
<p>It's important that you understand and upgrade your software regularly; this isn't just an Octopus need, it's for all software. By staying up to date, you ensure your business remains compliant and that audit checks pass with flying colors.</p>
<h2>Enhancements to existing features</h2>
<p>Just because we've shipped a feature doesn't mean it's done and forgotten; we're always listening and looking for ways to improve what we offer.</p>
<p>You'll always gain by updating Octopus software, and more often than not, you'll learn about these in our <a href="https://octopus.com/blog">blog section</a> on our website. You can also subscribe, so you're always up to date with any new blogs that are published.</p>
<p>A great example of this is Platform Hub, where we are releasing this functionality to our customers. You might be asking what Platform Hub is? Check that out <a href="https://octopus.com/blog/introducing-platform-hub">in this blog post</a>. We also have a <a href="https://octopus.com/use-case/platform-hub">feature page</a>, should you be interested.</p>
<h2>New features</h2>
<p>We love building features that make developers' lives easier.</p>
<p>You can see what we're up to by visiting our <a href="https://roadmap.octopus.com/">public roadmap</a>, leave us a signal on what's important to you by voting, and you can also submit ideas directly to our product and engineering teams for consideration.</p>
<h2>Staying within vendor support</h2>
<p>Every Octopus software release receives six months of critical patches.</p>
<p>You can learn more about this on our <a href="https://octopus.com/blog/releases-and-lts">blog post</a>, where we discuss it, and in our <a href="https://octopus.com/docs/administration/upgrading">documentation</a>, which covers it for self-hosted Octopus customers.</p>
<p>Should you ever need to contact the <a href="https://octopus.com/support">Octopus Deploy Support Team</a>, the team will be happy to help.</p>
<h2>Deprecations</h2>
<p>Occasionally, Octopus will deprecate features that will no longer be supported. These features are eventually removed.</p>
<p>Staying up to date means you're protected against known vulnerabilities as soon as fixes are available, and you're not carrying risks that have already been solved.</p>
<p>You can learn more about this <a href="https://octopus.com/docs/deprecations">in our docs</a>.</p>
<h2>Conclusion</h2>
<p>The longer you leave it (not updating regularly), the more risk you carry, the more it costs to resolve when something breaks, and the longer it takes to get back on track. Staying current keeps your attack surface small.</p>
<p>If you prefer that Octopus handle upgrading Octopus for you, then I'd recommend Octopus Cloud. Octopus Cloud reliably hosts thousands of Octopus Deploy customers.</p>
<p>Octopus Cloud is the easiest way to run Octopus Deploy. It has the same software and functionality as Octopus Server, except we host it for you and we call it a Cloud instance. You don't need to download, install, or manage it yourself. You can get started with a <a href="https://octopus.com/free-signup">free account</a> to try it out.</p>
<p>You can learn more about its architecture <a href="https://octopus.com/blog/octopus-cloud-architecture">in this blog post</a>.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Code review is theater now</title>
      <link href="https://octopus.com/blog/code-review-is-theater-now" />
      <id>https://octopus.com/blog/code-review-is-theater-now</id>
      <published>2026-07-03</published>
      <updated>2026-07-03</updated>
      <summary>AI doubled PR volume. Bugs tripled. Code review can't keep up. The fix isn't better reviews. It's pipelines that verify what they ship, every time.</summary>
      <author>
        <name>John Bristowe, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Back in March, Gene Kim shared a conversation he had with Jez Humble <a href="https://www.linkedin.com/posts/realgenekim_enterprise-ai-summit-april-9-10-2026-activity-7439162723055300608-HEI9">on LinkedIn</a>. Jez made a beautifully sarcastic remark:</p>
<blockquote>
<p>Don't worry about code reviews, Gene. Code reviews and approvals have always involved a lot of theater. We just need to perpetuate that illusion a little longer and keep pretending that humans are actually reviewing all that agent-generated code.</p>
</blockquote>
<p>Jez is absolutely right; code reviews do involve a lot of theater. Especially now in the era of AI-generated code. In the short amount of time since this post, this trend has become more pronounced. Code review used to be considered a solid approach to ensuring quality and compliance. It just isn't anymore, and we need to be honest about its effectiveness for development teams today.</p>
<h2>The chocolate belt wrappers</h2>
<p>Consider the all-too-familiar process of reviewing a pull request (PR). The notification bell icon lights up, indicating that you have something to review. You open it, review the code, slap "LGTM" on it, and click "approve." <em>It compiles. Ship it.</em></p>
<p>Now consider the scenario in which agents write the majority of the PRs. You probably know how this ends up if you've ever seen the <a href="https://www.youtube.com/watch?v=A2x8N4DjxnE">"Job Switching" episode of <em>I Love Lucy</em></a>.</p>
<p><a href="https://www.youtube.com/watch?v=A2x8N4DjxnE">Lucy and Ethel wrapping chocolates</a></p>
<p>In the episode, Lucy and Ethel take jobs on an assembly line wrapping chocolates. Everything starts fine until then the belt speeds up. Lucy and Ethel can't keep pace, so they start hiding chocolates wherever they can. The chocolates keep coming. The wrapping of chocolates, what we call code review, becomes theater.</p>
<p>In our world, these chocolates are PRs, AI coding agents are the belt, and code review is Lucy, frantically trying to keep up while the quality of what's getting through drops with every passing minute. A lot of what's coming off that belt can be slop. It compiles. (Or, sometimes not.) If you're lucky, it passes your test matrix. Looking closely at the code, it looks fine until you realize the model copied a pattern from its training data that doesn't actually fit your problem. The person reviewing would likely not realize this. The reviewer would likely check whether the syntax and structure are correct, not whether the code should exist in the first place.</p>
<p>Agents can produce huge chunks of code in the time it takes to read this sentence. The PRs reflect this. Now consider the burden this places on a reviewer. Is it reasonable to evaluate a 40,000-line change? Does it get better if we atomize it into 4,000 tiny 10-line diffs? You can read each diff and still miss whether it's the right change. That's because you weren't part of the reasoning that produced it. You have no context whatsoever. It's like flipping to the middle of a book and claiming you know where you are in the story.</p>
<p>Yes, AI makes producing code much, much faster. However, reviewing that code has become much, much harder. As an industry, we tout and celebrate the speed. But we don't talk about the PRs piling up, putting <a href="https://thenewstack.io/cleanup-cost-ai-code/">everyone downstream under pressure</a>.</p>
<h2>The chocolate belt speeds up</h2>
<p>If you take a look at the <a href="https://cloud.google.com/resources/content/dora-roi-of-ai-assisted-software-development">2026 DORA report</a>, 90% of developers now use AI tools at work. Developers are spending 2+ hours a day with these tools, completing 21% more tasks and merging 98% more pull requests.</p>
<p>With great power comes great responsibility. The average number of bugs per developer is up 54%. <a href="https://www.faros.ai/blog/key-takeaways-from-the-dora-report-2025">Faros AI's analysis</a> of 10,000+ developers found incidents per pull request are up 242.7%. We've essentially doubled our merge rates while breaking things three times as often. We see the impact of AI-generated code in our own data, too. Our <a href="https://octopus.com/publications/ai-pulse-report">2026 AI Pulse report</a> found that AI reduces task hours across every part of the delivery pipeline except for code review. 72% of developers use AI to write code, but only 56% bother using it for their reviews. The chocolate belt is accelerating, and Lucy and Ethel are starting to look nervous.</p>
<p>To be fair, Daniel Stenberg, the creator of curl, <a href="https://thenewstack.io/curls-daniel-stenberg-ai-is-ddosing-open-source-and-fixing-its-bugs/">recently noted</a> that AI-generated contributions have gone from slop to genuinely good. Problem solved, right? Not quite. PRs are arriving faster than his team can review them. We have better chocolates, but the same belt speed problem. Our review queue is starting to resemble a backlog.</p>
<p>So what do we do about it? The prevailing sentiment right now is to chuck AI at the review problem, too. Make Ethel check Lucy's work. But think about that. They're standing at the same belt and they've trained on the same data. They have the same blind spots. "AI reviewed it so we're good" is the new "the dog ate my homework." Except now the dog wrote the homework, ate it, barfed it up, and gave it an A+.</p>
<p>That's the real takeaway from the DORA data. AI is an amplifier. It can amplify our intelligence or our stupidity. We need to be careful. Right now, a lot of us have the chocolate belt of PRs cranked up to full speed.</p>
<h2>Enter the wrapping machine</h2>
<p>The chocolate belt does exactly what it's supposed to do. The wrapping process (code review) is what failed. And the fix has been staring us in the face since the Continuous Delivery (CD) movement began. Our deployment pipeline is the assurance mechanism, not the human with the approve button. If quality and security requirements are missing from the pipeline as automated checks, code review will never ensure they are met. We are simply hoping that a human – somewhere in the chain – might catch the problem.</p>
<p>Yes, we still need people who can look at a system and say, "This is the wrong approach." That's not going away. But we're expecting that same person also to be the last line of defense against every bug and every security gap in every deployment. That was never going to work. We just didn't have a reason to admit it until now.</p>
<h2>What's actually in the chocolate</h2>
<p>Let's stop pretending code review is something it isn't.</p>
<p>Code review is great for knowledge sharing and catching design-level issues. But it's horrible at catching every bug in a 40,000-line diff. Bugs matter when code is shipping to production.</p>
<p>So the question we should be asking ourselves isn't "how do we make code review scale?" It's "how do we build a pipeline that can verify what it's shipping, regardless of who or what wrote the code?"</p>
<p>Policy-as-code is one way to get there. We write rules that define our deployment standards, and the pipeline checks every deployment against them. The developer sees what went wrong and how to fix it. There's no waiting around for someone to review a diff.</p>
<h2>Learning to wrap chocolate</h2>
<p>It would be foolish of me not to mention the fact that there's something a chocolate wrapping machine can't teach you. And that's the process of wrapping chocolate. In our world, that's the act of conducting a code review. It's how junior engineers develop judgment.</p>
<p>Mentorship comes from reading other people's code, getting feedback on your own, and absorbing the unwritten reasons behind certain decisions. That pipeline is already breaking. 73% of organizations have reduced junior developer hiring in the past two years. Junior devs dropped from 32.8% to 24.8% of Stack Overflow respondents between 2024 and 2025. If we let that continue without figuring out another way for juniors to learn, we're in trouble. We end up with a generation of engineers who can prompt effectively but can't reason about a system's design.</p>
<p>I'm not saying we need to remove code review. But we need to stop kidding ourselves that it's the all-seeing, all-knowing quality gate we've built it up to be.</p>
<h2>Wrapping up</h2>
<p>To reiterate, Jez was right. Code review has worked well enough when humans are involved in the volume of code being reviewed. It was good enough when a team merged a handful of PRs a day. However, it's not good enough when AI is generating them.</p>
<p>The answer isn't a better performance. It's a better pipeline. One that can prove our software works before it hits production. The CD community has been saying this for years. Most of us just didn't have a reason urgent enough to listen. But with the advent of AI and code generation, we're now compelled to.</p>
<p>If our quality gates live in our pipeline, it doesn't matter whether the code was written by a human, an AI, or a very determined cat walking across a keyboard.</p>
<p>If the quality of our code reviews is determined by a human's abilities, we're in trouble, because AI sped up the belt, and the chocolates aren't going to wrap themselves.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Octopus Easy Mode - Ephemeral Environments</title>
      <link href="https://octopus.com/blog/octo-easy-mode-15-ephemeral-environments" />
      <id>https://octopus.com/blog/octo-easy-mode-15-ephemeral-environments</id>
      <published>2026-07-03</published>
      <updated>2026-07-03</updated>
      <summary>Learn how to create ephemeral environments in Octopus</summary>
      <author>
        <name>Matthew Casperson, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>In the <a href="/blog/octo-easy-mode-14-k8s">previous post</a>, you created a functional Kubernetes deployment project. In this post, you'll create <a href="https://octopus.com/docs/projects/ephemeral-environments">Ephemeral Environments</a> to simulate the deployment of feature branches.</p>
<p><a href="/blog/easymode">Return to the series index.</a></p>
<h2>Prerequisites</h2>
<ul>
<li>An <a href="https://octopus.com/start">Octopus Cloud</a> account. If you don't have one, you can sign up for a free trial.</li>
<li>The Octopus AI Assistant Chrome extension. You can install it from the <a href="https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha">Chrome Web Store</a>.</li>
</ul>
<p>:::div{.hint}
The Octopus AI Assistant will work with an on-premises Octopus instance, but it requires more configuration. The
cloud-hosted version of Octopus doesn't need extra configuration. This means the cloud-hosted version is the easiest way to get started.
:::</p>
<h2>Creating the project</h2>
<p>Ephemeral environments support the creation and destruction of short-lived environments and their associated resources. These environments are often used to deploy feature-branch builds, allowing developers to interact with their work before it is merged into a mainline branch.</p>
<p>Paste the following prompt into the Octopus AI Assistant and run it:</p>
<pre><code class="language-markdown">Create a Kubernetes project called "K8s Web App with Ephemeral Environments", and then:
* Use client side apply in the Kubernetes step (the mock Kubernetes cluster only supports client side apply).
* Disable verification checks in the Kubernetes steps (the mock Kubernetes cluster doesn't support verification checks).
* Enable retries on the K8s deployment step.
* Add support for ephemeral environments, with the Parent Environment and Ephemeral Environment channel both called "Features"

---

Create a token account called "Mock Token".

---

Create a feed called "Docker Hub" pointing to "https://index.docker.io" using anonymous authentication.

---

Create a Kubernetes target with the tag "Kubernetes", the URL https://mockk8s.octopusdemos.com, attach it to the "Development", "Test", "Production" environments and the "Features" parent environment, using the health check container image "octopusdeploy/worker-tools:6.5.0-ubuntu.22.04" from the "Docker Hub" feed, using the token account, and the "Hosted Ubuntu" worker pool.
</code></pre>
<p>:::div{.hint}
The document separator (<code>---</code>) is used to split the prompt into multiple sections. Each section is applied sequentially, which allows you to create different types of resources in a single prompt.
:::</p>
<p>As we did in the last post, the AI Assistant creates a functional Kubernetes project pointing to a mock Kubernetes server.</p>
<p>We then added support for ephemeral environments, which requires:</p>
<ul>
<li>A parent environment called <code>Features</code></li>
<li>A channel that deploys to ephemeral environments, also called <code>Features</code></li>
</ul>
<p>:img{ src="/blog/img/octo-easy-mode-15-ephemeral-environments/parent-environment.png" alt="Parent Environment" loading="lazy" }
:img{ src="/blog/img/octo-easy-mode-15-ephemeral-environments/channel.png" alt="Channel" loading="lazy" }</p>
<p>Create a new deployment of the project, select the <code>Features</code> channel, and define the <code>FeatureBranch</code> custom field to the name of a feature branch like <code>features/font-change</code>:</p>
<p>:img{ src="/blog/img/octo-easy-mode-15-ephemeral-environments/new-release.png" alt="New Release" loading="lazy" }</p>
<p>The value assigned to the custom field is used as the name for the new environment. Any invalid characters, like the backslash, are automatically sanitized to provide a valid environment name.</p>
<p>The deployment is visible in the <code>Ephemeral Environments</code> section:</p>
<p>:img{ src="/blog/img/octo-easy-mode-15-ephemeral-environments/ephemeral-environments.png" alt="Ephemeral Environments" loading="lazy" }</p>
<p>Because the Kubernetes step is configured to deploy resources to the namespace <code>#{Octopus.Environment.Name | ToLower}</code>, the ephemeral deployment creates resources in a namespace based on the ephemeral environment name, keeping it separate from the traditional deployments to the <code>Development</code>, <code>Test</code>, and <code>Production</code> environments.</p>
<p>You'll also notice that the Kubernetes target was selected for the deployment because it was attached to the parent <code>Features</code> environment. This demonstrates how durable Octopus resources are linked to ephemeral environment deployments.</p>
<p>Runbooks can be used for those scenarios where you need to provision and deprovision the environment.</p>
<p>Run the following prompt to add two runbooks to the project:</p>
<pre><code class="language-markdown">Create a runbook called "Provision Environment" in the project "K8s Web App with Ephemeral Environments".
Allow the runbook to be run from the "Features" environment.
Add a "Run a kubectl script" step run against the target tag "Kubernetes" and echo the text "Provisioning the environment" from a bash script.
Run the step from the "Hosted Ubuntu" worker pool.

---

Create a runbook called "Deprovision Environment" in the project "K8s Web App with Ephemeral Environments".
Allow the runbook to be run from the "Features" environment.
Add a "Run a kubectl script" step run against the target tag "Kubernetes" and echo the text "Deprovisioning the environment" from a bash script.
Run the step from the "Hosted Ubuntu" worker pool.
</code></pre>
<p>In the <code>Ephemeral Environments</code> project section, open the <code>Settings</code> tab, and select the new runbooks from the <code>Provisioning runbook</code> and <code>Deprovisioning runbook</code> fields:</p>
<p>:img{ src="/blog/img/octo-easy-mode-15-ephemeral-environments/runbooks.png" alt="Ephemeral Environments Runbooks" loading="lazy" }</p>
<p>Both runbooks must have a published snapshot. Open each runbook, click the <code>Publish</code> button, and click the <code>Publish</code> button again:</p>
<p>:img{ src="/blog/img/octo-easy-mode-15-ephemeral-environments/publish-runbooks.png" alt="Publish Runbooks" loading="lazy" }</p>
<p>Now, when you deploy to an ephemeral environment, the <code>Provision Environment</code> runbook is executed. After one week, the <code>Deprovision Environment</code> runbook is automatically executed, or you can manually deprovision an environment in the <code>Ephemeral Environments</code> section under the <code>Overview</code> tab:</p>
<p>:img{ src="/blog/img/octo-easy-mode-15-ephemeral-environments/deprovision-environment.png" alt="Deprovision Environment" loading="lazy" }</p>
<h2>What just happened?</h2>
<p>You created a sample Kubernetes project with:</p>
<ul>
<li>A channel called <code>Features</code> to deploy to an ephemeral environment based on a custom field value</li>
<li>A Parent Environment called <code>Features</code></li>
<li>A Kubernetes target linked to the <code>Features</code> parent environment</li>
<li>Two runbooks: one to provision the ephemeral environment, and another to deprovision it</li>
</ul>
<h2>What's next?</h2>
<p>The <a href="/blog/octo-easy-mode-16-argocd-manifest-update">next step</a> is an example of deploying an Argo CD Application with a GitOps based workflow.</p>]]></content>
    </entry>
    <entry>
      <title>Continuous Delivery Office Hours Ep.6: Change approvals</title>
      <link href="https://octopus.com/blog/continuous-delivery-office-hours-e6" />
      <id>https://octopus.com/blog/continuous-delivery-office-hours-e6</id>
      <published>2026-07-02</published>
      <updated>2026-07-02</updated>
      <summary>Learn more about the purpose and common pitfalls of software change approvals.</summary>
      <author>
        <name>Steve Fenton, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>When you add approval stages to increase stability, the last thing you expect is instability. That's the opposite of what you wanted. Yet that's what happens when organizations respond to incidents by increasing the weight of change approval processes.</p>
<p>That means there's more art to change approvals than most people realize, and it's a threat to an organization's ability to deploy and operate software if they don't have <em>change finesse</em>.</p>
<h2>Watch the episode</h2>
<p>You can watch the episode below, or read on to find some of the key discussion points.</p>
<p><a href="https://www.youtube.com/watch?v=ODzaXj889wA">Watch Continuous Delivery Office Hours Ep.6</a></p>
<h2>Organizational trauma</h2>
<p>Change approvals don't arrive without reason. If you have a heavyweight change approval process and frequent or extended change freezes, the chances are that they were introduced after a major incident. If you break financial software around tax year-end, banning deployments for a month before and a month after is, in theory, a reasonable resolution.</p>
<p>Almost every industry has a cadence it wants to protect from instability. Retail has seasonal sales events, the music industry has superstar ticket launches, and finance has a peak as the end of the tax year approaches. The goal is to make sure you can operate your business during these times.</p>
<p>With that goal in mind, we have to bust the myth of change approvals as the mechanism to achieve it. Attempting to protect these key moments with change freezes or cumbersome approval processes has one result: increased instability.</p>
<h2>Heavyweight change approvals</h2>
<p>Heavyweight change approvals make things less stable by delaying work and causing batches of unreleased changes to accumulate. Meanwhile, developers are starting new work and are losing the immediate familiarity with the oldest changes as they press ahead. One of the ways approvals gain weight is through approval chains, which we looked at in depth in the <a href="https://octopus.com/publications/compliance-through-continuous-delivery">Compliance through Continuous Delivery report</a>.</p>
<p>Large batches also come with admin that can introduce further problems. Testing becomes more difficult, the likelihood of merge issues increases, and pinpointing the source of a problem is far harder.</p>
<p>This is why the DORA research placed <a href="https://dora.dev/capabilities/streamlining-change-approval/">streamlined change approvals</a> in their core model for software delivery. Centralized change approval boards don't work, and process is never the solution to your stability problems.</p>
<h2>Streamlining</h2>
<p>There are some easy ways to streamline change approvals. Most of these don't look like traditional change management, which is good because we know that doesn't work.</p>
<p>The first way to trim the process is to automate your verification stages. At every level of review, tasks can be automated, whether it's automatically linting and formatting code (instead of debating it), running automated builds and tests, or validating your SBOM is free from insecure dependencies.</p>
<p>Where you need a human review, use a peer-review process for individual changes, enforced on commit, with humans brought in only after the automated validation has passed. If you have advanced change approval needs, categorizing changes by risk lets you apply your people to the changes that most need their perspective.</p>
<p>You won't achieve all of this in one day. It's part of your continuous improvement process. You may improve your chances of stripping the bureaucracy if you follow the <a href="https://octopus.com/devops/culture/capability-culture-cycle/">capability culture cycle</a> pattern.</p>
<h2>Small batches, again</h2>
<p>If you follow industry experts or the research, you'll notice that small batches keep cropping up as the answer to many kinds of dysfunction. That's not a coincidence. Large batches cause far-reaching problems that build superlinearly as more changes collect unreleased.</p>
<p>Anything that causes batch size to increase, including change approvals, must be subject to fierce improvement.</p>
<p>Happy deployments!</p>
<p>:::div{.hint}</p>
<p>Continuous Delivery Office Hours is a series of conversations about software delivery, with Tony Kelly, Bob Walker, and Steve Fenton.</p>
<p>You can find more episodes on <a href="https://www.youtube.com/playlist?list=PLAGskdGvlaw3CrxkUOAMmiy928lr5D4oh">YouTube</a>, <a href="https://podcasts.apple.com/us/podcast/continuous-delivery-office-hours/id1872101651">Apple Podcasts</a>, and <a href="https://pca.st/hwjaox59">Pocket Casts</a>.</p>
<p>:::</p>]]></content>
    </entry>
    <entry>
      <title>Sandboxing AI Agents</title>
      <link href="https://octopus.com/blog/ai-agent-sandboxes" />
      <id>https://octopus.com/blog/ai-agent-sandboxes</id>
      <published>2026-07-01</published>
      <updated>2026-07-01</updated>
      <summary>Learn how to approach security and sandboxing shared AI agents</summary>
      <author>
        <name>Matthew Casperson, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>It has become clear after many discussions with large enterprises that the interest and excitement around AI agents will only grow. Many enterprises now have C-level executives responsible for implementing AI, which brings associated budgets and measurable outcomes. Meanwhile, individual contributors are well along in their AI journey, using AI-assisted coding agents and general-purpose AI assistants.</p>
<p>Securing these agents is a top concern for enterprises. One common solution to improve the security of AI agents is to run them in a sandboxed environment. In this post, I'll take a look at what it means to "sandbox" an AI agent in a production environment.</p>
<h2>In brief</h2>
<ul>
<li>Local AI agents are general-purpose assistants that can perform almost any action on behalf of a user.</li>
<li>Local AI agents benefit from sandboxes as a countermeasure to their broad access to CLI tools, local files, and networks.</li>
<li>Shared AI agents are designed for specific tasks.</li>
<li>Shared AI agents should be decomposed into the agent harness and the tools called by the agent.</li>
<li>The tools called by shared AI agents are typical web services.</li>
<li>The term "sandbox" has little meaning for shared AI agents, as the tools can be secured with existing security policies and practices.</li>
</ul>
<h2>Distinguishing between local and shared agents</h2>
<p>Before discussing what it means to sandbox an agent, it is important to distinguish between local and shared agents.</p>
<p>Local agents are the result of bespoke configuration in an individual's own workspace. It is the coding agent with a mishmash of MCP servers and personal credentials that a developer has set up to help them with their work. Or the OpenClaw style agent that runs in the background automating tasks like monitoring emails, browsing the web, or organizing files.</p>
<p>To use the pets/cattle analogy (where pets have names and are lovingly cared for while cattle are interchangeable), local agents are pets. Local agents must support a wide range of tasks, including code generation, running scripts, manipulating files, and answering questions. They were never intended to be distributed, and little thought is put into how they might be recreated. Each developer is responsible for their own local agent. In fact, much of the functionality provided by a local agent likely relies on MCP servers exposed by an IDE, which are not available outside a local development environment.</p>
<p>Shared (or managed) agents are designed to perform specialized tasks. They must be secure, testable, deployable, and supported. Shared agents will often be hosted as web applications, perhaps using protocols like the Model Context Protocol (MCP).</p>
<p>Local agents have unique security concerns. It is mesmerizing, and slightly horrifying, watching a local agent query the contents of your <code>/etc/environment</code> file to get the credentials required to execute a <code>curl</code> command as it doggedly attempts to upload a file to a remote server. Local agents are like sharing your keyboard with the most brilliant and amoral entity in the known universe.</p>
<p>Because local agents are general-purpose AI tools, they tend to have broad access to the CLI, local files, and networks. So it makes sense to run local agents in an isolated environment to distinguish between the trust granted to a user and the trust granted to the local AI agent.</p>
<p>Shared agents have a far narrower scope than local agents. Shared agents are designed to solve specific tasks and interact with the world through a small window. The limited scope of shared agents has implications for their security.</p>
<p>The focus of this post is on shared agents. This is not to diminish the security implications of local AI agents, but rather to note that shared agents, iteratively developed and deployed to a production environment, align very closely with the core functionality provided by Octopus.</p>
<p>But before we can understand what it means to sandbox a shared agent, we first need to understand the architecture of shared agents.</p>
<h2>Shared agent architecture</h2>
<p>At the heart of every AI agent is an LLM making decisions about how best to achieve its task.</p>
<p>For all their wonder and complexity, it is best to think of LLMs used by shared agents as string functions: the prompt string goes in, the response string comes out. (I'm going to ignore the social engineering security aspect of LLMs here, as the generated output of an LLM used by a shared agent is not typically consumed by a person.)</p>
<p>That is it. LLMs cannot, on their own, interact with the world. They cannot browse a web page, read a file, or save a record in a database.</p>
<p>Because LLMs can’t interact with the world, there is very little to contain in a sandbox.</p>
<p>However, this inability to interact with the world severely restricts the problems that LLMs can solve. A chatbot is about as complex a solution as you can build with an isolated LLM. To build useful AI agents, LLMs must be able to act.</p>
<p>This is where the concept of tool calling comes in. Tools are just a fancy way of describing code exposed to an LLM that can interact with the world. MCP is the most common interface through which LLMs learn about and execute tools.</p>
<p>When a tool like <code>switch_lightbulb_on</code> is exposed by an MCP server to an LLM, a prompt like <code>Switch on the lights</code> will cause a physical light bulb to turn on.</p>
<p>Treating the LLM and the tools it calls as separate concerns is crucial to understanding how sandboxes apply to shared AI agents.</p>
<h2>Sandboxing the tools</h2>
<p>There are many industry examples demonstrating the pattern where the LLM is run as a regular service while the tools called by the LLM are isolated within a sandbox environment.</p>
<p>:::div{.hint}
Some of these quotes have been edited for clarity.
:::</p>
<p><a href="https://www.anthropic.com/engineering/managed-agents">Claude describes the LLM as the brain and the tools as the hands of an AI agent</a>:</p>
<blockquote>
<p>The solution we arrived at was to decouple what we thought of as the “brain” (Claude and its harness) from both the “hands” (sandboxes and tools that perform actions) and the “session” (the log of session events).</p>
</blockquote>
<p>Notably, in this description, the hands include sandboxes.</p>
<p><a href="https://www.redhat.com/en/blog/red-hat-ai-and-openshell-driving-security-enhanced-agent-execution-for-enterprise-ai">Red Hat describes the separation of the brain and the hands, with the hands running in a sandbox, as "the right choice for multi-tenant agent platforms and production workloads"</a>:</p>
<blockquote>
<p>The agent's "brain" (reasoning and orchestration) is decoupled from its "hands" (tool execution and code). The platform orchestrates the agent loop and delegates execution to disposable, stateless sandboxes that you control. Credentials are physically separated from the execution environment, injected at the network boundary rather than stored where agent-generated code can reach them. Both the Responses API and Anthropic's Managed Agents follow this pattern, whether the sandbox runs in the provider's cloud or on your own infrastructure through self-hosted environments. This is the right choice for multi-tenant agent platforms and production workloads.</p>
</blockquote>
<p><a href="https://www.youtube.com/watch?v=fegwPmaAPQk">How 11x Rebuilt Their Alice Agent: From ReAct to Multi-Agent with LangGraph</a> notes that agents work best when tools do the heavy lifting:</p>
<blockquote>
<p>Tools are preferable over skills. Don't try to make your agent too smart. Just give it the right tools and tell it how to use them.</p>
</blockquote>
<p>In the video <a href="https://www.youtube.com/watch?v=W9y_a2ZOatI">Securing MCP in an Agentic World with Arjun Sambamoorthy from Cisco</a>, Arjun describes the importance of run-time MCP security with sandboxes isolating MCP servers:</p>
<blockquote>
<p>We should also sandbox and isolate MCP servers to make sure there's no crosspollination that's actually happening.</p>
</blockquote>
<p><a href="https://www.youtube.com/watch?v=CvZDJxd4LKM">Agentic AI Safety &#x26; Security by Dawn Song</a> describes the importance of decomposing systems to enforce the principle of least privilege:</p>
<blockquote>
<p>The idea is that instead of building one monolithic agent with different components in one system, one can actually separate the overall agent system into separate components where each component can run its own, for example, container or context such that each separate component can have its own set of privileges depending on its needed capabilities and so on and hence enable and help enforce principle of least privilege.</p>
</blockquote>
<p><a href="https://developers.openai.com/api/docs/guides/agents/sandboxes">OpenAI describes when to use a sandbox</a>, and notes that "the sandbox stays focused on provider-specific execution":</p>
<blockquote>
<p>Use sandboxes when the agent needs to manipulate files, run commands, mount a data room, produce artifacts, expose a service, or continue stateful work later.</p>
<p>The key split is the boundary between the harness and compute. The harness is the control plane around the model: it owns the agent loop, model calls, tool routing, handoffs, approvals, tracing, recovery, and run state. Compute is the sandbox execution plane where model-directed work reads and writes files, runs commands, installs dependencies, uses mounted storage, exposes ports, and snapshots state.</p>
<p>Keeping those boundaries separate lets your application keep sensitive control plane work in trusted infrastructure while the sandbox stays focused on provider-specific execution.</p>
</blockquote>
<p><a href="https://techcommunity.microsoft.com/blog/appsonazureblog/introducing-azure-container-apps-sandboxes-secure-infrastructure-for-agentic-wor/4524131">Azure Container Apps Sandboxes</a> provide a managed service where:</p>
<blockquote>
<p>Agents can run anything safely - an agent spawns a sandbox, executes work inside it, and returns the output with no agent host privileges required.</p>
</blockquote>
<p><a href="https://aws.amazon.com/blogs/machine-learning/introducing-the-amazon-bedrock-agentcore-code-interpreter/">AWS provides the Amazon Bedrock AgentCore Code Interpreter</a>, which similarly provides a sandbox where untrusted code is run:</p>
<blockquote>
<p>With the AgentCore Core Interpreter, AI agents can write and execute code securely in sandbox environments, enhancing their accuracy and expanding their ability to solve complex end-to-end tasks.</p>
</blockquote>
<p>The provided diagram clearly shows the Agent and LLM sitting outside the sandbox, and the code being executed inside it:</p>
<p>:img{ src="/blog/img/ai-agent-sandboxes/agentcore-code-interpreter.png" alt="AgentCore Code Interpreter Diagram" loading="lazy" }</p>
<p>What is clear from these examples is that the LLM is hosted separately from the tools it calls, and it is the tools that are sandboxed, as this is where the real work is done.</p>
<h2>What even is a sandbox?</h2>
<p>When taking the approach of sandboxing tools, the next decision is which guardrails the sandbox must provide.</p>
<p>At the extreme end, a sandbox provides an environment in which untrusted scripts can run. An example of this is <a href="https://www.infoq.com/news/2026/01/intel-deepmath-llm-architecture/">Intel DeepMath</a>, which is a lightweight agent that specializes in solving mathematical problems by running small, sandboxed Python scripts that support and enhance its problem-solving process:</p>
<blockquote>
<p>Instead of verbose text, the model emits tiny Python snippets for intermediate steps, runs them in a secure sandbox, and folds the results back into its reasoning, reducing errors and output length.</p>
</blockquote>
<p>Your local coding assistant AI agent may even have produced Python scripts to modify files in bulk or search for text.</p>
<p>Because you can do almost anything with a Python script, you need a robust sandbox to prevent any malicious or undesirable actions from being executed.</p>
<p>Running untrusted code is an extreme example, though. Most tools will be far more routine, performing deterministic actions like returning data, sending messages, triggering a workflow, approving a request, etc. Indeed, most of the tools called by a shared AI agent are just wrappers around existing APIs.</p>
<p>The sandbox around these tools must address the same cross-cutting concerns as any web service container, like authentication, authorization, rate limiting, PII redaction, observability, CPU and memory limits, firewalls, etc.</p>
<p>At this point, it may not even make sense to talk about sandboxes at all. Any modern Platform as a Service (PaaS) or orchestration platform has almost certainly addressed these common security concerns, usually without using the term "sandbox."</p>
<h2>Do sandboxes make sense?</h2>
<p>General-purpose local AI agents running in an individual's workspace absolutely benefit from a sandbox. The fact that a local AI agent can and will do anything you ask (and sometimes things you don't) means a specialized sandbox is a valid countermeasure.</p>
<p>In <a href="https://www.youtube.com/watch?v=J7ol1VDkg7w">OpenClaw + Windows</a>, Microsoft demonstrates how OpenClaw is prevented from making unwanted changes to the system by running it in a sandbox:</p>
<blockquote>
<p>And you'll notice down here in the corner we've got lots of permissions options along with our sandbox configuration. Now, this sandbox is really interesting because this is using MXC, the Microsoft Execution Containers.</p>
<p>You've got full support about what files and folders you want OpenClaw to have access to, and really granular security features like clipboard access or talking to the internet itself.</p>
<p>OpenClaw already has a rich safety layer, and that layer is only augmented more by appropriate containment that can be managed by me or policies applied by IT.</p>
</blockquote>
<p>The concept of a sandbox is also applicable for the execution of generated scripts, which administrators must assume can perform any action.</p>
<p>However, the concept of a sandbox is less meaningful when used to isolate specific, deterministic tools required by shared agents. The security layer built into any modern PaaS offering already supports the cross-cutting security concerns required to host web-based services, authentication and authorization policies are available on APIs exposed by tools, and individual tools can be turned on and off as needed in an MCP server.</p>
<p>You could make a good argument that this collection of controls effectively serves as a sandbox. For example, <a href="https://github.com/kubernetes-sigs/agent-sandbox/">agent-sandbox</a> combines existing Kubernetes features to provide an AI agent sandbox.</p>
<p>But using the term "sandbox" feels more like a distraction from the implementation of existing, standard security controls applied to any web service because it implies that there is some unique security layer that is specifically required to support AI agents.</p>
<h2>Conclusion</h2>
<p>The term sandbox is thrown around a lot these days. You don't have to look hard to find examples of AI agents going rogue and deleting files or trashing databases, and it is natural to assume that some kind of sandbox is required to rein in freewheeling AI agents.</p>
<p>But it is important to distinguish between general-purpose local AI agents that are incentivized to support any kind of action and specialized shared AI agents that are designed for a very specific purpose. Further decomposing shared AI agents into the agent harness and the tools highlights that it is the tools that need to be constrained. And centrally managed tools exposed as web services (with an MCP server being a specialized web server) already have a wealth of existing, comprehensive security controls available to secure them.</p>
<p>Enterprises should focus on constraining the tools used by shared AI agents, rather than being distracted by hype around sandboxes. Your existing best practices can be applied to centrally managed tools; there is no need to shoehorn in an additional security layer under the guise of a sandbox.</p>
<p>Happy Deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Blue/green deployments on Kubernetes with Argo Rollouts</title>
      <link href="https://octopus.com/blog/blue-green-deployments-kubernetes-argo" />
      <id>https://octopus.com/blog/blue-green-deployments-kubernetes-argo</id>
      <published>2026-06-30</published>
      <updated>2026-06-30</updated>
      <summary>Learn how to use Argo Rollouts to perform blue/green deployments to Kubernetes.</summary>
      <author>
        <name>Jubril Oyetunji, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>One of the harder questions to answer at scale is how to ship without a few seconds where your users are getting timeouts or your fleet is split across two image versions.</p>
<p>Kubernetes's default rolling update strategy gradually deploys new pods and retires old ones, but during the swap, your service runs both versions side by side, and a regression in the new image affects every request that lands on a new pod.</p>
<p>Progressive delivery patterns like blue/green have long existed: you stand the new version up alongside the old, prove it's healthy on a separate preview endpoint, then flip user traffic across. Blast radius shrinks to nothing in the bad case, rollback is a single command, and your release stops being a held-breath moment.</p>
<p>In this post, you'll set that up with <a href="https://argoproj.github.io/argo-rollouts/">Argo Rollouts</a>, the controller behind progressive delivery in the Argo ecosystem (which graduated from the CNCF in 2022).</p>
<h2>What is Argo Rollouts (and why you need it)</h2>
<p>Argo Rollouts is a Kubernetes controller and a set of CRDs that bolt blue/green, canary, and other progressive delivery strategies onto your cluster. The primary CRD is Rollout, a drop-in replacement for the standard Deployment.</p>
<p>You convert an existing Deployment by changing the apiVersion to argoproj.io/v1alpha1 and the kind to Rollout, then adding a strategy.blueGreen or strategy.canary block that describes how a new revision should roll out.</p>
<p>A major reason to reach for it is that there's no built-in way to do this kind of traffic control in Kubernetes (you can't decide where requests go independently of which pods are Ready). There's no easy rollback to the previous version once the update has started.</p>
<p>Argo Rollouts fills in everything around that. It plugs into ingress controllers (Traefik, ALB) and service meshes (Istio, Linkerd, SMI) for real traffic shaping. It can query metrics providers (Prometheus, Datadog, CloudWatch, New Relic) to gate promotions on hard numbers, and it tracks every revision as its own ReplicaSet, so flipping back is instant.</p>
<h2>Prerequisites</h2>
<p>This tutorial assumes some familiarity with Kubernetes. You'll also need:</p>
<ul>
<li>A working Kubernetes cluster (EKS, GKE, AKS, or local like Minikube/Kind).</li>
<li><a href="https://kubernetes.io/docs/tasks/tools/">kubectl</a> installed and pointed at the cluster (kubectl get nodes should return at least one Ready node)</li>
<li><a href="https://helm.sh/docs/intro/install/">helm</a> v3 installed</li>
<li><a href="https://curl.se/">curl</a> for hitting the demo app</li>
</ul>
<h3>Step 1: Installing the Argo Rollouts controller</h3>
<p>Argo Rollouts ships as a controller that runs in its own namespace, along with a kubectl plugin you'll use on your laptop to inspect and steer rollouts.</p>
<p>To install the controller, follow these steps:</p>
<ol>
<li>
<p>Add the Argo Helm repo and install the controller:</p>
<pre><code class="language-bash">helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argo-rollouts argo/argo-rollouts \
  --namespace argo-rollouts \
  --create-namespace \
  --wait
</code></pre>
</li>
<li>
<p>Confirm the controller pods are up:</p>
<pre><code class="language-bash">kubectl -n argo-rollouts get pods
</code></pre>
<p>You should see something like:</p>
<pre><code class="language-text">NAME                             READY   STATUS    RESTARTS   AGE  
argo-rollouts-dcd465dfc-8m2ql    1/1     Running   0          79s  
argo-rollouts-dcd465dfc-q92k4    1/1     Running   0          79s
</code></pre>
</li>
</ol>
<h3>Step 2: Installing the kubectl argo rollouts plugin</h3>
<p>The controller is running, but the most ergonomic way to drive a rollout — inspecting state, setting images, promoting, rolling back — is the kubectl argo rollouts plugin. It's a separate binary that drops onto your PATH, and kubectl picks it up automatically.</p>
<p>To install the plugin, follow these steps:</p>
<p>On macOS with Homebrew:</p>
<pre><code class="language-bash">brew install argoproj/tap/kubectl-argo-rollouts
</code></pre>
<p>On Linux:</p>
<pre><code class="language-bash">curl -sLO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64
sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
</code></pre>
<p>Verify it's wired up:</p>
<pre><code class="language-bash">kubectl argo rollouts version
</code></pre>
<p>You should see something like <code>kubectl-argo-rollouts: v1.8.3+...</code>. From here on, we'll use kubectl argo rollouts ... subcommands to drive the rollout.</p>
<h3>Step 3: Defining the Rollout</h3>
<p>Argo Rollouts' core idea is that you swap your Deployment for a Rollout resource. The pod template inside it is identical to a Deployment's.</p>
<p>What changes is the spec.strategy block, which describes how a new revision should roll out.</p>
<p>For blue/green, you need three things:</p>
<ol>
<li>A Rollout with <code>spec.strategy.blueGreen</code> configured</li>
<li>An active Service, which always points to whichever ReplicaSet is currently serving production traffic</li>
<li>A preview Service, which points at the new ReplicaSet before it gets promoted, so that you can test it in isolation</li>
</ol>
<p>Argo Rollouts injects the rollouts-pod-template-hash label into each Service's selector at runtime, which is how it switches traffic without you ever editing the Services.</p>
<p>Write the manifest:</p>
<pre><code class="language-bash">
cat > rollout.yaml &#x3C;&#x3C;'EOF'
apiVersion: v1
kind: Service
metadata:
  name: rollouts-demo-active
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app: rollouts-demo
---
apiVersion: v1
kind: Service
metadata:
  name: rollouts-demo-preview
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app: rollouts-demo
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollouts-demo
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollouts-demo
  template:
    metadata:
      labels:
        app: rollouts-demo
    spec:
      containers:
        - name: rollouts-demo
          image: argoproj/rollouts-demo:blue
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          resources:
            requests:
              cpu: 25m
              memory: 32Mi
  strategy:
    blueGreen:
      activeService: rollouts-demo-active
      previewService: rollouts-demo-preview
      autoPromotionEnabled: false
      scaleDownDelaySeconds: 30
EOF
</code></pre>
<p>A few fields are worth calling out in the <code>blueGreen</code> block:</p>
<ul>
<li><code>activeService</code> and <code>previewService</code> are the names of the two ClusterIP Services above. Argo Rollouts owns their selectors from here on; you don't edit them by hand.</li>
<li><code>autoPromotionEnabled</code>: false is what makes this a manual promotion. The new ReplicaSet comes up, you inspect it on the preview Service, and only then do you flip the active Service over. Set it to true (the default) and Argo will auto-promote the moment the new pods are Ready.</li>
<li><code>scaleDownDelaySeconds</code>: 30 keeps the old (blue) ReplicaSet around for 30 seconds after promotion, so if something goes wrong in those first few seconds, you can flip back instantly without rescheduling pods.</li>
</ul>
<p>Apply it:</p>
<pre><code class="language-bash">kubectl apply -f rollout.yaml
</code></pre>
<p>We're using <a href="https://github.com/argoproj/rollouts-demo">argoproj/rollouts-demo,</a>, a tiny app published by the Argo team that serves an HTML dashboard and a /color endpoint that reports which tagged image is running (blue, green, yellow, etc.). It's perfect for seeing the cutover happen in real time.</p>
<h3>Step 4: Checking the initial state</h3>
<p>Take a look at the rollout:</p>
<pre><code class="language-bash">kubectl argo rollouts get rollout rollouts-demo
</code></pre>
<p>Output:</p>
<pre><code class="language-text">Name:            rollouts-demo  
Namespace:       default  
Status:          ✔ Healthy  
Strategy:        BlueGreen  
Images:          argoproj/rollouts-demo:blue (stable, active)  
Replicas:  
  Desired:       2  
  Current:       2  
  Updated:       2  
  Ready:         2  
  Available:     2  

NAME                                       KIND        STATUS     AGE  INFO  
⟳ rollouts-demo                            Rollout     ✔ Healthy  34s  
└──\# revision:1  
   └──⧉ rollouts-demo-86c957c6d6           ReplicaSet  ✔ Healthy  34s  stable,active  
      ├──□ rollouts-demo-86c957c6d6-72kjf  Pod         ✔ Running  34s  ready:1/1  
      └──□ rollouts-demo-86c957c6d6-nv3zg  Pod         ✔ Running  34s  ready:1/1
</code></pre>
<p>One revision, two pods, both stable and active. Both Services currently point at the same ReplicaSet hash. You can confirm with:</p>
<pre><code class="language-bash">kubectl get svc rollouts-demo-active rollouts-demo-preview \
  -o jsonpath='{range .items[*]}{.metadata.name}{" -> hash="}{.spec.selector.rollouts-pod-template-hash}{"\n"}{end}'
</code></pre>
<p>Output:</p>
<pre><code class="language-bash">rollouts-demo-active -> hash=86c957c6d6
rollouts-demo-preview -> hash=86c957c6d6
</code></pre>
<h3>Step 5: Triggering a new version</h3>
<p>Now let's deploy a new revision. We'll change the image tag from blue to yellow:</p>
<pre><code class="language-bash">kubectl argo rollouts set image rollouts-demo \
  rollouts-demo=argoproj/rollouts-demo:yellow
</code></pre>
<p>Argo creates a new ReplicaSet (rev 2) for the yellow image and waits, because we set autoPromotionEnabled: false. The active Service still points at blue. The preview Service is re-pointed at yellow:</p>
<p>kubectl argo rollouts get rollout rollouts-demo</p>
<p>Output:</p>
<pre><code class="language-text">Status:          ॥ Paused
Message:         BlueGreenPause
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:blue (stable, active)
                 argoproj/rollouts-demo:yellow (preview)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                       KIND        STATUS     AGE  INFO
⟳ rollouts-demo                            Rollout     ॥ Paused
├──# revision:2
│  └──⧉ rollouts-demo-7cf9dff6bb           ReplicaSet  ✔ Healthy  38s  preview
│     ├──□ rollouts-demo-7cf9dff6bb-cbp2c  Pod         ✔ Running  38s  ready:1/1
│     └──□ rollouts-demo-7cf9dff6bb-fn2gt  Pod         ✔ Running  38s  ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-86c957c6d6           ReplicaSet  ✔ Healthy  5m   stable,active
      ├──□ rollouts-demo-86c957c6d6-72kjf  Pod         ✔ Running  5m   ready:1/1
      └──□ rollouts-demo-86c957c6d6-nv3zg  Pod         ✔ Running  5m   ready:1/1
</code></pre>
<p>This is the heart of blue/green. The cluster is now running both versions, but only blue is serving real traffic.</p>
<h3>Step 6: Proving the split with curl</h3>
<p>Forward both Services to your laptop on different local ports:</p>
<pre><code class="language-bash">kubectl port-forward svc/rollouts-demo-active 8080:80 >/dev/null 2>&#x26;1 &#x26;
kubectl port-forward svc/rollouts-demo-preview 8081:80 >/dev/null 2>&#x26;1 &#x26;
sleep 3
</code></pre>
<p>Hit each one using:</p>
<pre><code class="language-bash">echo "active : $(curl -s http://127.0.0.1:8080/color)"
echo "preview: $(curl -s http://127.0.0.1:8081/color)"
</code></pre>
<p>Output:</p>
<pre><code class="language-text">active : "blue"  
preview: "yellow"
</code></pre>
<p>This is exactly the window where you'd run smoke tests, point a staging frontend at the preview hostname, or have Argo run an AnalysisTemplate against Prometheus.</p>
<p>Nothing about production traffic has changed yet.</p>
<p>When you're done with the port-forwards, run the following command:</p>
<pre><code class="language-bash">kill %1 %2 2>/dev/null
</code></pre>
<h3>Step 7: Promoting</h3>
<p>When you're happy, flip the active Service over with one command:</p>
<pre><code class="language-bash">kubectl argo rollouts promote rollouts-demo
</code></pre>
<p>Output:</p>
<pre><code class="language-bash">rollout 'rollouts-demo' promoted
</code></pre>
<p>Argo updates the active Service's selector to the new ReplicaSet hash.</p>
<p>Subsequent requests should show production traffic is in yellow. The old blue pods stick around for scaleDownDelaySeconds (30 by default) before being torn down, which is what makes the next section possible.</p>
<p>Confirm the cutover by running:</p>
<pre><code class="language-bash">kubectl argo rollouts status rollouts-demo --timeout 60s
</code></pre>
<p>You should see Healthy, and the Service selectors should now agree:</p>
<pre><code class="language-bash">kubectl get svc rollouts-demo-active rollouts-demo-preview \
  -o jsonpath='{range .items[*]}{.metadata.name}{" -> hash="}{.spec.selector.rollouts-pod-template-hash}{"\n"}{end}'
</code></pre>
<p>Output:</p>
<pre><code class="language-bash">rollouts-demo-active -> hash=7cf9dff6bb
rollouts-demo-preview -> hash=7cf9dff6bb
</code></pre>
<h3>Step 8: Rolling back</h3>
<p>If something goes wrong after the promotion (a metric tanks, you spot an error in the logs, a teammate flags a bug), undo it by running:</p>
<pre><code class="language-bash">kubectl argo rollouts undo rollouts-demo
</code></pre>
<p>That brings the previous ReplicaSet back as the new "preview" and pauses, waiting for you to confirm with <code>promote</code> once more, which flips the active Service back to it. Because the old pods were kept warm by scaleDownDelaySeconds, this happens in seconds, not whatever your image pull time is.</p>
<h3>Step 9: Cleaning up</h3>
<p>Once you're done, you can tear down the demo using:</p>
<pre><code class="language-bash">kubectl delete -f rollout.yaml
helm uninstall argo-rollouts -n argo-rollouts
kubectl delete ns argo-rollouts
</code></pre>
<h2>How does this fit with Octopus Deploy and Argo CD</h2>
<p>Everything we've done so far works on its own. You've got a Rollout, two Services, and a one-command promote/undo loop.</p>
<p>Argo Rollouts is happy to do its job at the cluster level. What it doesn't have is an opinion on how dev becomes staging and then becomes production. Who's allowed to push the button, or what the deployment history looked like six weeks ago.</p>
<p>This is the layer <a href="https://octopus.com/">Octopus Deploy</a> is built for. A good mental model is:</p>
<ul>
<li><strong>Argo Rollouts owns the cluster-side mechanics</strong>: Which ReplicaSet is active, which is preview, when to flip, and when to scale down old pods.</li>
<li><strong>Argo CD owns the GitOps sync</strong>: The Rollout (and its Services) live in a Git repo, and the cluster state is reconciled to match.</li>
<li><strong>Octopus owns everything above that</strong>: Environments, approval gates, release lifecycles, audit trails, and the self-service UI that developers actually click on.</li>
</ul>
<p>The promotion path between environments is described once in Octopus and reused across every service, instead of being re-encoded in each team's CI script.</p>
<h2>Ship green, sleep through the night</h2>
<p>If you made it this far, you've got the cluster-side mechanics of progressive delivery sorted: a Rollout flipping between active and preview Services, a manual promotion gate, and instant rollback. That's the hard, hands-on layer done.</p>
<p>What's missing is the orchestration above it, including environments, approvals, audit trails, and the self-service flow your developers actually click. That's where <a href="https://octopus.com/">Octopus Deploy</a> slots in, sitting on top of <a href="https://octopus.com/blog/argo-cd-in-octopus">Argo CD</a> and Argo Rollouts to give you a complete progressive delivery stack across every environment, not just one cluster.</p>
<p><a href="https://octopus.com/docs/argo-cd">Connect your Argo CD instance to Octopus</a> and see how the whole pipeline comes together, or <a href="https://octopus.com/start">try Octopus free</a> and wire it up against your own cluster.</p>
<p>Happy deployments!</p>]]></content>
    </entry>
    <entry>
      <title>Fix unsecured Argo CD communications</title>
      <link href="https://octopus.com/blog/mtls-support-argocd" />
      <id>https://octopus.com/blog/mtls-support-argocd</id>
      <published>2026-06-25</published>
      <updated>2026-06-25</updated>
      <summary>Get a deep dive of the new mTLS support in Argo CD 3.5 and explore how it enhances the security of internal communication.</summary>
      <author>
        <name>Patroklos Papapetrou, Octopus Deploy</name>
      </author>
      <content type="html"><![CDATA[<p>Securing communication between internal components is a long-awaited feature by the ArgoCD community.
Historically, to ensure encrypted communication between the <code>repo-server</code> and its internal clients, such as <code>argocd-server</code> and <code>argocd-application-controller</code>,
required operators to find solutions outside ArgoCD.</p>
<p>Argo CD 3.5 introduces native, first-class mutual TLS (mTLS) support. By embedding encryption and identity verification directly into its components,
it eliminates the need for running service mesh sidecars, writing custom certificate-rotation scripts, or managing complex volume projections
just to make sure traffic is secure between the ArgoCD components.</p>
<h2>Why mTLS is one step ahead of one-way TLS</h2>
<p>Standard TLS provides one-way authentication: the client verifies the identity of the server via a certificate,
but the server accepts connections from any client inside the network boundary. So far so good!</p>
<p>Let's take, however, the case of zero-trust architecture, where relying entirely on the network is an antipattern.
If an attacker compromises a single pod within the cluster, they can potentially communicate with the <code>repo-server</code> without any authentication.</p>
<p>Mutual TLS (mTLS) addresses this by requiring both sides to authenticate before exchanging any data:</p>
<ul>
<li>The client verifies the <code>repo-server</code> certificate to ensure it is communicating with the legitimate repository management layer.</li>
<li>The <code>repo-server</code> validates the client's certificate against a trusted Certificate Authority (CA) to confirm that the incoming connection originates from an authorized Argo CD component.</li>
</ul>
<p>:::figure</p>
<p>:img{ src="/blog/img/mtls-support-argocd/blog_1.png" alt="mTLS vs TLS" }</p>
<p>:::</p>
<p>What do you gain by using mTLS:</p>
<ul>
<li>First and most important, you stop assuming internal cluster traffic is safe. Every internal component that needs to "talk" to the <code>repo-server</code> must explicitly prove its identity.</li>
<li>If your environment is regulated by compliance frameworks such as SOC 2, HIPAA, and PCI-DSS, then you are already covered. Native mTLS satisfies the security requirements without adding third-party dependencies.</li>
<li>By issuing different certificates to different components, you can precisely log which service initiated a connection and establish a foundation for fine-grained access controls.</li>
<li>Even if an attacker gains access to a pod, they can't actually talk to the <code>repo-server</code> because they lack the required signed client certificate.</li>
</ul>
<h2>The pre-3.5 reality: how operators managed internal encryption</h2>
<p>Before native support was introduced in version 3.5, achieving mTLS within an Argo CD deployment forced teams to choose between several complex architectural workarounds.</p>
<h3>The service mesh approach</h3>
<p>The most common pattern was transferring the encryption responsibility to an external service mesh like Istio or Linkerd.
Teams would inject sidecar proxies into their Argo CD pods to intercept traffic and handle the TLS handshake transparently.
This architecture was ok-ish. It worked, but if you ask operators, they would probably complain because they had to manage, upgrade, and debug an entirely separate control plane.
On the other side, if secure communications is a hard-requirement, then this approach was a great implementation.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/mtls-support-argocd/blog_4.png" alt="Service Mesh Approach for mTLS in Argo CD" loading="lazy" }</p>
<p>:::</p>
<h3>Manual cert-manager integration</h3>
<p>Other teams used <code>cert-manager</code> to automatically provision certificates into Kubernetes Secrets,
paired with custom Kustomize or Helm patches to manually map those secrets to specific paths inside the Argo deployments.
The main point of failure here was certificate rotation. Coordinating pod restarts to ensure components picked up renewed certificates often required writing custom wrapper scripts or relying on auxiliary operators like Reloader, which added more moving pieces to the platform.</p>
<h3>Static secret vaulting</h3>
<p>In highly locked-down environments, teams synchronized certificates from external stores like HashiCorp Vault using tools like the External Secrets Operator (ESO).
While this approach is very auditable-friendly, it creates a tight coupling between the secret store, the sync operator, and the GitOps controller.</p>
<p>In smaller companies, the fallback was often base64-encoding static, long-lived certificates directly into Kubernetes ConfigMaps or Secrets.
This bypassed infrastructure overhead but required manual secret rotation which was often a manual process.</p>
<p>Overall, you can see the same pattern in all three approaches described above (there might be more, but they all follow the same basic principles)
Internal communication security was handled as an external network problem rather than a native application capability, resulting in fragile configurations challenging to maintain.</p>
<h2>What's new in 3.5: built-in mTLS</h2>
<p>Argo CD 3.5 simplifies this landscape by moving the handshake, verification, and configuration logic entirely into the application.
You no longer need to write complex volume mounts or alter network layers; It's as simple as provisioning a single, specifically named Kubernetes Secret to activate mTLS.</p>
<p>Below you can find a high-level overview of the new features:</p>
<ol>
<li>Super-easy setup (auto-discovery). Argo CD automatically looks for a Secret named <code>argocd-repo-server-mtls</code>. When discovered, the manifests handle the internal mounting and environment configurations out of the box, eliminating manual template patching.</li>
<li>The <code>repo-server</code> requires self-directed communication to execute liveness and readiness probes. In a strict mTLS environment, a service can easily block its own health checks. Argo CD 3.5 addresses this by automatically generating memory-lived, ephemeral certificates dedicated exclusively to internal loopback probes, ensuring monitoring remains functional without manual intervention.</li>
<li>The feature accommodates both simple and highly complex environments. You can start with a single shared client certificate across all components and transition to unique per-component identities later without re-architecting your underlying deployment strategy.</li>
</ol>
<p>:::figure</p>
<p>:img{ src="/blog/img/mtls-support-argocd/blog_2.png" alt="mTLS handshake. How Argo CD 3.5 handles authenticates connections between Argo CD components" loading="lazy" }</p>
<p>:::</p>
<p>Now let's take a more detailed look at the setup process for the shared-certificate configuration.</p>
<h2>Getting started: the shared-certificate setup</h2>
<p>For the majority of production deployments, a single shared client certificate used by all client components is enough and requires minimum configuration</p>
<h3>Step 1: Generate the certificates</h3>
<p>If you are not using an automated PKI pipeline, you can generate the required CA, server, and client certificates using standard OpenSSL commands:</p>
<pre><code class="language-bash">openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
  -subj "/CN=argocd-internal-repo-ca"

openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
  -subj "/CN=argocd-repo-server"
openssl x509 -req -days 365 -in server.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out server.crt

openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
  -subj "/CN=argocd-core-clients"
openssl x509 -req -days 365 -in client.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out client.crt
</code></pre>
<h3>Step 2: Construct the mTLS kubernetes secret</h3>
<p>Create a secret named <code>argocd-repo-server-mtls</code> in your Argo CD namespace. The keys within the data block must strictly conform to the expected naming convention:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Secret
metadata:
  name: argocd-repo-server-mtls
  namespace: argocd
type: Opaque
data:
  client-ca.crt: &#x3C;BASE64_ENCODED_CA_CRT>
  client.crt: &#x3C;BASE64_ENCODED_CLIENT_CRT>
  client.key: &#x3C;BASE64_ENCODED_CLIENT_KEY>
  server-ca.crt: &#x3C;BASE64_ENCODED_CA_CRT>
</code></pre>
<p>If you are a CLI fan, you can generate and inject this secret directly using kubectl:</p>
<pre><code class="language-bash">kubectl create secret generic argocd-repo-server-mtls \
  --from-file=client-ca.crt=ca.crt \
  --from-file=client.crt=client.crt \
  --from-file=client.key=client.key \
  --from-file=server-ca.crt=ca.crt \
  -n argocd
</code></pre>
<h3>Step 3: Trigger a rolling restart</h3>
<p>As usual, to let the services pick up the new configuration, secrets, and certificates and initialize mTLS, you need to run a rolling restart across your deployments:</p>
<pre><code class="language-bash">kubectl rollout restart -n argocd \
  deployment/argocd-server \
  deployment/argocd-repo-server \
  deployment/argocd-application-controller \
  deployment/argocd-applicationset-controller
</code></pre>
<p>If you are running the application controller in a High Availability (HA) configuration, remember to target the statefulset instead:</p>
<pre><code class="language-bash">kubectl rollout restart -n argocd statefulset/argocd-application-controller
</code></pre>
<h3>Validating the connection handshake</h3>
<p>Once the pods are restarted, check the <code>argocd-repo-server</code> logs to confirm successful initialization.
You should observe log messages related to the self-generation of certificates for the internal health check as below:</p>
<pre><code class="language-terminaloutput">Generated ephemeral health-check client certificate (CN=argocd-repo-server-health)
</code></pre>
<p>To verify that mTLS is now enabled, try to execute a direct gRPC or HTTP request to the repo-server from an unauthenticated
pod inside the cluster. The connection should terminate immediately during the TLS handshake phase, log an untrusted client error on the server side,
and prevent any data exposure.</p>
<p>To the contrary all internal communications to <code>repo-server</code> are now encrypted.</p>
<p>So far we have seen how easy to enable mTLS using a default approach, same client certificate across all components. This probably works for most of the production environments,
but there are cases where you might want to use different certificates for different components. The following sections will show you how to do that.</p>
<h2>Advanced configurations: per-component certificates</h2>
<p>In enterprise environments with strict auditing requirements or multi-tenant architectures, sharing a single client certificate across all services may violate compliance rules.
Argo CD 3.5 supports unique and different, per-component certificates through the following two approaches.</p>
<p>:::figure</p>
<p>:img{ src="/blog/img/mtls-support-argocd/blog_3.png" alt="Advanced Configurations: Per-Component Identities" loading="lazy" }</p>
<p>:::</p>
<h3>Option A: multiple keys within a single secret</h3>
<p>You can store all individual component certificates inside the primary <code>argocd-repo-server-mtls</code> secret using distinct key identifiers.
This centralizes your secret management while allowing smooth delivery to each client component via volume projection patches.
Check the sample <code>yaml</code> below. You can see that for each component we have a separate key for the client certificate (<code>server-*</code>, <code>controller-*</code>, etc.)</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Secret
metadata:
  name: argocd-repo-server-mtls
  namespace: argocd
type: Opaque
data:
  client-ca.crt: &#x3C;BASE64_CA_PEM>
  server-client.crt: &#x3C;BASE64_SERVER_CERT_PEM>
  server-client.key: &#x3C;BASE64_SERVER_KEY_PEM>
  controller-client.crt: &#x3C;BASE64_CONTROLLER_CERT_PEM>
  controller-client.key: &#x3C;BASE64_CONTROLLER_KEY_PEM>
</code></pre>
<p>Once you have added the above secret to K8s, patch the volume mount configuration of each component deployment,
ensuring it maps its custom key to the filename the client expects (client.crt):</p>
<pre><code class="language-yaml">spec:
  template:
    spec:
      volumes:
      - name: argocd-repo-server-mtls
        secret:
          secretName: argocd-repo-server-mtls
          items:
          - key: server-client.crt
            path: client.crt
          - key: server-client.key
            path: client.key
          - key: client-ca.crt
            path: client-ca.crt
</code></pre>
<p>The main advantage of this approach is that you are still keeping all certificates in a single secret.
Obviously the disadvantage is that you need to maintain explicit volume mount overrides across all components that need to talk to <code>repo-server</code>.</p>
<h3>Option B: isolated secret objects per component</h3>
<p>The second approach suggests breaking the secret configuration to individual, component-specific secrets (e.g., <code>argocd-repo-server-mtls-server</code>, <code>argocd-repo-server-mtls-controller</code>):
So for each component that communicates with <code>repo-server</code>, you create a separate secret with the appropriate client certificate and key. For example:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Secret
metadata:
  name: argocd-repo-server-mtls-server
  namespace: argocd
type: Opaque
data:
  client.crt: &#x3C;BASE64_SERVER_CERT_PEM>
  client.key: &#x3C;BASE64_SERVER_KEY_PEM>
</code></pre>
<p>Then you need to patch again each deployment's volume specification to point to its corresponding secret:</p>
<pre><code class="language-yaml">spec:
  template:
    spec:
      volumes:
      - name: argocd-repo-server-mtls
        secret:
          secretName: argocd-repo-server-mtls-server
</code></pre>
<p>This is the most clear approach because you keep the secrets for each component isolated from the others.
This allows you to rotate keys independently of the primary secret, which is a common practice in multi-tenant environments.</p>
<p>But as you know, there's no free beer. The downside (if you think of it as a downside) of this approach is that you need to maintain separate Kubernetes Secret objects for each component.</p>
<h2>Real-world use cases</h2>
<h3>Comprehensive zero-trust layering</h3>
<p>Native mTLS provides a critical mid-tier authentication layer that complements existing security layers.
A strong and complete defense-in-depth model for Argo CD typically relies on the following layers:</p>
<ol>
<li>Network policies that restrict pod-to-pod communication paths so that only valid Argo CD components can route packets to the <code>repo-server</code> port.</li>
<li>K8s service accounts that define exactly what an authenticated client process is allowed to execute once the connection is opened.</li>
<li>Native mTLS which is used to authenticate data exchange between services at the application layer.</li>
<li>Application RBAC which allows fine-grained access control to Argo CD resources.</li>
</ol>
<h3>Audit-ready compliance (SOC 2 / HIPAA / PCI-DSS)</h3>
<p>When preparing for an audit, proving internal data security can be challenging when relying on third-party service meshes or complex bespoke scripts.
Auditors look for easily verifiable, reproducible security controls. Pointing to a native, platform-supported configuration driven by a declarative Kubernetes Secret simplifies the compliance narrative considerably compared to explaining a custom infrastructure mesh setup.</p>
<h3>Multi-tenant control planes</h3>
<p>For large organizations running multi-tenant internal developer platforms (IDPs), combining per-component certificates with upstream gRPC interceptors allows cluster administrators to log, trace, and isolate internal requests precisely by team or business unit.
This level of traceability is highly beneficial for forensics and chargeback metrics.</p>
<h2>Migration patterns</h2>
<p>Transitioning an active production cluster from a service mesh or custom cert-manager architecture to native mTLS might sound like a complex task but
it can be executed with minimal risk using the following, incremental, approach:</p>
<ol>
<li>You can keep your existing Root CA. Use your current certificate authority to generate the initial client certificates and keys.</li>
<li>Deploy the <code>argocd-repo-server-mtls</code> secret into the cluster while your existing sidecars or custom mounts are active. The presence of the secret will not change anything until a reload occurs.</li>
<li>Execute a rolling restart of all Argo CD components to pick up the new configuration.</li>
<li>Verify that the connection handshake (via the native mTLS configuration) is successful and that all internal traffic is now encrypted.</li>
<li>Once the connection is established, remove the old configuration, disable sidecar injection, etc.</li>
</ol>
<p>Well done! – you are now using native mTLS for all internal traffic.</p>
<h2>Architectural recommendation</h2>
<p>This article cannot cover all the production cases out there but we can summarize the recommendations for two groups of implementations.
If you are operating a small-to-medium-sized Argo CD installation looking for an immediate security upgrade with low operational friction and no specific requirement to audit individual internal component traffic streams.
On the other side, if you are looking for a more robust and comprehensive security model, because, for instance, you are in a highly regulated enterprise environment,
then you should consider the per-component approach that gives you the most flexibility and control.</p>
<p>For most teams, starting with a shared certificate configuration is the most pragmatic approach.
The good (excellent) news is that if your compliance needs scale over time, migrating to separate component identities involves adjusting your manifest overlays without replacing your underlying secrets architecture.</p>]]></content>
    </entry>
</feed>