Ideas For Docker up.ps1 Scripts

> Because Docker can be brittle
Cover Image for Ideas For Docker up.ps1 Scripts

Introduction

When working with Docker, it's easy to get lost in the sauce. There are a lot of moving parts (despite what the marketing says), and it's easy to forget what you need to do to get things up and running. This is especially true if you're switching between projects, some of which use Docker and others that don't.

A Perfect World

Imagine a perfect world where it takes only one command to have a fully functional Sitecore instance running in Docker:

git clone https://github.com/user/sample-project.git && cd sample-project && powershell -File up.ps1

Sound too good to be true? Of course it is!

However, we can get close to this with a solid up.ps1 script. Is it a good idea? Maybe not. But it's fun to think about.

Current Example

Let's see what the latest and greatest example is from Sitecore. This repo is the current example project for XM Cloud and is used by the Sitecore MVP site: https://github.com/Sitecore/XM-Cloud-Introduction/blob/main/up.ps1

The key features and functions are:

  1. Option to run against edge, vs local, thus affecting the run of Traefik and host containers.
  2. Build images. Update the XM Cloud base image and build all Sitecore Docker containers.
  3. Index and push items.
  4. Open the site and CM in a browser.
  5. Environment variable loading and validation.

Overall, it's a relatively simple script.

Maximalist Up Script

Some have argued that up.ps1 should do as little as possible. There is something to be said about keeping things simple and letting things fail. YAGNI, the best code is no code, let it crash, etc. However, I would argue that the up.ps1 scripts I have seen don't do enough (or don't exist at all). In order to explore this concept, I will take the idea of writing a maximalist up script to the extreme, listing a series of ideas for what the script could assist with.

Qualities of a maximalist up script:

  • Automatically do most things that you would otherwise need to do manually.
  • Detect specific errors which are commonly encountered and overcome them in real time or provide helpful error messages.
  • Should be able to be run multiple times without issue.
  • Functionality is configurable via environment variables and/or script flags.

Here's the list of ideas:

  • Check that the .env file exists and is populated and run init.ps1 if needed.
  • Check if a new version of Docker is available.
  • Check that the necessary ports on the host machine aren't already in use.
  • Check that IIS is shut off.
  • Check that the Docker service / Docker Desktop are installed / running. If not, start them.
  • Check that the Dependencies are OK.
  • Check that the database server is reachable (if running DBs on host machine).
  • Check that the script has been run with the necessary permissions.
  • Check validity of custom Sitecore configs to ensure that they are well-formed and don't contain any obvious errors. If bad configs make their way into your containers, it can take a while to track down the issue.
  • Check key environment variables.
  • Show current version of node, npm, dotnet, PowerShell, Docker, docker compose, etc.
  • Show CPU, memory, disk space, etc. to identify potential runtime errors or performance bottlenecks.
  • Show the host name of the machine running Docker Desktop.
  • Restart Docker Desktop as doing so can fix all kinds of issues.
  • Run git commands (pull, etc.).
  • Build containers.
  • Start the containers.
  • Open Sitecore in a browser.
  • Open the rendering host in a browser.
  • Log in to Sitecore.
  • Sync serialized items to Sitecore.
  • Populate Solr schema.
  • Rebuild indexes.
  • Run automated smoke tests to ensure that key functionalities are working as expected.
  • Warm up the site by making requests to key pages.
  • Call ChatGPT to do... ???
  • Print a funny message similar to Sitecore PowerShell Extensions' script execution messages.
  • ASCII art.
  • Choose your own adventure game.
  • Call your parents.

Example Script

Below is an example up.ps1 script that does some of the above. I don't take credit for much of it; it's mostly a soup of other code and ideas I have found online.

# Adapted from https://github.com/ElakkuvanR/Sitecore.Headless.NextJs/blob/master/up.ps1
# Updated to use docker compose v2
[CmdletBinding(DefaultParameterSetName = "no-arguments")]
Param (
[Parameter(HelpMessage = "Run Build")]
[switch]$SkipBuild,
[string]
$DockerDesktopPath = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
)
$ErrorActionPreference = "Stop";
$PSVersion = $PSVersionTable.PSVersion.Major
if ($PSVersion -lt 5) {
Write-Error "You need at least PowerShell version 5 to run this script." -ForegroundColor Red
exit
}
else {
Write-Host "PowerShell version is $($PSVersionTable.PSVersion)." -ForegroundColor Green
}
Write-Host "Ensuring that IIS is shut off..."
iisreset /stop
Write-Host "Checking Docker..."
docker --version
# Start Docker Desktop
Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe"
$timeout = 60 # Timeout in seconds
$startTime = Get-Date
$dockerIsReady = $false
do {
Start-Sleep -Seconds 2
try {
docker info > $null
$dockerIsReady = $true
}
catch {
# Ignore errors and continue checking
}
$currentTime = Get-Date
} while (-not $dockerIsReady -and ($currentTime - $startTime).TotalSeconds -lt $timeout)
if ($dockerIsReady) {
Write-Host "Docker Desktop is running."
}
else {
Write-Host "Timed out waiting for Docker Desktop to start"
exit
}
if (Get-Service "com.docker.service" -ErrorAction SilentlyContinue) {
Start-Service "com.docker.service"
do {
$status = (Get-Service "com.docker.service").Status
Start-Sleep -Seconds 2
} while ($status -ne 'Running')
Write-Host "Docker service is running"
}
else {
Write-Host "Docker service is not running / installed"
exit
}
Write-Host "Stopping running containers prior to re-upping"
docker compose down
$envContent = Get-Content .env -Encoding UTF8
# Check whether init has been run
$envCheck = $envContent | Where-Object { $_ -imatch "^SITECORE_LICENSE=.+" }
if (-not $envCheck) {
throw "Missing SITECORE_LICENSE environment variable. Did you run 'init.ps1 -InitEnv'?"
}
$CM_HOST = ($envContent | Where-Object { $_ -imatch "^CM_HOST=.+" }) -split "=" | Select-Object -Last 1
if ([string]::IsNullOrEmpty($CM_HOST)) {
throw "Missing required environment variable. Did you run 'init.ps1 -InitEnv'?"
}
Write-Host "CM_HOST:" $CM_HOST
# Tell the user what the PC host name is
# We will later want to use the host machine host name instead of IP address for Docker, because VPNs can change IPs, but the host name remains constant
$hostMachineHostName = [System.Net.Dns]::GetHostName()
Write-Output "Host Machine Hostname: $hostMachineHostName"
if ($Build -eq $true) {
# Build all containers in the Sitecore instance, forcing a pull of latest base containers
Write-Host "Building containers..." -ForegroundColor Green
docker compose build
if ($LASTEXITCODE -ne 0) {
Write-Error "Container build failed, see errors above."
}
}
# Start the Sitecore instance
Write-Host "Starting Sitecore environment..." -ForegroundColor Green
docker compose up -d
# Wait for Traefik to expose CM route
Write-Host "Waiting for CM to become available..." -ForegroundColor Green
$startTime = Get-Date
do {
Start-Sleep -Milliseconds 100
try {
$status = Invoke-RestMethod "http://localhost:8079/api/http/routers/cm-secure@docker"
}
catch {
if ($_.Exception.Response.StatusCode.value__ -ne "404") {
throw
}
}
} while ($status.status -ne "enabled" -and $startTime.AddSeconds(15) -gt (Get-Date))
if (-not $status.status -eq "enabled") {
$status
Write-Error "Timeout waiting for Sitecore CM to become available via Traefik proxy. Check CM container logs."
}
try {
$dotnetVersion = dotnet --version
Write-Host ".NET Core version: $dotnetVersion." -ForegroundColor Green
Write-Host "Restoring Sitecore CLI..." -ForegroundColor Green
dotnet tool restore
# If this isn't run, there could be error in the log about a missing sitecore.json file
dotnet sitecore init
Write-Host "Logging into Sitecore..." -ForegroundColor Green
dotnet sitecore login --cm "https://$CM_HOST/" --auth "https://$ID_HOST/" --allow-write true
if ($LASTEXITCODE -ne 0) {
Write-Error "Unable to log into Sitecore, did the Sitecore environment start correctly? See logs above."
exit
}
}
catch {
Write-Host "An error occurred." -ForegroundColor Red
exit
}
# Write-Host "Populating Solr managed schema..." -ForegroundColor Green
# dotnet sitecore index schema-populate
# if ($LASTEXITCODE -ne 0) {
# Write-Error "Populating Solr managed schema failed, see errors above."
# }
# Write-Host "Rebuilding indexes ..." -ForegroundColor Green
# dotnet sitecore index rebuild
# if ($LASTEXITCODE -ne 0) {
# Write-Error "Rebuild indexes failed, see errors above."
# }
# Write-Host "Pushing items to Sitecore..." -ForegroundColor Green
# dotnet sitecore ser push --publish
# if ($LASTEXITCODE -ne 0) {
# Write-Error "Serialization push failed, see errors above."
# }
Write-Host "Opening site..." -ForegroundColor Green
Start-Process https://$CM_HOST/sitecore/
Start-Process "https://$CD_HOST/"
Write-Host ""
Write-Host "Use the following command to monitor CM/CD:" -ForegroundColor Green
Write-Host "docker compose logs -f [cd/cm]"
Write-Host ""

Conclusion

Personally, I have saved much time and frustration by writing a solid up.ps1 script, so I approve of this post. 😉

If you have any interesting functionality in your up.ps1 scripts, please get in touch!

Keep up.ps1ing your game,

-MG


More Posts

Cover Image for Content Editor Search Bar Not Working

Content Editor Search Bar Not Working

> Sometimes it works, sometimes not

Cover Image for Azure PaaS Cache Optimization

Azure PaaS Cache Optimization

> App Services benefit greatly from proper configuration

Cover Image for How to Exclude Paths in Sitecore Content Serialization

How to Exclude Paths in Sitecore Content Serialization

> And how to exclude unnecessary media items generated by SXA