Building a Custom AI Worker Image for Octopus
This post walks through setting up octopus-deploy-ai-steps — a reusable step template that drops Claude Code or GitHub Copilot CLI into any Octopus runbook or deployment process as a non-interactive agent. By the end, you’ll have a working Octopus step that accepts a prompt, runs an AI agent against your environment, and publishes a markdown report as a deployment artifact.
This is the foundation PoC for the AI Pipelines theme. Later posts on K8s troubleshooting and CVE triage build on top of what’s set up here.
Prerequisites
- Octopus Deploy 2024.1 or later — earlier versions don’t support Git-sourced step templates
- An Anthropic API key (for Claude Code) or a GitHub token with Copilot scope (for Copilot CLI) — you need at least one
- A Linux worker with Docker for container mode (recommended), or any worker with internet access for local mode
kubectlconfigured on the worker if you intend to use the agent for Kubernetes tasks- Familiarity with Octopus step templates and variable sets — this post doesn’t cover Octopus basics
How the step template works
Before configuring anything, it’s worth understanding the execution model so the parameter choices make sense.
The step template is a shell script (Bash for Linux, PowerShell for Windows) that you point Octopus at via a Git source. When the step runs, it:
- Reads routing flags to determine which agent and execution mode to use
- Optionally installs dependencies (Node.js, the CLI tool) if running in local mode
- Writes an MCP configuration file if you’ve provided one
- Resolves the prompt — from inline text, a local file, or a GitHub URL
- Runs the AI CLI non-interactively with the prompt and any tool configuration
- Captures stdout and stderr to the working directory
- Publishes the output as an artifact attached to the Octopus deployment task
- Optionally cleans up the working directory
The routing is handled by two entry point scripts:
scripts/octo-ai.sh— Linux and macOS workersscripts/octo-ai.ps1— Windows workers
Three flags drive the behaviour:
| Flag | Options | Notes |
|---|---|---|
--ai-agent | claude or copilot | Which CLI to run |
--os | linux or windows | Determines which scripts are sourced |
--mode | container or local | Container pulls a pre-built image; local installs directly on the worker |
flowchart TD
Caller["Octopus Step\n--ai-agent --os --mode\n--prompt-content --mcp-configuration\n--allowed-tools --max-turns"]
Entry["octo-ai.sh / octo-ai.ps1\nreads routing flags only\npasses all args verbatim"]
InstallDeps["install-dependencies.sh / .ps1\n(local mode only)"]
AgentScripts["claude-linux.sh · claude-windows.ps1\ncopilot-linux.sh · copilot-windows.ps1\nsources lib/common.*"]
ResolvePrompt["resolve_prompt"]
WriteMCP["write_mcp_config"]
InstallNode["install_node + install_npm_tool\n(local mode only)"]
AICLI["AI CLI\nclaude -p ... / copilot --prompt ..."]
Output["stdout → output.json\nstderr → agent-stderr.log"]
Artifacts["publish_all_artifacts\nPOST /api → Octopus artifact API"]
Cleanup["cleanup_workdir\n(if --cleanup=true)"]
Caller --> Entry
Entry -->|"local + install-deps=true"| InstallDeps
InstallDeps --> AgentScripts
Entry --> AgentScripts
AgentScripts --> ResolvePrompt & WriteMCP & InstallNode
ResolvePrompt & WriteMCP & InstallNode --> AICLI
AICLI --> Output
Output --> Artifacts
Artifacts --> Cleanup
Phase 1: Register the step template in Octopus
Octopus supports Git-sourced step templates, which means you can point directly at the repository rather than copying scripts into the library.
- In Octopus, go to Library → Step Templates → Add Step Template.
- Choose Run a Script as the base template type.
- Under Script Source, select Git repository.
- Set the repository URL to
https://github.com/rohitnb/octopus-deploy-ai-steps. - Set the branch to
main. - Set the script file to
scripts/octo-ai.shfor Linux workers, orscripts/octo-ai.ps1for Windows.
Save the template. You’ll configure parameters in the next phase.
Phase 2: Configure the parameters
The step template exposes a set of parameters that control everything from agent selection to cleanup behaviour. Add these to the template definition.
Required parameters:
| Parameter | Type | Description |
|---|---|---|
| AI Agent | Single choice | claude or copilot |
| Worker OS | Single choice | linux or windows |
| Execution Mode | Single choice | container or local |
| AI Agent API Key | Sensitive text | Anthropic key or GitHub token |
| Octopus API Key | Sensitive text | Key with artifact write access |
| Octopus Server URL | Text | Your Octopus instance URL |
| Octopus Space Name | Text | Name of the target space |
| Prompt Content | Large text | Inline prompt, file path, or GitHub URL |
Optional parameters:
| Parameter | Type | Description |
|---|---|---|
| Install Dependencies | Boolean | Auto-install Node.js and CLI in local mode |
| MCP Configuration | Large text | JSON config for MCP servers |
| Allowed Tools | Text | Comma-separated list of tools the agent may call |
| Max Turns | Number | Cap on agent reasoning turns (default: no limit) |
| Cleanup Working Directory | Boolean | Delete working directory after run |
| GitHub PAT | Sensitive text | Required if prompts are sourced from private GitHub URLs |
Phase 3: Store secrets in a variable set
Rather than entering API keys in every project that uses the step, store them once in a shared variable set.
- Go to Library → Variable Sets → Add Variable Set. Name it something like
AI Pipeline Credentials. - Add variables for your Anthropic API key, Octopus API key, and server URL.
- Mark all key and token variables as Sensitive — Octopus will mask them in task logs.
- In each project that uses the AI step, include this variable set under Variables → Library Variable Sets.
Using a centralized variable set means rotating a key is a one-place change.
Phase 4: Add the step to a runbook
With the template registered and credentials stored, add the step to a runbook.
- Open or create a runbook in your target project.
- Add a step, search for your registered step template by name.
- Set the execution worker to your Linux Docker worker (for container mode) or the appropriate deployment target.
- Fill in the parameters, binding sensitive values to your variable set variables using Octopus variable binding syntax:
#{AI Pipeline Credentials.AnthropicApiKey}. - Set the prompt. For an initial test, use inline text:
"List all namespaces in the current Kubernetes context and report any with no running pods.".
Phase 5: Choose your execution mode
Container mode (recommended for Linux workers with Docker) pulls a pre-built image from GHCR that includes Claude Code, Node.js, and kubectl. The worker needs Docker but nothing else. This is the approach to standardise on — the image version is pinned, so you get reproducible behaviour across runs.
Set --mode=container and leave --install-dependencies unset.
Local mode runs the scripts directly on the worker. Useful when Docker isn’t available — a bare VM, a deployment target you don’t control fully, or a Windows worker. On first run, set --install-dependencies=true and the script will install Node.js and the CLI tool. On subsequent runs you can set it to false to skip the install and speed up the step.
The tradeoff: local mode means the CLI version is whatever was installed on first run, not pinned to an image tag. If the CLI updates and behaviour changes, you won’t necessarily notice until a prompt produces unexpected output.
Phase 6: Configure MCP servers (optional)
MCP (Model Context Protocol) lets you extend what tools the agent can call during execution. This is how you give the agent access to APIs or services beyond kubectl and the Octopus API.
The step accepts MCP configuration as a JSON string. A basic example that wires in a Kubernetes MCP server:
1
2
3
4
5
6
7
8
9
10
11
{
"mcpServers": {
"kubernetes": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-kubernetes"],
"env": {
"KUBECONFIG": "/home/octopus/.kube/config"
}
}
}
}
The step substitutes any ${VARIABLE} placeholders in the JSON with values from the Octopus environment before writing the config file. Store the raw JSON in an Octopus variable and bind it to the MCP Configuration parameter.
Phase 7: Restrict tool access with an allowlist
By default, the agent can call any tool available to it. For runbooks where you want the agent to read and reason but not take write actions, pass a comma-separated allowlist to --allowed-tools.
For a read-only Kubernetes inspection runbook:
1
kubectl,octopus_api_get
For a runbook that should only read logs:
1
kubectl_logs,kubectl_describe
The allowlist is enforced by the CLI tool itself — the agent will not attempt calls outside it. This is the primary safety mechanism for limiting agent scope in production runbooks.
Phase 8: Run it and inspect the artifact
Trigger the runbook. The step will run the agent, capture its output, and publish it as an artifact named agent-report.md attached to the task.
To view the artifact:
- Open the completed runbook task.
- In the task summary panel, look for Artifacts.
- Click
agent-report.mdto open the markdown report in the browser.
The report format depends on what you asked the agent to do, but for a diagnostic prompt you’ll typically see: findings, relevant evidence (log lines, event descriptions, config excerpts), and recommendations. The agent narrates its reasoning, not just its conclusions.
Known limitations / what I’d do differently
Prompt quality is everything. The step template is a delivery mechanism — it gets the agent to the environment with the right credentials and tools. What the agent actually does depends entirely on the prompt. Vague prompts produce vague reports. The investment in prompt design pays off in report quality, and it’s worth treating prompts as versioned artifacts just like the scripts themselves. A GitHub URL prompt source makes this easy.
Container mode image updates are manual. The pre-built image on GHCR is pinned by the step configuration, not auto-updated. When Claude Code or Copilot CLI release significant updates, you’ll need to pull a newer image tag and update the step configuration. There’s no automatic signal that this is worth doing — it’s a gap worth closing with a periodic review process.
MCP server stability varies. Some MCP servers are well-maintained; others are community projects that may lag behind API changes in the underlying service. If an MCP server is a critical part of your runbook, test it separately from the AI step and treat it as a dependency with its own reliability profile.
Local mode creates drift risk. If you’re running multiple workers in local mode, CLI versions may diverge across workers over time unless you have a process for keeping them in sync. Container mode sidesteps this entirely.
Artifact size isn’t bounded. A very long-running agent on a complex diagnostic task can produce a large report. Octopus doesn’t enforce a size limit on artifacts, but large artifacts slow down the task summary UI. Setting --max-turns caps the agent’s reasoning depth and keeps reports to a manageable size.