Speed up scratch image builds with publish-first contexts
This commit is contained in:
44
devops/docker/Dockerfile.hardened.runtime
Normal file
44
devops/docker/Dockerfile.hardened.runtime
Normal 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"]
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user