Fix Missing JSON Rendering in Sitecore Docker Containers

Fix Missing JSON Rendering in Sitecore Docker Containers

You have Sitecore 10.4 running in Docker containers with SXA. Everything looks fine. But when you try to add a rendering and look for JSON Rendering — it’s simply not there. The rendering type doesn’t exist in your Available Renderings.

If this sounds familiar, here’s exactly what’s going on and how to fix it — step by step.


What’s Going On?

JSON Rendering is part of Sitecore Headless Services (formerly JSS Server Components). If your Docker setup was built with just SPE and SXA modules, Headless Services isn’t included. You need to add it.

The good news: you don’t need a new container or a fresh setup. You layer the Headless Services module on top of your existing CM image using your Dockerfile. That’s it.

Here’s what the layering looks like:

Base CM Image → + SPE → + SXA → + Headless Services → Your Custom CM

Important: Only the CM image needs Headless Services. Unlike some modules, Headless Services does NOT have database DACPACS or Solr configs in its asset image for Sitecore 10.4 — those DB changes are already pre-baked into the base mssql-init image. You only modify the CM Dockerfile.


Prerequisites

  • Sitecore 10.4 running in Docker with SXA (XP0 topology)
  • Docker Desktop for Windows with Windows Containers enabled
  • Sitecore license with Headless Services entitlement
  • Your project’s docker-compose files and Dockerfiles accessible

Check Your License First

Before doing anything, verify your license includes Headless Services. Save the following as Check-SitecoreHeadlessLicense.ps1 and run it:

# Check-SitecoreHeadlessLicense.ps1
# Usage: .\Check-SitecoreHeadlessLicense.ps1 -LicensePath "<your location>\license.xml"

param(
    [Parameter(Mandatory=$true)]
    [string]$LicensePath
)

if (-not (Test-Path $LicensePath)) {
    Write-Host "ERROR: License file not found at: $LicensePath" -ForegroundColor Red
    exit 1
}

$content = Get-Content $LicensePath -Raw

Write-Host ""
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host "  Sitecore Headless License Check" -ForegroundColor Cyan
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host "  License file: $LicensePath"
Write-Host ""

if ($content -match "Sitecore\.JSS") {
    Write-Host "  [PASS] Sitecore.JSS key found" -ForegroundColor Green
    Write-Host "         Your license includes Headless Services." -ForegroundColor Green
} else {
    Write-Host "  [FAIL] Sitecore.JSS key NOT found" -ForegroundColor Red
    Write-Host "         Your license does not include Headless Services." -ForegroundColor Red
    Write-Host "         Contact your Sitecore account manager to obtain" -ForegroundColor Yellow
    Write-Host "         a license with the JSS/Headless entitlement." -ForegroundColor Yellow
}

# Bonus: check other modules
Write-Host ""
Write-Host "  Other module entitlements found:" -ForegroundColor Cyan

$modules = @(
    @{ Key = "Sitecore.SPE";        Label = "PowerShell Extensions (SPE)" },
    @{ Key = "Sitecore.SXA";        Label = "Experience Accelerator (SXA)" },
    @{ Key = "Sitecore.Horizon";    Label = "Horizon (Pages)" },
    @{ Key = "Sitecore.Publishing"; Label = "Publishing Service" }
)

foreach ($mod in $modules) {
    if ($content -match [regex]::Escape($mod.Key)) {
        Write-Host "    [*] $($mod.Label)" -ForegroundColor DarkGreen
    }
}
Write-Host ""

Run it like this:

.\Check-SitecoreHeadlessLicense.ps1 -LicensePath "<your location>\license.xml"

If you see [PASS], you’re good to proceed. Sitecore 10.2+ subscription licenses include Headless Services automatically.


Files You’ll Modify

Only three files:

your-project/
  docker/build/cm/
    Dockerfile                    ← add Headless Services layer
  docker-compose.override.yml    ← add build arg
  .env                           ← add version variable

Step 1: Update Your .env File

Open your .env file and add the HEADLESS_VERSION variable alongside your existing module versions:

# Existing module versions
SPE_VERSION=6.5-1809
SXA_VERSION=10.4-1809

# Add this line
HEADLESS_VERSION=22.0-1809

Note: The tag depends on your Windows base image. Use 1809 for ltsc2019 or ltsc2022 for Server 2022. Check the Sitecore Docker Images repo on GitHub for the exact tag for your version.


Step 2: Update docker-compose.override.yml

In your docker-compose.override.yml, add HEADLESS_SERVICES_IMAGE to the cm service’s build args. The new line is highlighted with a comment:

cm:
  image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-xp0-cm:${VERSION:-latest}
  build:
    context: ./docker/build/cm
    args:
      BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}sitecore-xp0-cm:${SITECORE_VERSION}
      SPE_IMAGE: ${SITECORE_MODULE_REGISTRY}sitecore-spe-assets:${SPE_VERSION}
      SXA_IMAGE: ${SITECORE_MODULE_REGISTRY}sitecore-sxa-xp1-assets:${SXA_VERSION}
      TOOLING_IMAGE: ${SITECORE_TOOLS_REGISTRY}sitecore-docker-tools-assets:${TOOLS_VERSION}
      SOLUTION_IMAGE: ${REGISTRY}${COMPOSE_PROJECT_NAME}-solution:${VERSION:-latest}
      # --- ADD THIS LINE ---
      HEADLESS_SERVICES_IMAGE: ${SITECORE_MODULE_REGISTRY}sitecore-headless-services-xp1-assets:${HEADLESS_VERSION}

