Migrating tenants to a new Octopus Deploy target

We have 50 tenants that share a target. We are retiring this target and need to migrate the tenants to the new target. What’s the best way to do this?

Starting state

We have two targets, “Server A” and “Server B”. We have 50 tenants that are currently hosted on Server A. Server A is being retired and we want to migrate our tenants to Server B.


First, we need to see how the current target is configured. If all of the tenants are attached individually like in the screenshot below (see the “50 tenants” chip next to Server A), we’ll need to replace that with a tenant tag first.

Let’s create a new tenant tag set. We’ll call it Hosting and create a tag for Server A and Server B.’

Now let’s update our tenants to have the Server A tag. Doing this manually isn’t bad for a few tenants, but for 50, we’ll want to use a script.

This following script is an example starting point. You may need to add extra logic or filtering for your case, and you will want to test any scripts prior to running against your Octopus instance.

The script will start by adding the Server A tag to each tenant associated with the Server A target. Then it will remove the direct associations between the target and the tenants. Finally, it adds the appropriate Hosting tag to each target.

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$octopusBaseUrl = "Your Octopus Url"
$apiKey = "Your Api Key"
$spaceId = "Your Space Id"
$targetA = "Your Target A Id"
$targetB = "Your Target B Id"

$ErrorActionPreference = 'Stop'

function Invoke-OctopusApi {
        [Parameter(Position = 0, Mandatory)]$Uri,
        [ValidateSet("Get", "Post", "Put", "Delete")]$Method = 'Get',

    $uriParts = @($octopusBaseUrl, $Uri.TrimStart('/'))
    $uri = ($uriParts -join '/')

    $requestParameters = @{
        Uri                   = $uri
        Method          = $Method
        Headers         = @{ "X-Octopus-ApiKey" = $apiKey }
        UseBasicParsing = $true

    if ($null -ne $Body) { $requestParameters.Add('Body', ($Body | ConvertTo-Json -Depth 10)) }

    return Invoke-WebRequest @requestParameters | % Content | ConvertFrom-Json

$apiPrefix = "api/"

$target = Invoke-OctopusApi "$apiPrefix/machines/$targetA" -Method Get

$target.TenantIds | ForEach-Object {
    $tenant = Invoke-OctopusApi "$apiPrefix/tenants/$($_)" -Method Get
    $tenant.TenantTags += "Hosting/Server A"
    Invoke-OctopusApi "$apiPrefix/tenants/$($_)" -Method Put -Body $tenant | Out-Null

$target.TenantIds = @()
$target.TenantTags += "Hosting/Server A"

Invoke-OctopusApi "$apiPrefix/machines/$targetA" -Method Put -Body $target

$target = Invoke-OctopusApi "$apiPrefix/machines/$targetB" -Method Get
$target.TenantTags += "Hosting/Server B"

Invoke-OctopusApi "$apiPrefix/machines/$targetB" -Method Put -Body $target

Checking the infrastructure screen shows that our targets are configured correctly.

Now we’re ready for the migration!

Once the new server is ready for testing, remove the Server A tag from one of the tenants and replace it with the Server B tag. When you deploy a release for that tenant, it will go to the new server instead of the old one.

Once you’re comfortable with the new server, you can update some more tenants (or all of them) to use the Server B tag. I recommend doing this with a script similar to the first one we used. And :tada: your tenants will deploy to the new server.