Post

Setting Up Your Platform Hub Repo

Setting Up Your Platform Hub Repo

This post walks through setting up the GitHub repository that powers the IssueOps policy authoring workflow for Octopus Deploy Platform Hub. By the end, you’ll have a repo wired up as the GitOps source of truth for your Platform Hub — storing OCL policy files, process templates, and project templates — with two AI agents ready to author and publish policies on demand.

This is the foundation PoC for the IssueOps theme. The posts on generating policies with IssueOps and authoring locally with Copilot both assume this setup is in place.


Prerequisites

  • Octopus Deploy Platform Hub configured and connected to a GitHub repository via the Platform Hub version control settings
  • GitHub repository with Actions enabled — this will become your platform-hub repo
  • GitHub Copilot with agent mode enabled on your account, or access to Claude Code (you need at least one)
  • Octopus MCP server configured if you intend to use Claude Code for local authoring (the Policy Author agent uses it for what-if analysis)
  • Familiarity with Octopus Platform Hub concepts — this post doesn’t cover Platform Hub basics

How the repo is structured

Before creating anything, it’s worth understanding the directory layout and what each part does, so the setup decisions make sense.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.octopus/
  policies/           # OCL policy files — source of truth for Platform Hub
  process-templates/  # Shared deployment process templates
  project-templates/  # Reusable project templates

octopus-agent/
  output-<timestamp>/
    reports/          # What-if reports produced by the Policy Author agent

.claude/
  agents/             # Agent definitions for Claude Code

.github/
  agents/             # Agent definitions for GitHub Copilot
  ISSUE_TEMPLATE/     # Issue template for submitting policy requests

policies-documentation/
  schema.md           # OCL policy schema reference
  best-practices.md   # Authoring guidelines
  examples.md         # Example policies
  troubleshooting.md  # Common issues and fixes

The .octopus/ directory is what Platform Hub reads. Everything else supports the authoring workflow. The octopus-agent/ directory accumulates what-if reports over time — it’s worth committing these alongside the OCL files so there’s a permanent record of the impact analysis that preceded each policy.


Phase 1: Connect the repo to Platform Hub

Platform Hub needs to know which GitHub repository holds your OCL files. This is configured under Platform Hub’s version control settings.

TODO: Screenshot — Platform Hub version control settings page, showing the GitHub repository field, default branch, and policies directory path filled in

Set the policies directory to .octopus/policies. Set the default branch to main. Platform Hub will read from this branch when publishing — which is why the Policy Publisher agent hard-stops if your OCL file hasn’t been pushed there yet.

Once connected, create the directory structure manually or let it be created by the first agent run. Either works, but creating the skeleton upfront avoids confusion on the first run.

1
2
3
4
5
6
7
8
mkdir -p .octopus/policies
mkdir -p .octopus/process-templates
mkdir -p .octopus/project-templates
mkdir -p octopus-agent
mkdir -p .claude/agents
mkdir -p .github/agents
mkdir -p .github/ISSUE_TEMPLATE
mkdir -p policies-documentation

Commit and push to main.


Phase 2: Add the agent definition files

The two agents — Policy Author and Policy Publisher — are defined as Markdown files that tell Copilot or Claude Code how to behave. These files are the same regardless of whether you’re using GitHub Copilot Cloud, Claude Code locally, or Copilot in VS Code/JetBrains. The invocation method differs; the agent logic doesn’t.

Policy Author agent

Create .github/agents/octopus-deploy-policy-author.agent.md and .claude/agents/octopus-deploy-policy-author.md with the Policy Author agent definition. The agent runs four phases in order:

  1. Intent parsing — maps plain-English intent to specific input.* fields in the Platform Hub schema
  2. OCL authoring — checks for existing policies that cover the same ground before writing a new file; derives a snake_case package name; writes the OCL in the correct format with 12-space Rego indentation
  3. What-if analysis — calls the Octopus MCP server to resolve projects, fetch deployment processes, and evaluate each project statically against the authored policy
  4. Report generation — writes the what-if report to octopus-agent/output-<timestamp>/reports/<package_name>.md and commits the OCL file

The key constraints baked into the agent definition:

  • Never uses input.* fields that aren’t in the Platform Hub schema
  • Never combines two independent policy conditions into one OCL file
  • Forces violation_action = "warn" when scope covers more than one project
  • Never deletes OCL files — retires by setting default evaluate := false in scope

TODO: Code block or link — Add the full agent definition file here, or link to the file in the repo once it’s public. The definition is long (~400 lines) and is better read in context than reproduced inline.

Policy Publisher agent

Create .github/agents/octopus-deploy-policy-publish.agent.md and .claude/agents/octopus-deploy-policy-publish.md with the Policy Publisher agent definition.

The publisher runs these pre-flight checks before making any API calls:

  1. OCL file exists at .octopus/policies/<slug>.ocl
  2. Policy is not fully disabled (scope block has an evaluate if override)
  3. File is committed, not just staged
  4. Commit has been pushed to the remote default branch — hard stop if not

Then it publishes in three steps: resolve the current version via GET /api/platformhub/policies/<slug>/versions/v2, increment the patch version, publish via POST /api/platformhub/main/policies/<slug>/publish, and activate via POST /api/platformhub/policies/<slug>/versions/<version>/modify-status.

The hard stop on unpushed commits is intentional and important. The Octopus publish API reads from the remote default branch, not your local files. If you run the publisher before pushing, it silently publishes the previous committed version — the policy activates but enforces the wrong content. The pre-flight check makes this failure mode impossible rather than just unlikely.

TODO: Code block or link — Add the full publisher agent definition here, or link to the repo.


Phase 3: Add the issue template

