Speed up scratch image builds with publish-first contexts

This commit is contained in:
master
2026-03-09 07:37:24 +02:00
parent c9686edf07
commit f218ec82ec
8 changed files with 358 additions and 38 deletions

View File

@@ -0,0 +1,44 @@
# syntax=docker/dockerfile:1.7
# Runtime-only hardened image for publish-first local builds.
ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0-noble
ARG APP_BINARY=StellaOps.Service
ARG APP_USER=stella
ARG APP_UID=10001
ARG APP_GID=10001
ARG APP_PORT=8080
FROM ${RUNTIME_IMAGE} AS runtime
ARG APP_BINARY=StellaOps.Service
ARG APP_USER=stella
ARG APP_UID=10001
ARG APP_GID=10001
ARG APP_PORT=8080
RUN groupadd -r -g ${APP_GID} ${APP_USER} && \
useradd -r -u ${APP_UID} -g ${APP_GID} -d /var/lib/${APP_USER} ${APP_USER} && \
mkdir -p /app /var/lib/${APP_USER} /var/run/${APP_USER} /tmp && \
chown -R ${APP_UID}:${APP_GID} /app /var/lib/${APP_USER} /var/run/${APP_USER} /tmp
WORKDIR /app
COPY --chown=${APP_UID}:${APP_GID} app/ ./
COPY --chown=${APP_UID}:${APP_GID} healthcheck.sh /usr/local/bin/healthcheck.sh
ENV ASPNETCORE_URLS=http://+:${APP_PORT} \
DOTNET_EnableDiagnostics=0 \
COMPlus_EnableDiagnostics=0 \
APP_BINARY=${APP_BINARY}
RUN chmod 500 /app && \
chmod +x /usr/local/bin/healthcheck.sh && \
find /app -maxdepth 1 -type f -name '*.dll' -exec chmod 400 {} \; && \
find /app -maxdepth 1 -type f -name '*.json' -exec chmod 400 {} \; && \
find /app -maxdepth 1 -type f -name '*.pdb' -exec chmod 400 {} \; && \
find /app -maxdepth 1 -type d -exec chmod 500 {} \;
USER ${APP_UID}:${APP_GID}
EXPOSE ${APP_PORT}
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD /usr/local/bin/healthcheck.sh
ENTRYPOINT ["sh","-c","exec dotnet ./\"$APP_BINARY\".dll"]

View File

