Andrew Mercer

Octopus Deploy CLI & API — Setup and Usage Guide

1. Install the Octopus CLI

The modern CLI is simply called octopus (older versions were octo — if you see octo referenced anywhere, that's the legacy tool, deprecated in favor of this one).

Linux (including WSL, matching your usual setup):

# via apt (Debian/Ubuntu)
curl -sSfL https://apt.octopus.com/public.key | sudo apt-key add -
sudo sh -c 'echo "deb https://apt.octopus.com/ any main" > /etc/apt/sources.list.d/octopus.com.list'
sudo apt-get update
sudo apt-get install octopuscli

Or via direct binary download (avoids apt-key deprecation warnings on newer Ubuntu):

curl -L -o octopuscli.tar.gz https://download.octopusdeploy.com/octopus-tools/9.1.7/OctopusTools.9.1.7.linux-x64.tar.gz
mkdir -p ~/bin/octopus-cli
tar -xzf octopuscli.tar.gz -C ~/bin/octopus-cli
echo 'export PATH="$HOME/bin/octopus-cli:$PATH"' >> ~/.bashrc
source ~/.bashrc

Check octopus.com/downloads/octopuscli for the current version number before pulling — 9.1.7 is illustrative, confirm the latest.

Verify install:

octopus version

2. Create an API key

  1. Log into your Octopus instance in the browser
  2. Click your profile icon (top right) → Profile
  3. Go to the My API Keys tab
  4. Click New API Key
  5. Give it a purpose-specific name (e.g. andrew-cli-nginx-audit) — makes it easy to identify and revoke later without guessing what it was for
  6. Set an expiry if your org's policy requires it (recommended regardless — don't create a permanent key for a one-off task)
  7. Copy the key immediately — like most API key flows, Octopus shows it once and won't let you retrieve it again

Store it properly, matching your usual credential handling:

pass insert octopus/api-key

3. Authenticate the CLI

Two ways to supply credentials — env vars (good for scripting/CI) or per-command flags.

Environment variables (recommended for interactive use):

export OCTOPUS_URL="https://your-instance.octopus.app"
export OCTOPUS_API_KEY="$(pass octopus/api-key)"

Add these to ~/.bashrc if you'll use the CLI regularly, or keep them in a small .env-style script you source per session if you'd rather not have the key sitting in shell history/environment persistently.

Per-command flags (explicit, no env dependency):

octopus project list --url "https://your-instance.octopus.app" --api-key "$(pass octopus/api-key)"

Test the connection:

octopus space list

If this returns your Octopus space(s) without error, auth is working.

4. Core commands you'll actually use

List/inspect resources

octopus project list
octopus environment list
octopus release list --project "infrastructure-logging"
octopus deployment list --project "infrastructure-logging" --environment "Dev"

Working with variables

The CLI's variable management is more limited than the API directly — for reading/writing project variables (like your auth_basic_user_file_base64 case), you'll often need the raw REST API rather than a dedicated CLI subcommand, since sensitive variables in particular have limited CLI support. See section 5.

Create a release

octopus release create \
  --project "infrastructure-logging" \
  --version "1.2.3+logstash.1" \
  --channel "logstash" \
  --package-version "1.2.3"

Deploy a release

octopus release deploy \
  --project "infrastructure-logging" \
  --version "1.2.3+logstash.1" \
  --environment "Dev" \
  --progress

--progress streams live deployment log output to your terminal instead of just firing and returning immediately.

Check deployment status

octopus deployment list --project "infrastructure-logging" --environment "Dev" --latest

5. Using the raw REST API directly (needed for sensitive variables)

The CLI wraps the REST API, but not every operation has full CLI coverage — sensitive variable read/write is one of those gaps. For those cases, hit the API directly with curl or Terraform's Octopus provider (per your earlier question).

Base pattern:

curl -s -H "X-Octopus-ApiKey: $(pass octopus/api-key)" \
  "https://your-instance.octopus.app/api/spaces/Spaces-1/projects" | jq

Note the Spaces-1 — most Octopus instances default to a single space with this ID, but confirm yours:

curl -s -H "X-Octopus-ApiKey: $(pass octopus/api-key)" \
  "https://your-instance.octopus.app/api/spaces" | jq '.Items[] | {Id, Name}'