The GitHub issue template is what makes the IssueOps flow work — it structures the request so the Policy Author agent always receives the inputs it needs without having to ask clarifying questions.

Create .github/ISSUE_TEMPLATE/octopus-deploy-add-policy.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name: 🐙 Add Octopus Deploy Policy
description: Add an Octopus Deploy platform hub policy
title: "[OD-Policy] <title>"
labels: ["🐙 policy", "🤖 copilot"]
body:
- type: textarea
  id: policy-prompt
  attributes:
    label: Describe the policy
    description: >
      Please enter the policy you would like created. Policies are best when
      they are direct — do not include multiple policies in one request.
      Break those out to separate issues.
  validations:
    required: true
- type: input
  id: projects-list
  attributes:
    label: Project(s) in scope
    description: >
      Enter the Octopus Deploy project in the format `project-slug` OR
      `space/project-slug`. Enter multiple as comma-separated values.
    placeholder: Default/project-one,SecondSpace/project-two
  validations:
    required: true

The two labels — 🐙 policy and 🤖 copilot — are what trigger the Copilot Coding Agent automatically. Without both labels, the issue is created but Copilot doesn’t pick it up.

TODO: Screenshot — GitHub repository issue templates page showing the 🐙 Add Octopus Deploy Policy template in the list


Phase 4: Add the policies documentation

The Policy Author agent uses the policies documentation as context when generating Rego. Without it, the agent produces syntactically valid OCL that may reference incorrect field names or miss Platform Hub-specific conventions.

The documentation lives in policies-documentation/ and covers:

  • schema.md — every input.* field the policy engine exposes, with types and conditional presence notes
  • best-practices.md — naming conventions, the warn-before-block principle, existence-and-skipping checks
  • examples.md — ready-to-use Rego organized by enforcement goal (required steps, production controls, process structure, tagging standards)
  • troubleshooting.md — common evaluation problems and how to diagnose them

TODO: Note — The documentation files are sourced from the Octopus Platform Hub docs. Add links to the canonical docs pages here, or copy the content into the repo files if you want the agent to work offline or against a private Octopus instance.

The quality of the agent’s output is directly proportional to the quality of the documentation it has access to. The schema and examples files matter most — the schema tells it which fields exist, and the examples show it the patterns to follow.


Phase 5: Seed the repo with your first OCL file

Before the agents have run, the .octopus/policies/ directory is empty. The Policy Author agent checks for existing policies before creating new ones — if the directory is empty, it always creates a new file. Once you have a few policies in place, it will correctly identify cases where extending an existing policy scope is better than creating a duplicate.

A good first policy to add manually is a broad warn-mode check that gives you confidence the pipeline is working before you write stricter rules. For example, a policy that warns whenever a deployment to production happens without any manual intervention step, scoped to a single low-risk project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
name = "Deploy - Require Manual Intervention for Production"
description = "Enforces that deployments to Production include at least one enabled manual intervention step."
violation_action = "warn"
violation_reason = "A manual intervention step is required for deployments to Production but is missing from the deployment process."

conditions {
    rego = <<-EOT
            package require_manual_intervention_production

            default result := {"allowed": false}

            targeting_production if {
                input.Environment.Slug == "production"
            }

            has_manual_intervention if {
                some step in input.Steps
                step.ActionType == "Octopus.Manual"
                step.Enabled == true
            }

            result := {"allowed": true} if {
                not targeting_production
            }

            result := {"allowed": true} if {
                targeting_production
                has_manual_intervention
            }

            result := {
                "allowed": false,
                "reason": "Deployment to Production requires a manual intervention step. Add an enabled Octopus.Manual step to the deployment process.",
            } if {
                targeting_production
                not has_manual_intervention
            }
            EOT
}

scope {
    rego = <<-EOT
            package require_manual_intervention_production

            default evaluate := false

            evaluate if {
                input.Project.Slug == "your-project-slug"
                not input.Runbook
            }
            EOT
}

Replace your-project-slug with a real slug from your Octopus instance. Commit and push this to main, then use the Policy Publisher agent to activate it. Seeing a policy publish successfully end-to-end confirms the repo-to-Platform Hub connection is working before you introduce the full IssueOps flow.

TODO: Screenshot — Platform Hub policies list showing the manually seeded policy in an active state after the first publisher run


Known limitations / what I’d do differently

The agent documentation context is manual to maintain. The policies-documentation/ files are copies of the Octopus docs at a point in time. When the Platform Hub schema evolves — new input.* fields, new action types, changes to the OCL format — the agent’s context goes stale silently. A better approach would be to fetch from the live docs at agent runtime, or set up a scheduled sync. For now, treating these files as versioned documentation with explicit update dates is the minimum viable approach.

Branch protection rules interact with the IssueOps flow. If your repo has branch protection requiring PR reviews before merging to main, the Copilot Coding Agent will open a PR but someone still needs to approve it before the publisher can run. That’s the intended governance model — but it means the fully automated end-to-end path (issue opened → policy activated, no human touch) isn’t achievable with branch protection in place. This is a feature, not a bug, but it’s worth being explicit about when setting expectations with stakeholders.

One policy per OCL file is a hard constraint. The agent enforces this, but it means your policies directory grows quickly if you have many narrow rules. This is a good thing for auditability and independent versioning, but it can feel like overhead when you’re managing dozens of files. A naming convention and periodic review of whether policies can be consolidated helps.

The Octopus MCP server must be running for what-if analysis. The Policy Author agent’s what-if phase calls the Octopus MCP server to fetch live project and deployment process data. If the MCP server isn’t connected when the agent runs, it will skip the what-if analysis and produce only the OCL file — noting the omission in its output. Always verify MCP connectivity before triggering the author agent on consequential policies.

This post is licensed under CC BY 4.0 by the author.