@@ -1,28 +1,43 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Build hardened Docker images for all Stella Ops services using the shared template/matrix.
Build hardened Docker images for Stella Ops services using the shared matrix.
.DESCRIPTION
PowerShell port of build-all.sh. Reads services-matrix.env (pipe-delimited) and builds
each service image using Dockerfile.hardened.template (or Dockerfile.console for Angular).
The default path publishes .NET services locally and builds hardened runtime
images from small temporary contexts so scratch setup does not keep streaming
the full monorepo into Docker for every backend image.
.PARAMETER Registry
Docker image registry prefix. Default: stellaops
.PARAMETER TagSuffix
Tag suffix for built images. Default: dev
.PARAMETER SdkImage
.NET SDK base image. Default: mcr.microsoft.com/dotnet/sdk:10.0-noble
Reserved for legacy repo-context builds. Default: mcr.microsoft.com/dotnet/sdk:10.0-noble
.PARAMETER RuntimeImage
.NET runtime base image. Default: mcr.microsoft.com/dotnet/aspnet:10.0-noble
.PARAMETER Services
Optional service filter. Only listed services are rebuilt.
.PARAMETER PublishNoRestore
Skip restore during local dotnet publish when a prior solution build already ran.
.PARAMETER UseLegacyRepoContext
Fall back to repo-root docker builds for backend services.
#>
[CmdletBinding()]
param(
[string]$Registry,
[string]$TagSuffix,
[string]$SdkImage,
[string]$RuntimeImage
[string]$RuntimeImage,
[string[]]$Services,
[switch]$PublishNoRestore,
[switch]$UseLegacyRepoContext
)
$ErrorActionPreference = 'Continue'
$previousNativeErrorPreference = $null
if ($PSVersionTable.PSVersion.Major -ge 7) {
$previousNativeErrorPreference = $global:PSNativeCommandUseErrorActionPreference
$global:PSNativeCommandUseErrorActionPreference = $false
}
if ([string]::IsNullOrWhiteSpace($Registry)) {
$Registry = if ([string]::IsNullOrWhiteSpace($env:REGISTRY)) { 'stellaops' } else { $env:REGISTRY }
@@ -53,11 +68,105 @@ if (-not (Test-Path $MatrixPath)) {
exit 1
}
$runtimeDockerfile = Join-Path $Root 'devops/docker/Dockerfile.hardened.runtime'
$healthcheckScript = Join-Path $Root 'devops/docker/healthcheck.sh'
$fastContextRoot = Join-Path ([System.IO.Path]::GetTempPath()) 'stellaops-fast-images'
$serviceFilter = @{}
foreach ($serviceName in ($Services | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })) {
$serviceFilter[$serviceName.Trim().ToLowerInvariant()] = $true
}
Write-Host "Building services from $MatrixPath -> ${Registry}/<service>:${TagSuffix}" -ForegroundColor Cyan
if ($serviceFilter.Count -gt 0) {
Write-Host "Service filter: $($serviceFilter.Keys -join ', ')" -ForegroundColor Cyan
}
$succeeded = @()
$failed = @()
function Invoke-DockerBuild([string[]]$Arguments) {
& docker @Arguments 2>&1 | ForEach-Object {
$text = if ($_ -is [System.Management.Automation.ErrorRecord]) {
$_.ToString()
}
else {
"$_"
}
Write-Host $text
}
return $LASTEXITCODE
}
function Invoke-NativeCommand([string]$Command, [string[]]$Arguments) {
& $Command @Arguments 2>&1 | ForEach-Object {
$text = if ($_ -is [System.Management.Automation.ErrorRecord]) {
$_.ToString()
}
else {
"$_"
}
Write-Host $text
}
return $LASTEXITCODE
}
function Should-BuildService([string]$ServiceName) {
if ($serviceFilter.Count -eq 0) {
return $true
}
return $serviceFilter.ContainsKey($ServiceName.ToLowerInvariant())
}
function Remove-BuildContext([string]$ContextPath) {
if (Test-Path $ContextPath) {
Remove-Item -Path $ContextPath -Recurse -Force -ErrorAction SilentlyContinue
}
}
function New-PublishedBuildContext([string]$Service, [string]$Project) {
if (-not (Test-Path $runtimeDockerfile)) {
throw "Runtime Dockerfile not found: $runtimeDockerfile"
}
if (-not (Test-Path $healthcheckScript)) {
throw "Healthcheck script not found: $healthcheckScript"
}
$serviceRoot = Join-Path $fastContextRoot $Service
$appDir = Join-Path $serviceRoot 'app'
Remove-BuildContext $serviceRoot
New-Item -ItemType Directory -Path $appDir -Force | Out-Null
$publishArguments = @(
'publish',
(Join-Path $Root $Project),
'-c', 'Release',
'-o', $appDir,
'/p:UseAppHost=false',
'/p:PublishTrimmed=false',
'--nologo'
)
if ($PublishNoRestore) {
$publishArguments += '--no-restore'
}
$publishExitCode = Invoke-NativeCommand 'dotnet' $publishArguments
if ($publishExitCode -ne 0) {
Remove-BuildContext $serviceRoot
throw "dotnet publish failed for $Service"
}
Copy-Item -Path $runtimeDockerfile -Destination (Join-Path $serviceRoot 'Dockerfile') -Force
Copy-Item -Path $healthcheckScript -Destination (Join-Path $serviceRoot 'healthcheck.sh') -Force
return $serviceRoot
}
foreach ($line in Get-Content $MatrixPath) {
$line = $line.Trim()
if (-not $line -or $line.StartsWith('#')) { continue }
@@ -65,11 +174,15 @@ foreach ($line in Get-Content $MatrixPath) {
$parts = $line -split '\|'
if ($parts.Count -lt 5) { continue }
$service = $parts[0]
$service = $parts[0]
$dockerfile = $parts[1]
$project = $parts[2]
$binary = $parts[3]
$port = $parts[4]
$project = $parts[2]
$binary = $parts[3]
$port = $parts[4]
if (-not (Should-BuildService $service)) {
continue
}
$image = "${Registry}/${service}:${TagSuffix}"
$dfPath = Join-Path $Root $dockerfile
@@ -81,25 +194,57 @@ foreach ($line in Get-Content $MatrixPath) {
if ($dockerfile -like '*Dockerfile.console*') {
Write-Host "[console] $service -> $image" -ForegroundColor Yellow
docker build `
-f $dfPath $Root `
--build-arg "APP_DIR=$project" `
--build-arg "APP_PORT=$port" `
-t $image
$buildExitCode = Invoke-DockerBuild @(
'build',
'-f', $dfPath,
$Root,
'--build-arg', "APP_DIR=$project",
'--build-arg', "APP_PORT=$port",
'-t', $image
)
}
elseif (-not $UseLegacyRepoContext -and $dockerfile -like '*Dockerfile.hardened.template*') {
Write-Host "[service fast] $service -> $image" -ForegroundColor Green
$contextPath = $null
try {
$contextPath = New-PublishedBuildContext -Service $service -Project $project
$buildExitCode = Invoke-DockerBuild @(
'build',
'-f', (Join-Path $contextPath 'Dockerfile'),
$contextPath,
'--build-arg', "RUNTIME_IMAGE=$RuntimeImage",
'--build-arg', "APP_BINARY=$binary",
'--build-arg', "APP_PORT=$port",
'-t', $image
)
}
catch {
Write-Host $_.Exception.Message -ForegroundColor Red
$buildExitCode = 1
}
finally {
if ($contextPath) {
Remove-BuildContext $contextPath
}
}
}
else {
Write-Host "[service] $service -> $image" -ForegroundColor Green
docker build `
-f $dfPath $Root `
--build-arg "SDK_IMAGE=$SdkImage" `
--build-arg "RUNTIME_IMAGE=$RuntimeImage" `
--build-arg "APP_PROJECT=$project" `
--build-arg "APP_BINARY=$binary" `
--build-arg "APP_PORT=$port" `
-t $image
$buildExitCode = Invoke-DockerBuild @(
'build',
'-f', $dfPath,
$Root,
'--build-arg', "SDK_IMAGE=$SdkImage",
'--build-arg', "RUNTIME_IMAGE=$RuntimeImage",
'--build-arg', "APP_PROJECT=$project",
'--build-arg', "APP_BINARY=$binary",
'--build-arg', "APP_PORT=$port",
'-t', $image
)
}
if ($LASTEXITCODE -eq 0) {
if ($buildExitCode -eq 0) {
$succeeded += $service
}
else {
@@ -120,3 +265,7 @@ if ($failed.Count -gt 0) {
}
Write-Host 'Build complete. Remember to enforce readOnlyRootFilesystem at deploy time and run sbom_attest.sh (DOCKER-44-002).' -ForegroundColor Cyan
if ($PSVersionTable.PSVersion.Major -ge 7) {
$global:PSNativeCommandUseErrorActionPreference = $previousNativeErrorPreference
}

View File

@@ -1,6 +1,10 @@
#!/usr/bin/env bash
# Build hardened images for the core services using the shared template/matrix (DOCKER-44-001)
# Build hardened images for the core services using the shared template/matrix.
# The default path publishes .NET services locally and builds runtime-only
# images from small temporary contexts to avoid repeatedly sending the full
# monorepo into Docker.
set -uo pipefail
FAILED=()
SUCCEEDED=()
@@ -10,6 +14,12 @@ REGISTRY=${REGISTRY:-"stellaops"}
TAG_SUFFIX=${TAG_SUFFIX:-"dev"}
SDK_IMAGE=${SDK_IMAGE:-"mcr.microsoft.com/dotnet/sdk:10.0-noble"}
RUNTIME_IMAGE=${RUNTIME_IMAGE:-"mcr.microsoft.com/dotnet/aspnet:10.0-noble"}
USE_LEGACY_REPO_CONTEXT=${USE_LEGACY_REPO_CONTEXT:-"false"}
PUBLISH_NO_RESTORE=${PUBLISH_NO_RESTORE:-"false"}
SERVICES=${SERVICES:-""}
FAST_CONTEXT_ROOT=${FAST_CONTEXT_ROOT:-"${TMPDIR:-/tmp}/stellaops-fast-images"}
RUNTIME_DOCKERFILE="${ROOT}/devops/docker/Dockerfile.hardened.runtime"
HEALTHCHECK_SCRIPT="${ROOT}/devops/docker/healthcheck.sh"
if [[ ! -f "${MATRIX}" ]]; then
echo "matrix file not found: ${MATRIX}" >&2
@@ -17,9 +27,75 @@ if [[ ! -f "${MATRIX}" ]]; then
fi
echo "Building services from ${MATRIX} -> ${REGISTRY}/<service>:${TAG_SUFFIX}" >&2
if [[ -n "${SERVICES}" ]]; then
echo "Service filter: ${SERVICES}" >&2
fi
cleanup_context() {
local context_dir="${1:-}"
[[ -n "${context_dir}" && -d "${context_dir}" ]] && rm -rf "${context_dir}"
}
should_build_service() {
local service="$1"
[[ -z "${SERVICES}" ]] && return 0
IFS=',' read -r -a requested <<< "${SERVICES}"
for candidate in "${requested[@]}"; do
local trimmed="${candidate// /}"
[[ "${trimmed}" == "${service}" ]] && return 0
done
return 1
}
build_published_service_image() {
local service="$1"
local project="$2"
local binary="$3"
local port="$4"
local image="$5"
local context_dir="${FAST_CONTEXT_ROOT}/${service}"
cleanup_context "${context_dir}"
mkdir -p "${context_dir}/app"
local publish_args=(
publish "${ROOT}/${project}"
-c Release
-o "${context_dir}/app"
/p:UseAppHost=false
/p:PublishTrimmed=false
--nologo
)
if [[ "${PUBLISH_NO_RESTORE}" == "true" ]]; then
publish_args+=(--no-restore)
fi
dotnet "${publish_args[@]}" || {
cleanup_context "${context_dir}"
return 1
}
cp "${RUNTIME_DOCKERFILE}" "${context_dir}/Dockerfile"
cp "${HEALTHCHECK_SCRIPT}" "${context_dir}/healthcheck.sh"
docker build \
-f "${context_dir}/Dockerfile" "${context_dir}" \
--build-arg "RUNTIME_IMAGE=${RUNTIME_IMAGE}" \
--build-arg "APP_BINARY=${binary}" \
--build-arg "APP_PORT=${port}" \
-t "${image}"
local build_status=$?
cleanup_context "${context_dir}"
return ${build_status}
}
while IFS='|' read -r service dockerfile project binary port; do
[[ -z "${service}" || "${service}" =~ ^# ]] && continue
should_build_service "${service}" || continue
image="${REGISTRY}/${service}:${TAG_SUFFIX}"
df_path="${ROOT}/${dockerfile}"
if [[ ! -f "${df_path}" ]]; then
@@ -28,13 +104,15 @@ while IFS='|' read -r service dockerfile project binary port; do
fi
if [[ "${dockerfile}" == *"Dockerfile.console"* ]]; then
# Angular console build uses its dedicated Dockerfile
echo "[console] ${service} -> ${image}" >&2
docker build \
-f "${df_path}" "${ROOT}" \
--build-arg APP_DIR="${project}" \
--build-arg APP_PORT="${port}" \
-t "${image}"
elif [[ "${USE_LEGACY_REPO_CONTEXT}" != "true" && "${dockerfile}" == *"Dockerfile.hardened.template"* ]]; then
echo "[service fast] ${service} -> ${image}" >&2
build_published_service_image "${service}" "${project}" "${binary}" "${port}" "${image}"
else
echo "[service] ${service} -> ${image}" >&2
docker build \
@@ -53,7 +131,6 @@ while IFS='|' read -r service dockerfile project binary port; do
FAILED+=("${service}")
echo "FAILED: ${service}" >&2
fi
done < "${MATRIX}"
echo "" >&2
@@ -65,4 +142,5 @@ if [[ ${#FAILED[@]} -gt 0 ]]; then
echo "Some builds failed. Fix the issues and re-run." >&2
exit 1
fi
echo "Build complete. Remember to enforce readOnlyRootFilesystem at deploy time and run sbom_attest.sh (DOCKER-44-002)." >&2

View File

@@ -55,11 +55,19 @@ The scripts will:
3. Copy `env/stellaops.env.example` to `.env` if needed (works out of the box)
4. Start infrastructure and wait for healthy containers
5. Create or reuse the external frontdoor Docker network from `.env` (`FRONTDOOR_NETWORK`, default `stellaops_frontdoor`)
6. Build .NET solutions and Docker images
6. Build repo-owned .NET solutions, then publish backend services locally into small Docker contexts before building hardened runtime images (vendored dependency trees such as `node_modules` are excluded)
7. Launch the full platform with health checks
Open **https://stella-ops.local** when setup completes.
For targeted backend rebuilds after a scoped code change on Windows:
```powershell
.\devops\docker\build-all.ps1 -Services notify-web,orchestrator
```
This path avoids re-sending the full monorepo to Docker for every .NET service image.
## Manual path (step by step)
### 1. Environment file

View File

@@ -31,6 +31,8 @@ Setup scripts validate prerequisites, build solutions and Docker images, and lau
The scripts will check for required tools (dotnet 10.x, node 20+, npm 10+, docker, git), warn about missing hosts file entries, and copy `.env` from the example if needed. See the manual steps below for details on each stage.
On Windows and Linux, the backend image builder now publishes each selected .NET service locally and builds the hardened runtime image from a small temporary context. That avoids repeatedly streaming the whole monorepo into Docker during scratch setup.
### Quick validation + demo seed (first-run path)
```powershell
@@ -197,6 +199,18 @@ dotnet test src\Scanner\StellaOps.Scanner.sln
./scripts/build-all-solutions.sh --test
```
### Targeted backend image rebuilds
```powershell
.\devops\docker\build-all.ps1 -Services notify-web,advisory-ai-web
```
```bash
SERVICES=notify-web,advisory-ai-web ./devops/docker/build-all.sh
```
Use this after scoped backend changes instead of re-running the full image matrix.
### Module solution index
See [`docs/dev/SOLUTION_BUILD_GUIDE.md`](SOLUTION_BUILD_GUIDE.md) for the authoritative list. Current modules (39):

View File

@@ -5,7 +5,7 @@
- Treat the setup script itself as production surface: a clean repo plus docs must be enough to bootstrap the platform without manual script surgery.
- Re-run the clean setup path after the fix, then continue into Playwright-backed live verification on the rebuilt stack.
- Working directory: `devops/docker`.
- Allowed coordination edits: `scripts/setup.ps1`, `scripts/setup.sh`, `devops/compose/docker-compose.stella-ops.yml`, `docs/quickstart.md`, `docs/INSTALL_GUIDE.md`, `devops/README.md`, `devops/compose/README.md`, `src/Web/StellaOps.Web/scripts/chrome-path.js`, `src/Web/StellaOps.Web/scripts/verify-chromium.js`, `docs/implplan/SPRINT_20260309_001_Platform_scratch_setup_bootstrap_restore.md`.
- Allowed coordination edits: `scripts/setup.ps1`, `scripts/setup.sh`, `scripts/build-all-solutions.ps1`, `devops/compose/docker-compose.stella-ops.yml`, `docs/quickstart.md`, `docs/INSTALL_GUIDE.md`, `devops/README.md`, `devops/compose/README.md`, `src/Web/StellaOps.Web/scripts/chrome-path.js`, `src/Web/StellaOps.Web/scripts/verify-chromium.js`, `src/Authority/StellaOps.Authority.sln`, `src/Cli/StellaOps.Cli.sln`, `src/EvidenceLocker/StellaOps.EvidenceLocker.sln`, `src/Signals/StellaOps.Signals.sln`, `src/Tools/StellaOps.Tools.sln`, `src/Policy/StellaOps.Policy.engine.slnf`, `src/Policy/StellaOps.Policy.min.slnf`, `src/Policy/StellaOps.Policy.tests.slnf`, `src/Telemetry/StellaOps.Telemetry.Core/telemetry-tests.slnf`, `docs/implplan/SPRINT_20260309_001_Platform_scratch_setup_bootstrap_restore.md`.
- Expected evidence: clean setup invocation output, successful image-builder startup, rebuilt compose stack, and downstream Playwright verification artifacts.
## Dependencies & Concurrency
@@ -35,7 +35,7 @@ Completion criteria:
- [x] The fix preserves `REGISTRY`, `TAG_SUFFIX`, `SDK_IMAGE`, and `RUNTIME_IMAGE` overrides.
### PLATFORM-SETUP-002 - Re-run clean platform bootstrap and continue QA
Status: DONE
Status: DOING
Dependency: PLATFORM-SETUP-001
Owners: QA, Developer
Task description:
@@ -44,8 +44,21 @@ Task description:
Completion criteria:
- [x] The clean setup path is rerun from the repo script after the fix.
- [x] The stack is reachable through `https://stella-ops.local`.
- [x] The next live verification findings are captured for follow-on iterations.
- [ ] The stack is reachable through `https://stella-ops.local`.
- [ ] The next live verification findings are captured for follow-on iterations.
### PLATFORM-SETUP-003 - Repair scratch-bootstrap solution graph blockers
Status: DOING
Dependency: PLATFORM-SETUP-002
Owners: Developer
Task description:
- Fix the repo-level build graph defects exposed only by the documented full setup path after a complete Docker wipe. The fixes must preserve the canonical bootstrap workflow instead of bypassing it with `-SkipBuild`.
- Keep the repair limited to stale/corrupted solution metadata and bootstrap helper logic that prevents `scripts/setup.ps1` from completing from a clean repo state.
Completion criteria:
- [ ] `scripts/build-all-solutions.ps1` runs on this Windows host without PowerShell API compatibility errors.
- [ ] Broken solution entries discovered during the documented full setup are corrected in place.
- [ ] `scripts/setup.ps1` advances past the solution-build phase on an empty Docker state.
## Execution Log
| Date (UTC) | Update | Owner |
@@ -58,6 +71,8 @@ Completion criteria:
| 2026-03-09 | The next clean-start blocker was the external `FRONTDOOR_NETWORK` contract: a full Docker wipe removed `stellaops_frontdoor`, but neither setup script recreated it before `docker compose -f docker-compose.stella-ops.yml up -d`. Wired network creation into both setup scripts and updated the install docs to document the same manual prerequisite. | Developer |
| 2026-03-09 | Re-ran `scripts/setup.ps1 -SkipBuild -SkipImages` after the setup fixes and confirmed the stack came up cleanly on `https://stella-ops.local`; live Playwright auth also succeeded, proving the scratch bootstrap now reaches real browser-verifiable UI state. | Developer |
| 2026-03-09 | Demo seeding still exposed module migration debt (`no migration resources to consolidate` across several modules plus a duplicate `Unknowns` migration name). I did not treat that as a setup pass condition because the live frontdoor remained operable, but it remains a follow-on platform quality gap. | Developer |
| 2026-03-09 | Performed a full Docker wipe and reran the documented scratch bootstrap from zero state. Fixed additional repo bootstrap blockers exposed by the clean build matrix: stale `Authority`/`Cli`/`EvidenceLocker`/`Signals`/`Tools` solution references, `Tools` verifier project/test boundary drift, broken `Policy` and `Telemetry` solution filters, and unbounded solution discovery that recursed into frontend `node_modules` vendor samples. | Developer |
| 2026-03-09 | Investigated the next Windows bootstrap bottleneck: `devops/docker/build-all.ps1` still rebuilt every .NET service image from repo root, so Docker repeatedly transferred the monorepo into BuildKit during scratch setup. Reworked the builder to publish backend services locally into small temp contexts, kept the Angular console on its dedicated Dockerfile path, and threaded `--no-restore` through setup when the solution build already ran. | Developer |
## Decisions & Risks
- Decision: repair the documented setup path first instead of working around it with ad hoc manual builds, because scratch bootstrap is part of the product surface for this mission.
@@ -66,6 +81,8 @@ Completion criteria:
- Decision: treat browser-binary discovery as part of the scratch-bootstrap contract because a clean rebuild is not complete until Playwright can attach to a browser for live verification.
- Decision: preserve the `jobengine` compose service name and `jobengine.stella-ops.local` alias for compatibility, but map it to the canonical `orchestrator` image names emitted by the Docker build matrix so scratch setup uses the images it just produced.
- Decision: the automated setup path now owns creation of the external frontdoor Docker network because that network is part of the documented default compose topology, and a scratch bootstrap should not depend on an undocumented pre-existing Docker artifact.
- Decision: `scripts/build-all-solutions.ps1` must build only repo-owned solution surfaces under `src/`; vendored dependency trees such as frontend `node_modules` are excluded because they are not Stella bootstrap contracts and can contain native/Visual Studio samples that are invalid under `dotnet build`.
- Decision: the canonical .NET image builder now uses local `dotnet publish` plus a runtime-only Docker context by default, because repo-root `docker build` repeated monorepo context transfer for every service and made scratch setup unreasonably slow on Windows.
## Next Checkpoints
- 2026-03-09: rerun `scripts/setup.ps1 -SkipBuild` after the parser fix.

View File

@@ -400,11 +400,16 @@ function Build-Solutions {
# ─── 6. Build Docker images ────────────────────────────────────────────────
function Build-Images {
function Build-Images([switch]$PublishNoRestore) {
Write-Step 'Building Docker images'
$buildScript = Join-Path $Root 'devops/docker/build-all.ps1'
if (Test-Path $buildScript) {
& $buildScript
$buildArguments = @()
if ($PublishNoRestore) {
$buildArguments += '-PublishNoRestore'
}
& $buildScript @buildArguments
if ($LASTEXITCODE -ne 0) {
Write-Fail 'Docker image build failed.'
exit 1
@@ -600,7 +605,7 @@ if (-not $SkipBuild) {
}
if (-not $SkipImages) {
Build-Images
Build-Images -PublishNoRestore:(-not $SkipBuild)
}
Start-Platform

View File

@@ -280,13 +280,14 @@ build_solutions() {
# ─── 6. Build Docker images ────────────────────────────────────────────────
build_images() {
local publish_no_restore="${1:-false}"
step 'Building Docker images'
local script="${ROOT}/devops/docker/build-all.sh"
if [[ -x "$script" ]]; then
"$script"
PUBLISH_NO_RESTORE="$publish_no_restore" "$script"
ok 'Docker images built successfully'
elif [[ -f "$script" ]]; then
bash "$script"
PUBLISH_NO_RESTORE="$publish_no_restore" bash "$script"
ok 'Docker images built successfully'
else
warn "Build script not found at $script. Skipping image build."
@@ -377,7 +378,7 @@ check_prerequisites
check_hosts
if [[ "$IMAGES_ONLY" == "true" ]]; then
build_images
build_images false
echo ''
echo 'Done (images only).'
exit 0
@@ -398,7 +399,11 @@ if [[ "$SKIP_BUILD" != "true" ]]; then
fi
if [[ "$SKIP_IMAGES" != "true" ]]; then
build_images
if [[ "$SKIP_BUILD" == "true" ]]; then
build_images false
else
build_images true
fi
fi
start_platform