Deploy your Sitecore Commerce XC 9.3 solution to Docker

As I mentioned in my recent blog posts, Docker was built for Sitecore Commerce. The script I use for commerce deploys locally to Docker is similar to what I used to use for a local install when using Sitecore Commerce 9.2 and 9.1.

I modified the script a tiny bit to work with my Docker instances.

Please go through our previous blog posts on Docker for Sitecore Commerce: Dockerify your Sitecore 9.3 XP development environment - SSL, CM and Identity

Docker was built for Sitecore Commerce XC 9.3

Once your commerce solution is up and running you have to perform the following tasks to push your .NET Core app:

  • Build your Sitecore.Commerce.Engine.exe (console app with IIS bindings)
  • Build all the project references of Sitecore.Commerce.Engine project (DLL’s)
  • Gather the build output
  • While making a copy for Authoring, Minions, Ops and Shops, modify the AppSettings in the src\Sitecore.Commerce.Engine\wwwroot\config.json file. We need to modify the SiteTitle and EnvironmentName
  • Remove Kestrel related attributes SslPort, SslPfxPath and SslPfxPassword
  • Push the appropriate temp folder to the appropriate Docker data folder for Authoring, Minions, Ops and Shops,
  • If Bootstrap flag is passed in, get the Token from the Identity server and Bootstrap the Commerce engine on the Ops role

NOTE: Be careful when you build the solution using dotnet publish as opposed to the Sitecore.Commerce.Engine project, we ran in to an odd issue and documented it in a blog post: Sitecore Commerce Build & Deployment Error - Could not load file or assembly System.Runtime.CompilerServices.Unsafe Version=4.0.4.1 and System.IO.Compression Version=4.2.0.0

Here are the AppSettings from the Docker\data\commerce-minions\wwwroot\wwwroot\config.json :

"AppSettings": {
    "SiteTitle": "CommerceMinions_FRIEND",
    "BootStrapFile": "Global",
    "DeploymentId": "Deployment01",
    "EnvironmentName": "BeMyFriendMinions",
    "EncryptionProtectionType": "Machine",
    "EncryptionCertificateHash": "Enter a valid certificate thumbprint for a PXF file. X-509 not supported yet",
    "EncryptionSID": "Enter a valid SID for a domain registered user",
    "EncryptionKeyStorageLocation": "c:\\Encryption-Keys\\",
    "SitecoreIdentityServerUrl": "https://identity.BeMyFriend.local",
    "AllowedOrigins": [
      "https://bizfx.BeMyFriend.local",
      "http://cd.BeMyFriend.local",
      "https://cm.BeMyFriend.local",
      "https://localhost:4200"
    ],
    "AntiForgeryEnabled": false,
    "CommerceServicesHostPostfix": "",
    "UseHttpsInKestrel": "false"
}

Here the copy of my script which I use multiple times daily and it works on my machine ;)

Param(
    [string]$identityServerHost = "identity.BeMyFriend.local",
    [switch]$Bootstrap,
    [switch]$SkipPublish,
    [string]$dockerDataRoot = "C:\code\BeMyFriend\Docker\data",
    [string[]] $engines = @("Authoring", "Minions", "Ops", "Shops"),
    [string]$CommerceOps = "commerce-ops.BeMyFriend.local",
    [string]$adminUser = "admin",
    [string]$adminPassword = "b",
    [string]$solutionFolder = "C:\code\BeMyFriend\Commerce", 
    [string]$publishFolder = (Join-Path $solutionFolder "publishcode"),
    [string]$engineProjectPath = (Join-Path $solutionFolder "src\Sitecore.Commerce.Engine\Sitecore.Commerce.Engine.csproj")
)

Function Start-CommerceEngineCompile ( [string] $basePublishPath = $(Join-Path $publishFolder "engine") ) {
    if (Test-Path $publishFolder) {
        Remove-Item -Path $publishFolder -Recurse -Force
    }
    Write-Host ("Compiling and Publishing Engine to {0}" -f $basePublishPath) -ForegroundColor Green

    dotnet publish $engineProjectPath -o $basePublishPath
}
Function  Start-CommerceEnginePrepare ( [string] $basePublishPath = $(Join-Path $publishFolder "engine") ) {
    foreach ($engine in $engines) {
        Write-Host ("Customizing configuration values for {0}" -f $engine) -ForegroundColor Green
        $engineFullName = ("commerce-{0}" -f $engine)
        $engineFullName = $engineFullName.ToLower()

        $siteTitle = $("Commerce" + $engine + "_FRIEND")
        $environmentName = ("BeMyFriend{0}" -f $engine)
        
        $enginePath = Join-Path $publishFolder $engineFullName
        Copy-Item $basePublishPath $enginePath -Recurse -Force

        $pathToJson = $(Join-Path -Path $enginePath -ChildPath "wwwroot\config.json")
        $config = Get-Content $pathToJson -Raw | ConvertFrom-Json
        $appSettings = $config.AppSettings

        $appSettings.EnvironmentName = $environmentName
        $appSettings.SiteTitle = $siteTitle

        $appSettings = $appSettings| Select-Object * -ExcludeProperty SslPort, SslPfxPath, SslPfxPassword
        $config.AppSettings = $appSettings

        $config | ConvertTo-Json -Depth 10 -Compress | set-content $pathToJson
    }
}

Function Publish-CommerceEngine {
    Write-Host ("Deploying Commerce Engine") -ForegroundColor Green

    foreach ($engine in $engines) {
        $engineFullName = ("commerce-{0}" -f $engine)
        $engineFullName = $engineFullName.ToLower()

        $enginePath = ("{0}\{1}\*" -f $publishFolder,$engineFullName)
        $engineWebRoot = ("{0}\{1}\wwwroot" -f $dockerDataRoot, $engineFullName)

        Write-Host ("Copying to {0}" -f $engineWebRoot) -ForegroundColor Green
        if (Test-Path $engineWebRoot) {
            $deleteFiles = Join-Path $engineWebRoot "\*"
            Remove-Item -Path $deleteFiles -Recurse -Force -Exclude *.gitkeep

        }
        Copy-Item -Path "$enginePath" -Destination $engineWebRoot -Container -Recurse -Force
    }
}

Function Get-IdServerToken {
    $UrlIdentityServerGetToken = ("https://{0}/connect/token" -f $identityServerHost)

    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("Content-Type", 'application/x-www-form-urlencoded')
    $headers.Add("Accept", 'application/json')

    $body = @{
        password   = "$adminPassword"
        grant_type = 'password'
        username   = ("sitecore\{0}" -f $adminUser)
        client_id  = 'postman-api'
        scope      = 'openid EngineAPI postman_api'
    }
    Write-Host "Getting Identity Token From Sitecore.IdentityServer" -ForegroundColor Green
    $response = Invoke-RestMethod $UrlIdentityServerGetToken -Method Post -Body $body -Headers $headers

    $sitecoreIdToken = "Bearer {0}" -f $response.access_token

    $global:sitecoreIdToken = $sitecoreIdToken
	Write-Host $global:sitecoreIdToken
}

Function BootStrapCommerceServices {

    $UrlCommerceShopsServicesBootstrap = ("https://{0}/commerceops/Bootstrap()" -f $CommerceOps)
    Write-Host "BootStrapping Commerce Services: $($urlCommerceShopsServicesBootstrap)" -ForegroundColor Green
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("Authorization", $global:sitecoreIdToken)
    Invoke-RestMethod $UrlCommerceShopsServicesBootstrap -TimeoutSec 1200 -Method PUT -Headers $headers 
    Write-Host "Commerce Services BootStrapping completed" -ForegroundColor Green
}

if (!($SkipPublish)) {
    Start-CommerceEngineCompile
    Start-CommerceEnginePrepare
    Publish-CommerceEngine
}
if ($Bootstrap) {
    Get-IdServerToken
}

if ($Bootstrap) {
    BootStrapCommerceServices
}

If you have any questions, please get in touch with me. @akshaysura13 on twitter or on Slack.