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.

up.ps1

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

Cover Image for NextJS/JSS Edit Frames Before JSS v21.1.0

NextJS/JSS Edit Frames Before JSS v21.1.0

> It is possible. We have the technology.

Cover Image for NextJS: Short URL for Viewing Layout Service Response

NextJS: Short URL for Viewing Layout Service Response

> Because the default URL is 2long4me

Cover Image for NextJS: Access has been blocked by CORS policy

NextJS: Access has been blocked by CORS policy

> CORS is almost as much of a nuisance as GDPR popups

Cover Image for Tips for Applying Cumulative Sitecore XM/XP Patches and Hotfixes

Tips for Applying Cumulative Sitecore XM/XP Patches and Hotfixes

> It's probably time to overhaul your processes

Cover Image for JSS: Reducing Bloat in Multilist Field Serialization

JSS: Reducing Bloat in Multilist Field Serialization

> Because: performance, security, and error-avoidance

Cover Image for Tips for New Sitecore Developers

Tips for New Sitecore Developers

> Because learning Sitecore can be hard

Cover Image for Super Fast Project Builds with Visual Studio Publish

Super Fast Project Builds with Visual Studio Publish

> For when solution builds take too long

Cover Image for Tips for Forms Implementations

Tips for Forms Implementations

> And other pro tips

Cover Image for Add TypeScript Type Checks to RouteData fields

Add TypeScript Type Checks to RouteData fields

> Inspired by error: Conversion of type may be a mistake because neither type sufficiently overlaps with the other.

Cover Image for Troubleshooting 502 Responses in Azure App Services

Troubleshooting 502 Responses in Azure App Services

> App Services don't support all libraries

Cover Image for Hello World

Hello World

> Welcome to the show

Cover Image for Year in Review: 2022

Year in Review: 2022

> Full steam ahead

Cover Image for Symposium 2022 Reflections

Symposium 2022 Reflections

> Sitecore is making big changes

Cover Image for How to Run Old Versions of Solr in a Docker Container

How to Run Old Versions of Solr in a Docker Container

> Please don't make me install another version of Solr on my local...

Cover Image for Sitecore Symposium 2022

Sitecore Symposium 2022

> What I'm Watching 👀

Cover Image for Early Returns in React Components

Early Returns in React Components

> When and how should you return early in a React component?

Cover Image for SPE Script Performance & Troubleshooting

SPE Script Performance & Troubleshooting

> Script never ends or runs too slow? Get in here.

Cover Image for Content Editor Search Bar Not Working

Content Editor Search Bar Not Working

> Sometimes it works, sometimes not

Cover Image for Don't Ignore the HttpRequestValidationException

Don't Ignore the HttpRequestValidationException

> Doing so could be... potentially dangerous

Cover Image for JSS + TypeScript Sitecore Project Tips

JSS + TypeScript Sitecore Project Tips

> New tech, new challenges

Cover Image for Script: Boost SIF Certificate Expiry Days

Script: Boost SIF Certificate Expiry Days

> One simple script that definitely won't delete your system32 folder

Cover Image for NextJS: Unable to Verify the First Certificate

NextJS: Unable to Verify the First Certificate

> UNABLE_TO_VERIFY_LEAF_SIGNATURE

Cover Image for On Sitecore Stack Exchange (SSE)

On Sitecore Stack Exchange (SSE)

> What I've learned, what I see, what I want to see

Cover Image for Azure PaaS Cache Optimization

Azure PaaS Cache Optimization

> App Services benefit greatly from proper configuration

Cover Image for On Mentorship and Community Contributions

On Mentorship and Community Contributions

> Reflections and what I learned as an MVP mentor