Files
best-practices/octopus-process-templates.md
Paul O'Reilly 7e348f5ee3 distill: 48 cross-project best-practices from 2026-07 reflection sweep
Promotions from reflecting 21 projects' session logs (incl. agent-runtimes
122-log drain). Adds coverage across networking (eBPF VIP/VPN SNAT/VLAN
bridge/forward-auth preflight/ingress TLS), kubernetes (CSI hotplug/PodSecurity
debug/self-managed GitOps/runtime annotations), CI (dispatch tokens/runner
death/base image), git (CI-rebase/shallow reset/PR governance), python (async
session pool/httpx redirects/logging), TDD (AsyncMock/xfail lifecycle),
api-integration (SDK parse/token-scope 404/schema probing), plus docker,
scripting, debugging, security-architecture, secrets, react, octopus.

State: .distill-state.json refreshed with current HEADs + 5 newly-tracked projects.
2026-07-02 15:57:42 +12:00

273 lines
12 KiB
Markdown

# Octopus Deploy Process Templates
Best practices for creating and managing Octopus Deploy process templates using OCL (Octopus Configuration Language) in Platform Hub.
## Key Concepts
- **Step templates** (action templates) are reusable individual steps, created via API or UI, stored in a space's library
- **Process templates** are reusable multi-step deployment processes, stored as OCL files in Platform Hub's Git repo
- **Project templates** compose process templates into full project configurations (feature coming soon)
- Process templates live in `.octopus/process-templates/<slug>.ocl` in the Platform Hub Git repo
- Projects consume process templates via `process_template` blocks in their `deployment_process.ocl`
## OCL File Structure
### Process Template File
```hcl
name = "Deploy to Kubernetes - Helm"
description = "Standard Helm-based Kubernetes deployment with pre-validation and smoke tests"
# Parameters — values supplied by consuming projects
parameter "target_tags" {
display_settings = {
Octopus.ControlType = "TargetTags"
}
help_text = "Kubernetes target tags"
label = "Target Tags"
}
parameter "cloud_target" {
display_settings = {
Octopus.ControlType = "SingleLineText"
}
help_text = "Cloud provider (gcp, aws, azure)"
label = "Cloud Target"
value "gcp" {} # default value
}
# Steps — ordered deployment steps
step "deploy-helm" {
name = "Deploy via Helm"
properties = {
Octopus.Action.TargetRoles = "#{target_tags}"
}
action {
action_type = "Octopus.Script"
properties = {
Octopus.Action.Script.ScriptSource = "Inline"
Octopus.Action.Script.Syntax = "PowerShell"
Octopus.Action.Script.ScriptBody = "Write-Host 'Deploying...'"
}
worker_pool_variable = ""
}
}
```
### How Projects Consume Process Templates
In a project's `deployment_process.ocl`:
```hcl
process_template "deploy-app" {
name = "Deploy Application"
process_template_slug = "deploy-to-kubernetes-helm"
version_mask = "1.X" # auto-update minor/patch
parameter "target_tags" {
value = "kubernetes,production"
}
parameter "cloud_target" {
value = "aws"
}
}
```
## OCL Syntax Rules
From the EBNF grammar (https://github.com/OctopusDeploy/Ocl):
- **Name, `=`, and value** must be on the same line
- **Block name, labels, and `{`** must be on the same line
- **Closing `}`** must be on its own line (except empty blocks like `value "default" {}`)
- **Strings** use double quotes, cannot contain unescaped `"`
- **Multi-line strings** use heredoc: `<<-EOF` / `EOF` (indented variant)
- **Arrays** use `["item1", "item2"]`
- **Dictionaries** use `{ key = value }` (one entry per line inside braces)
## Referencing Step Templates
Step templates are referenced by **ID and version** in the action's properties, NOT by name:
```hcl
step "run-tests" {
name = "Run Unit Tests"
action {
# Reference a step template instead of action_type
properties = {
Octopus.Action.Template.Id = "ActionTemplates-62"
Octopus.Action.Template.Version = "1"
# Step template parameter values
Language = "go"
CloudTarget = "gcp"
}
worker_pool_variable = ""
}
}
```
When NOT using a step template, define `action_type` directly:
```hcl
action {
action_type = "Octopus.Script"
properties = { ... }
}
```
## Channel Scoping
Scope steps to specific channels using the `channels` attribute on the action block. Uses **channel slugs** (auto-generated from names):
```hcl
action {
action_type = "Octopus.Script"
channels = ["non-prod"] # only runs in non-prod channel
properties = { ... }
}
```
## Package References
```hcl
# Container image for worker execution
container {
feed = "registered-dockerhub" # feed slug, not ID
image = "octopusdeploy/worker-tools:ubuntu.22.04"
}
# Package reference in a step
packages "MyPackage" {
acquisition_location = "NotAcquired" # Server | ExecutionTarget | NotAcquired
feed = "platformhub-non-prod" # feed slug
package_id = "paul-oreilly-octopus/nonprod/listing-service"
properties = {
SelectionMode = "immediate"
}
}
```
## Parameter Types
| Control Type | `Octopus.ControlType` Value | Can Have Default |
|---|---|---|
| Single-line text | `SingleLineText` | Yes |
| Multi-line text | `MultiLineText` | Yes |
| Sensitive/password | `Sensitive` | Yes (encrypted) |
| Checkbox | `Checkbox` | Yes |
| Dropdown | `Select` | Yes |
| AWS/Azure/GCP Account | `AWSAccount` etc. | Yes |
| Worker Pool | (worker pool) | No |
| Package | (package) | No |
| Target Tags | `TargetTags` | No |
| Environments | (environments) | No |
| Channels | (channels) | No |
## Step Properties Reference
| Property | Type | Values | Default |
|---|---|---|---|
| `step.condition` | enum | `Success`, `Failure`, `Always`, `Variable` | `Success` |
| `step.start_trigger` | enum | `StartAfterPrevious`, `StartWithPrevious` | `StartAfterPrevious` |
| `step.package_requirement` | enum | `LetOctopusDecide`, `BeforePackageAcquisition`, `AfterPackageAcquisition` | `LetOctopusDecide` |
| `action.channels` | string[] | channel slugs | all |
| `action.environments` | string[] | environment slugs | all |
| `action.excluded_environments` | string[] | environment slugs | none |
| `action.is_disabled` | bool | | `False` |
| `action.is_required` | bool | | `False` |
| `action.notes` | string | step description | |
| `action.worker_pool` | string | worker pool slug | |
| `action.worker_pool_variable` | string | variable name | |
## Versioning
- Process templates use **semantic versioning** (major.minor.patch)
- `version_mask = "1.X"` in consuming projects auto-updates on minor/patch changes
- **Major version bumps** require explicit upgrade by consuming projects
- Keep major bumps for breaking changes (parameter renames, step removals)
- Minor/patch for new optional parameters, script improvements, bug fixes
## Dual-Image / Multi-Registry Channel Pattern
When a project builds distinct images to separate registries per environment tier (e.g., non-prod vs prod), model each registry as its own **Feed** and create **one channel-scoped deployment step per feed**. Two channel-scoped steps are cleaner than a single step with version rules, because differing `PackageId` values (`org-nonprod/app` vs `org-prod/app`) cannot both be satisfied by a single step's package reference.
```hcl
step "deploy-nonprod" {
action {
channels = ["non-prod"]
packages "app" {
feed = "registry-nonprod"
package_id = "org-nonprod/app"
}
}
}
step "deploy-prod" {
action {
channels = ["prod"]
packages "app" {
feed = "registry-prod"
package_id = "org-prod/app"
}
}
}
```
## Release Creation: CI-Driven vs Feed Triggers
Octopus feed triggers silently ignore non-SemVer Docker tags (e.g., `sha-abc12345`) and poll on a ~3-minute interval. For SHA-tagged workflows or any non-SemVer scheme:
- **Preferred:** have CI `POST /api/releases` directly after pushing the image. This is instant, avoids simultaneous-trigger race conditions on `UQ_ReleaseVersionUnique`, and works with any tag format.
- **Alternative:** tag with SemVer plus a timestamp build number (`1.2.3+YYYYMMDDHHMM`) for guaranteed uniqueness without needing external state.
Feed triggers remain fine for pure-SemVer tag schemes.
## Channel Version Rules and Enforcement
Channel version rules require **either a version range or a pre-release tag** — they cannot be empty. For non-SemVer tagging schemes, remove all rules (`Rules: []`) and rely on step channel-scoping (`action.channels`) for enforcement instead.
The **default channel** on any project cannot be deleted; either leave it unused or promote another channel to default before removing it.
## Auto-Promoting Lifecycles for Hands-Free Demos / PoCs
To demonstrate a full CI-to-deployed flow, set lifecycle phases to auto-deploy by moving environments from `OptionalDeploymentTargets` to `AutomaticDeploymentTargets`. Pair a non-prod lifecycle (Dev → Staging) with a prod lifecycle (Staging → Production) so each channel's releases promote automatically once created.
## Gotchas
1. **Step templates are space-scoped.** Process templates in Platform Hub cannot reference `ActionTemplates-*` IDs from other spaces. If you need reusable steps, use inline `action_type = "Octopus.Script"` in the process template OCL. Step templates are useful within a single space's projects, but not for cross-space process templates.
2. **Process template names** cannot contain parentheses, slashes, or ampersands — only letters, numbers, periods (`.`), commas (`,`), dashes (`-`), underscores (`_`), and hashes (`#`).
3. **Heredoc for multi-line scripts** — use `<<-EOT` / `EOT` for PowerShell scripts that contain double quotes. The `-` prefix allows indented closing tags.
4. **Every step needs a worker pool.** Process templates must have a `worker_pool` parameter (type `WorkerPool`), and every action must set `worker_pool_variable = "worker_pool"` referencing it. Without this, the template will fail to parse with "A step must specify a worker pool parameter".
5. **Publishing and sharing is UI-only.** Process template sharing (which spaces can see/use a template) is stored in the Octopus database, not in Git/OCL. You must publish and share each template through the UI. There is no API or CLI for this currently.
6. **Space creation via API requires a manager.** `POST /api/spaces` rejects an empty `SpaceManagersTeamMembers` with "select either teams and/or users as managers". Always include at least one user ID.
## Best Practices
1. **One template per deployment pattern**, not per cloud. Use parameters to vary cloud-specific behaviour.
2. **Use step templates for reusable individual steps**, process templates for reusable multi-step workflows.
3. **Parameters should have sensible defaults** where possible — reduces friction for consuming projects.
4. **Use `TargetTags` parameter type** for Kubernetes target selection rather than hardcoding roles.
5. **Name templates with the action, not the technology**: "Deploy to Kubernetes" not "Helm Chart Deployer".
6. **Keep descriptions updated** — they appear in the UI when browsing templates.
7. **Process templates cannot reference the project's own Git repo** for scripts — use inline scripts or external URLs.
8. **Test templates** by creating a test project that consumes them before sharing widely.
## Deployment Freeze Gotchas
- **Freeze recurrence is Daily/Weekly/Monthly only — no sub-daily granularity.** `RecurringSchedule.Type` accepts only `Daily`, `Weekly`, `Monthly`; `Cron`, `OnceDaily`, `Custom`, and `None` are rejected by validation. A short-cycle rolling freeze (e.g. a few minutes in every ten) cannot be expressed as a native recurring freeze. Fallback: a scheduled runbook that rewrites the freeze's `Start`/`End` every N minutes.
- **Deployment freezes are instance-level, not space-scoped.** Use `/api/deploymentfreezes` — the space-scoped path 404s silently.
## References
- OCL Syntax: https://octopus.com/docs/projects/version-control/ocl-file-format
- Config as Code Reference: https://octopus.com/docs/projects/version-control/config-as-code-reference
- Process Templates: https://octopus.com/docs/platform-hub/templates/process-templates
- Template Parameters: https://octopus.com/docs/platform-hub/templates/parameters
- Publishing & Sharing: https://octopus.com/docs/platform-hub/templates/publishing-and-sharing
- Best Practices: https://octopus.com/docs/platform-hub/templates/process-templates/best-practices
- Troubleshooting: https://octopus.com/docs/platform-hub/templates/process-templates/troubleshooting
- OCL Grammar (EBNF): https://github.com/OctopusDeploy/Ocl