Get a project's ID (needed for most subsequent calls):

curl -s -H "X-Octopus-ApiKey: $(pass octopus/api-key)" \
  "https://your-instance.octopus.app/api/spaces/Spaces-1/projects?partialName=infrastructure-logging" | jq '.Items[] | {Id, Name}'

Get the project's variable set:

PROJECT_ID="Projects-123"   # from above

curl -s -H "X-Octopus-ApiKey: $(pass octopus/api-key)" \
  "https://your-instance.octopus.app/api/spaces/Spaces-1/projects/$PROJECT_ID" | jq -r '.VariableSetId'

Read the variable set (sensitive values will show as null/masked — API won't return them, same limitation as the UI):

VARSET_ID="variableset-Projects-123"

curl -s -H "X-Octopus-ApiKey: $(pass octopus/api-key)" \
  "https://your-instance.octopus.app/api/spaces/Spaces-1/variables/$VARSET_ID" | jq '.Variables[] | select(.Name | contains("auth_basic_user_file"))'

Update a sensitive variable's value:

This requires a full PUT of the variable set with the updated value — you can't PATCH a single variable in place via API any more than you can via UI:

# 1. Pull the current variable set JSON
curl -s -H "X-Octopus-ApiKey: $(pass octopus/api-key)" \
  "https://your-instance.octopus.app/api/spaces/Spaces-1/variables/$VARSET_ID" > current-varset.json

# 2. Edit the target variable's value in the JSON (jq example below updates in place)
NEW_VALUE=$(cat nginx-users-dev.b64)

jq --arg newval "$NEW_VALUE" '
  (.Variables[] | select(.Name == "k8s.containers.logstash.secrets.auth_basic_user_file_base64" and .Scope.Environment[0] == "Environments-Dev-ID") | .Value) = $newval
' current-varset.json > updated-varset.json

# 3. PUT the updated set back
curl -s -X PUT \
  -H "X-Octopus-ApiKey: $(pass octopus/api-key)" \
  -H "Content-Type: application/json" \
  -d @updated-varset.json \
  "https://your-instance.octopus.app/api/spaces/Spaces-1/variables/$VARSET_ID"

Important caveats on this jq step:
- The exact Scope.Environment matching depends on your variable's actual scope structure — inspect current-varset.json first to get the right environment ID and JSON path before trusting this blindly, since scope structures vary (some variables are unscoped, some scoped to multiple environments/roles).
- Since this is a full-set PUT, any concurrent edit by someone else in the UI between your GET and PUT will get clobbered — low risk for a solo task, but worth being aware of for anything with more than one person touching variables.
- Double-check the variable's IsSensitive: true flag stays intact in your edited JSON — don't accidentally flip it while editing.

6. Wrapping this into a script (optional, ties everything together)

#!/bin/bash
set -euo pipefail

export OCTOPUS_URL="https://your-instance.octopus.app"
export OCTOPUS_API_KEY="$(pass octopus/api-key)"

PROJECT="infrastructure-logging"
ENVIRONMENT="Dev"
VERSION="1.2.3+logstash.$(date +%s | tail -c 4)"  # or your own increment logic

echo "Creating release $VERSION..."
octopus release create --project "$PROJECT" --version "$VERSION" --channel "logstash"

echo "Deploying to $ENVIRONMENT..."
octopus release deploy --project "$PROJECT" --version "$VERSION" --environment "$ENVIRONMENT" --progress

echo "Done. Verify with curl against the logging endpoint."

7. Key gotchas to remember

  • CLI and API can't read sensitive variable values back — same restriction as the UI, this is by Octopus design, not a tooling gap.
  • Full-set PUT, not per-variable PATCH — any variable update via API touches the whole variable set JSON; be careful not to clobber unrelated variables.
  • Release version snapshots variables at creation, not deploy — relevant to your earlier rollback question; the CLI/API follow the same behavior as the UI here.
  • API key expiry — if you set one, octopus commands will start failing with 401s once it lapses; worth noting the expiry date somewhere so it's not a surprise mid-task.

Want a version of the earlier "pull/backup/edit/push" command list rewritten to use the CLI/API instead of manual UI steps for the Octopus portion?