Step 3: Update the CM Dockerfile

Open docker/build/cm/Dockerfile and make two additions.

3a. Add the ARG and FROM at the top

Add HEADLESS_SERVICES_IMAGE to your ARGs, and add its FROM stage before FROM ${BASE_IMAGE}. The new lines are marked with comments:

# escape=`

ARG BASE_IMAGE
ARG SXA_IMAGE
ARG SPE_IMAGE
ARG HEADLESS_SERVICES_IMAGE        # <--- ADD THIS
ARG TOOLING_IMAGE
ARG SOLUTION_IMAGE

FROM ${SOLUTION_IMAGE} as solution
FROM ${TOOLING_IMAGE} as tooling
FROM ${SPE_IMAGE} as spe
FROM ${SXA_IMAGE} as sxa
FROM ${HEADLESS_SERVICES_IMAGE} AS headless_services    # <--- ADD THIS
FROM ${BASE_IMAGE}

🚨 Critical: FROM ${BASE_IMAGE} must always be last. The last unnamed FROM becomes your final output image. If you place another FROM after it, your image will be built on the wrong base. This is a very common mistake.

3b. Add the COPY and RUN after the SXA block

After your existing SXA module section, add the Headless Services layer:

# Add SPE module
COPY --from=spe \module\cm\content .\

# Add SXA module
COPY --from=sxa \module\cm\content .\
COPY --from=sxa \module\tools \module\tools
RUN C:\module\tools\Initialize-Content.ps1 -TargetPath .\; `
    Remove-Item -Path C:\module -Recurse -Force;

# --- ADD THIS BLOCK ---
# Headless Services module
COPY --from=headless_services C:\module\cm\content C:\inetpub\wwwroot
COPY --from=headless_services C:\module\tools C:\module\tools
RUN C:\module\tools\Initialize-Content.ps1 -TargetPath C:\inetpub\wwwroot; `
    Remove-Item -Path C:\module -Recurse -Force;

# Copy solution artifacts
COPY --from=solution \artifacts\cm\ C:\inetpub\wwwroot

Step 4: Build and Deploy

Stop your current containers:

docker compose stop

Build the updated images:

docker compose build

This pulls the Headless Services asset image and layers it onto your CM image. The first build takes a few minutes for the download.

Start the environment:

docker compose up --detach

Wait for healthy status:

docker compose ps

Wait until all containers show healthy before proceeding.


Step 5: Verify the Installation

Once the environment is up and healthy:

1. Test the Layout Service API

Browse to:

https://<your-cm-host>/sitecore/api/layout/render/jss?item=/&sc_apikey=TEST

You should get HTTP 400 with the message “API key is not valid.” This confirms the Headless Services API is responding. A 404 would mean the module isn’t installed.

2. Check Content Editor

Navigate to /sitecore/system/Modules/Layout Service in the Content Editor. If this node exists, the items have been deployed correctly.

3. Find JSON Rendering

Create a new rendering — JSON Rendering should now appear as an available rendering type. This is what we came here for!

4. Rebuild Indexes

Go to Control Panel → Populate Solr Managed Schema → Select All → Populate. Then Indexing Manager → Select All → Rebuild.


Gotchas and Lessons Learned

I ran into several issues getting this working. Here’s what I learned so you don’t have to.

🚫 Don’t add Headless Services to mssql-init

This is the biggest trap. The Headless Services asset image does not contain a C:\module\db folder. If you add a COPY instruction for that path in your mssql-init Dockerfile, the build fails with:

COPY failed: stat C:\module\db: file does not exist

For Sitecore 10.4, Headless Services database changes are pre-baked into the base mssql-init image. Only the CM Dockerfile needs the Headless Services layer.

🚫 Don’t assume all asset images have the same folders

Each module’s asset image may contain different combinations of these folders:

C:\module\
  cm\content\     ← CM webroot files (not all modules have this)
  cd\content\     ← CD webroot files (not all modules have this)
  db\             ← dacpac files (not all modules have this)
  solr\           ← Solr configs (not all modules have this)
  tools\          ← Initialize scripts (not all modules have this)

Always inspect before you COPY. Run this to see what’s actually inside an asset image:

docker run -it <asset-image-name> powershell
dir C:\module\

🚫 Watch your FROM order in Dockerfiles

The last unnamed FROM becomes your final output image. This is wrong:

FROM ${SPE_IMAGE} as spe
FROM ${BASE_IMAGE}              # NOT the final image!
FROM ${HEADLESS_IMAGE} as hds   # This becomes your final image - WRONG

This is correct:

FROM ${SPE_IMAGE} as spe
FROM ${HEADLESS_IMAGE} as hds   # Named stage, used for COPY only
FROM ${BASE_IMAGE}              # Last = your final image - CORRECT

🚫 Keep SXA image names consistent

If your docker-compose.override.yml uses sxa-xp1-assets in one service and sitecore-sxa-xp1-assets in another, you’ll pull different images or get build failures. Use the same image name in every service.


Summary

Three files changed, one docker compose build, and your JSON Rendering is available:

File Change
.env Add HEADLESS_VERSION variable
docker-compose.override.yml Add HEADLESS_SERVICES_IMAGE build arg to the cm service
docker/build/cm/Dockerfile Add ARG, FROM, COPY, and RUN for headless_services

No need to start from scratch or spin up a new container environment. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *