This commit is contained in:
master
2026-02-04 19:59:20 +02:00
parent 557feefdc3
commit 5548cf83bf
1479 changed files with 53557 additions and 40339 deletions

View File

@@ -1,26 +1,29 @@
# syntax=docker/dockerfile:1.7
# Multi-stage Angular console image with non-root runtime (DOCKER-44-001)
ARG NODE_IMAGE=node:20-bullseye-slim
ARG NODE_IMAGE=node:20-bookworm-slim
ARG NGINX_IMAGE=nginxinc/nginx-unprivileged:1.27-alpine
ARG APP_DIR=src/Web/StellaOps.Web
ARG DIST_DIR=dist
ARG APP_PORT=8080
FROM ${NODE_IMAGE} AS build
ARG APP_DIR
ARG DIST_DIR
ENV npm_config_fund=false npm_config_audit=false SOURCE_DATE_EPOCH=1704067200
WORKDIR /app
COPY ${APP_DIR}/package*.json ./
RUN npm ci --prefer-offline --no-progress --cache .npm
RUN npm install --no-progress
COPY ${APP_DIR}/ ./
RUN npm run build -- --configuration=production --output-path=${DIST_DIR}
FROM ${NGINX_IMAGE} AS runtime
ARG APP_PORT
ARG APP_PORT=8080
ARG DIST_DIR=dist
ENV APP_PORT=${APP_PORT}
USER 101
WORKDIR /
COPY --from=build /app/${DIST_DIR}/ /usr/share/nginx/html/
COPY ops/devops/docker/healthcheck-frontend.sh /usr/local/bin/healthcheck-frontend.sh
COPY devops/docker/healthcheck-frontend.sh /usr/local/bin/healthcheck-frontend.sh
RUN rm -f /etc/nginx/conf.d/default.conf && \
cat > /etc/nginx/conf.d/default.conf <<CONF
server {
@@ -29,7 +32,7 @@ server {
server_name _;
root /usr/share/nginx/html;
location / {
try_files $$uri $$uri/ /index.html;
try_files \$uri \$uri/ /index.html;
}
}
CONF

View File

@@ -2,8 +2,8 @@
# Hardened multi-stage template for StellaOps services
# Parameters are build-time ARGs so this file can be re-used across services.
ARG SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim
ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0-bookworm-slim
ARG SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:10.0-noble
ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0-noble
ARG APP_PROJECT=src/Service/Service.csproj
ARG CONFIGURATION=Release
ARG PUBLISH_DIR=/app/publish
@@ -14,17 +14,26 @@ ARG APP_GID=10001
ARG APP_PORT=8080
FROM ${SDK_IMAGE} AS build
ARG APP_PROJECT
ARG CONFIGURATION
ARG PUBLISH_DIR
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
DOTNET_NOLOGO=1 \
SOURCE_DATE_EPOCH=1704067200
WORKDIR /src
# Expect restore sources to be available offline via /.nuget/
COPY . .
RUN dotnet restore ${APP_PROJECT} --packages /.nuget/packages && \
RUN dotnet restore ${APP_PROJECT} && \
dotnet publish ${APP_PROJECT} -c ${CONFIGURATION} -o ${PUBLISH_DIR} \
/p:UseAppHost=true /p:PublishTrimmed=false
FROM ${RUNTIME_IMAGE} AS runtime
ARG APP_USER=stella
ARG APP_UID=10001
ARG APP_GID=10001
ARG APP_PORT=8080
ARG APP_BINARY=StellaOps.Service
ARG PUBLISH_DIR=/app/publish
# Create non-root user/group with stable ids for auditability
RUN groupadd -r -g ${APP_GID} ${APP_USER} && \
useradd -r -u ${APP_UID} -g ${APP_GID} -d /var/lib/${APP_USER} ${APP_USER} && \
@@ -34,23 +43,27 @@ RUN groupadd -r -g ${APP_GID} ${APP_USER} && \
WORKDIR /app
COPY --from=build --chown=${APP_UID}:${APP_GID} ${PUBLISH_DIR}/ ./
# Ship healthcheck helper; callers may override with their own script
COPY --chown=${APP_UID}:${APP_GID} ops/devops/docker/healthcheck.sh /usr/local/bin/healthcheck.sh
COPY --chown=${APP_UID}:${APP_GID} devops/docker/healthcheck.sh /usr/local/bin/healthcheck.sh
ENV ASPNETCORE_URLS=http://+:${APP_PORT} \
DOTNET_EnableDiagnostics=0 \
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 \
COMPlus_EnableDiagnostics=0 \
APP_BINARY=${APP_BINARY}
# Harden filesystem; deploys should also set readOnlyRootFilesystem true
# Keep the native AppHost binary (+x) and DLLs read-only (400)
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 {} \; && \
chmod 500 /app/${APP_BINARY} 2>/dev/null || true
USER ${APP_UID}:${APP_GID}
EXPOSE ${APP_PORT}
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD /usr/local/bin/healthcheck.sh
# Harden filesystem; deploys should also set readOnlyRootFilesystem true
RUN chmod 500 /app && \
find /app -maxdepth 1 -type f -exec chmod 400 {} \; && \
find /app -maxdepth 1 -type d -exec chmod 500 {} \;
# Use shell form so APP_BINARY env can be expanded without duplicating the template per service
ENTRYPOINT ["sh","-c","exec ./\"$APP_BINARY\""]

106
devops/docker/build-all.ps1 Normal file
View File

@@ -0,0 +1,106 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Build hardened Docker images for all Stella Ops services using the shared template/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).
.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
.PARAMETER RuntimeImage
.NET runtime base image. Default: mcr.microsoft.com/dotnet/aspnet:10.0-noble
#>
[CmdletBinding()]
param(
[string]$Registry = $env:REGISTRY ?? 'stellaops',
[string]$TagSuffix = $env:TAG_SUFFIX ?? 'dev',
[string]$SdkImage = $env:SDK_IMAGE ?? 'mcr.microsoft.com/dotnet/sdk:10.0-noble',
[string]$RuntimeImage = $env:RUNTIME_IMAGE ?? 'mcr.microsoft.com/dotnet/aspnet:10.0-noble'
)
$ErrorActionPreference = 'Continue'
$Root = git rev-parse --show-toplevel 2>$null
if (-not $Root) {
Write-Error 'Not inside a git repository.'
exit 1
}
$Root = $Root.Trim()
$MatrixPath = Join-Path $Root 'devops/docker/services-matrix.env'
if (-not (Test-Path $MatrixPath)) {
Write-Error "Matrix file not found: $MatrixPath"
exit 1
}
Write-Host "Building services from $MatrixPath -> ${Registry}/<service>:${TagSuffix}" -ForegroundColor Cyan
$succeeded = @()
$failed = @()
foreach ($line in Get-Content $MatrixPath) {
$line = $line.Trim()
if (-not $line -or $line.StartsWith('#')) { continue }
$parts = $line -split '\|'
if ($parts.Count -lt 5) { continue }
$service = $parts[0]
$dockerfile = $parts[1]
$project = $parts[2]
$binary = $parts[3]
$port = $parts[4]
$image = "${Registry}/${service}:${TagSuffix}"
$dfPath = Join-Path $Root $dockerfile
if (-not (Test-Path $dfPath)) {
Write-Warning "Skipping ${service}: dockerfile missing ($dfPath)"
continue
}
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
}
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
}
if ($LASTEXITCODE -eq 0) {
$succeeded += $service
}
else {
$failed += $service
Write-Host "FAILED: $service" -ForegroundColor Red
}
}
Write-Host ''
Write-Host '=== BUILD RESULTS ===' -ForegroundColor Cyan
Write-Host "Succeeded ($($succeeded.Count)): $($succeeded -join ', ')" -ForegroundColor Green
Write-Host "Failed ($($failed.Count)): $($failed -join ', ')" -ForegroundColor $(if ($failed.Count -gt 0) { 'Red' } else { 'Green' })
Write-Host ''
if ($failed.Count -gt 0) {
Write-Error 'Some builds failed. Fix the issues and re-run.'
exit 1
}
Write-Host 'Build complete. Remember to enforce readOnlyRootFilesystem at deploy time and run sbom_attest.sh (DOCKER-44-002).' -ForegroundColor Cyan

View File

@@ -1,13 +1,15 @@
#!/usr/bin/env bash
# Build hardened images for the core services using the shared template/matrix (DOCKER-44-001)
set -euo pipefail
set -uo pipefail
FAILED=()
SUCCEEDED=()
ROOT=${ROOT:-"$(git rev-parse --show-toplevel)"}
MATRIX=${MATRIX:-"${ROOT}/ops/devops/docker/services-matrix.env"}
MATRIX=${MATRIX:-"${ROOT}/devops/docker/services-matrix.env"}
REGISTRY=${REGISTRY:-"stellaops"}
TAG_SUFFIX=${TAG_SUFFIX:-"dev"}
SDK_IMAGE=${SDK_IMAGE:-"mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim"}
RUNTIME_IMAGE=${RUNTIME_IMAGE:-"mcr.microsoft.com/dotnet/aspnet:10.0-bookworm-slim"}
SDK_IMAGE=${SDK_IMAGE:-"mcr.microsoft.com/dotnet/sdk:10.0-noble"}
RUNTIME_IMAGE=${RUNTIME_IMAGE:-"mcr.microsoft.com/dotnet/aspnet:10.0-noble"}
if [[ ! -f "${MATRIX}" ]]; then
echo "matrix file not found: ${MATRIX}" >&2
@@ -45,6 +47,22 @@ while IFS='|' read -r service dockerfile project binary port; do
-t "${image}"
fi
if [[ $? -eq 0 ]]; then
SUCCEEDED+=("${service}")
else
FAILED+=("${service}")
echo "FAILED: ${service}" >&2
fi
done < "${MATRIX}"
echo "" >&2
echo "=== BUILD RESULTS ===" >&2
echo "Succeeded (${#SUCCEEDED[@]}): ${SUCCEEDED[*]:-none}" >&2
echo "Failed (${#FAILED[@]}): ${FAILED[*]:-none}" >&2
echo "" >&2
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

@@ -1,12 +1,112 @@
# service|dockerfile|project|binary|port
# Paths are relative to repo root; dockerfile is usually the shared hardened template.
api|ops/devops/docker/Dockerfile.hardened.template|src/VulnExplorer/StellaOps.VulnExplorer.Api/StellaOps.VulnExplorer.Api.csproj|StellaOps.VulnExplorer.Api|8080
orchestrator|ops/devops/docker/Dockerfile.hardened.template|src/Orchestrator/StellaOps.Orchestrator.WebService/StellaOps.Orchestrator.WebService.csproj|StellaOps.Orchestrator.WebService|8080
task-runner|ops/devops/docker/Dockerfile.hardened.template|src/Orchestrator/StellaOps.Orchestrator.Worker/StellaOps.Orchestrator.Worker.csproj|StellaOps.Orchestrator.Worker|8081
concelier|ops/devops/docker/Dockerfile.hardened.template|src/Concelier/StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj|StellaOps.Concelier.WebService|8080
excititor|ops/devops/docker/Dockerfile.hardened.template|src/Excititor/StellaOps.Excititor.WebService/StellaOps.Excititor.WebService.csproj|StellaOps.Excititor.WebService|8080
policy|ops/devops/docker/Dockerfile.hardened.template|src/Policy/StellaOps.Policy.Gateway/StellaOps.Policy.Gateway.csproj|StellaOps.Policy.Gateway|8084
notify|ops/devops/docker/Dockerfile.hardened.template|src/Notify/StellaOps.Notify.WebService/StellaOps.Notify.WebService.csproj|StellaOps.Notify.WebService|8080
export|ops/devops/docker/Dockerfile.hardened.template|src/ExportCenter/StellaOps.ExportCenter.WebService/StellaOps.ExportCenter.WebService.csproj|StellaOps.ExportCenter.WebService|8080
advisoryai|ops/devops/docker/Dockerfile.hardened.template|src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/StellaOps.AdvisoryAI.WebService.csproj|StellaOps.AdvisoryAI.WebService|8080
console|ops/devops/docker/Dockerfile.console|src/Web/StellaOps.Web|StellaOps.Web|8080
# Ordered by port-registry slot number. All services use port 8080 internally
# unless they have a legacy port assignment (authority=8440, signer=8441, etc.).
#
# ── Slot 0: Router Gateway ──────────────────────────────────────────────────────
router-gateway|devops/docker/Dockerfile.hardened.template|src/Router/StellaOps.Gateway.WebService/StellaOps.Gateway.WebService.csproj|StellaOps.Gateway.WebService|8080
# ── Slot 1: Platform ────────────────────────────────────────────────────────────
platform|devops/docker/Dockerfile.hardened.template|src/Platform/StellaOps.Platform.WebService/StellaOps.Platform.WebService.csproj|StellaOps.Platform.WebService|8080
# ── Slot 2: Authority ───────────────────────────────────────────────────────────
authority|devops/docker/Dockerfile.hardened.template|src/Authority/StellaOps.Authority/StellaOps.Authority/StellaOps.Authority.csproj|StellaOps.Authority|8440
# ── Slot 3: Gateway ─────────────────────────────────────────────────────────────
gateway|devops/docker/Dockerfile.hardened.template|src/Gateway/StellaOps.Gateway.WebService/StellaOps.Gateway.WebService.csproj|StellaOps.Gateway.WebService|8080
# ── Slot 4: Attestor ────────────────────────────────────────────────────────────
attestor|devops/docker/Dockerfile.hardened.template|src/Attestor/StellaOps.Attestor/StellaOps.Attestor.WebService/StellaOps.Attestor.WebService.csproj|StellaOps.Attestor.WebService|8442
# ── Slot 5: Attestor TileProxy ──────────────────────────────────────────────────
attestor-tileproxy|devops/docker/Dockerfile.hardened.template|src/Attestor/StellaOps.Attestor.TileProxy/StellaOps.Attestor.TileProxy.csproj|StellaOps.Attestor.TileProxy|8080
# ── Slot 6: Evidence Locker ─────────────────────────────────────────────────────
evidence-locker-web|devops/docker/Dockerfile.hardened.template|src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.WebService/StellaOps.EvidenceLocker.WebService.csproj|StellaOps.EvidenceLocker.WebService|8080
evidence-locker-worker|devops/docker/Dockerfile.hardened.template|src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Worker/StellaOps.EvidenceLocker.Worker.csproj|StellaOps.EvidenceLocker.Worker|8080
# ── Slot 8: Scanner ─────────────────────────────────────────────────────────────
scanner-web|devops/docker/Dockerfile.hardened.template|src/Scanner/StellaOps.Scanner.WebService/StellaOps.Scanner.WebService.csproj|StellaOps.Scanner.WebService|8444
scanner-worker|devops/docker/Dockerfile.hardened.template|src/Scanner/StellaOps.Scanner.Worker/StellaOps.Scanner.Worker.csproj|StellaOps.Scanner.Worker|8080
# ── Slot 9: Concelier ───────────────────────────────────────────────────────────
concelier|devops/docker/Dockerfile.hardened.template|src/Concelier/StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj|StellaOps.Concelier.WebService|8080
# ── Slot 10: Excititor ──────────────────────────────────────────────────────────
excititor|devops/docker/Dockerfile.hardened.template|src/Excititor/StellaOps.Excititor.WebService/StellaOps.Excititor.WebService.csproj|StellaOps.Excititor.WebService|8080
excititor-worker|devops/docker/Dockerfile.hardened.template|src/Excititor/StellaOps.Excititor.Worker/StellaOps.Excititor.Worker.csproj|StellaOps.Excititor.Worker|8080
# ── Slot 11: VexHub ─────────────────────────────────────────────────────────────
vexhub-web|devops/docker/Dockerfile.hardened.template|src/VexHub/StellaOps.VexHub.WebService/StellaOps.VexHub.WebService.csproj|StellaOps.VexHub.WebService|8080
# ── Slot 12: VexLens ────────────────────────────────────────────────────────────
vexlens-web|devops/docker/Dockerfile.hardened.template|src/VexLens/StellaOps.VexLens.WebService/StellaOps.VexLens.WebService.csproj|StellaOps.VexLens.WebService|8080
# ── Slot 13: VulnExplorer (api) ─────────────────────────────────────────────────
api|devops/docker/Dockerfile.hardened.template|src/VulnExplorer/StellaOps.VulnExplorer.Api/StellaOps.VulnExplorer.Api.csproj|StellaOps.VulnExplorer.Api|8080
# ── Slot 14: Policy Engine ──────────────────────────────────────────────────────
policy-engine|devops/docker/Dockerfile.hardened.template|src/Policy/StellaOps.Policy.Engine/StellaOps.Policy.Engine.csproj|StellaOps.Policy.Engine|8080
# ── Slot 15: Policy Gateway ─────────────────────────────────────────────────────
policy|devops/docker/Dockerfile.hardened.template|src/Policy/StellaOps.Policy.Gateway/StellaOps.Policy.Gateway.csproj|StellaOps.Policy.Gateway|8084
# ── Slot 16: RiskEngine ─────────────────────────────────────────────────────────
riskengine-web|devops/docker/Dockerfile.hardened.template|src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.WebService/StellaOps.RiskEngine.WebService.csproj|StellaOps.RiskEngine.WebService|8080
riskengine-worker|devops/docker/Dockerfile.hardened.template|src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.Worker/StellaOps.RiskEngine.Worker.csproj|StellaOps.RiskEngine.Worker|8080
# ── Slot 17: Orchestrator ───────────────────────────────────────────────────────
orchestrator|devops/docker/Dockerfile.hardened.template|src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.WebService/StellaOps.Orchestrator.WebService.csproj|StellaOps.Orchestrator.WebService|8080
orchestrator-worker|devops/docker/Dockerfile.hardened.template|src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Worker/StellaOps.Orchestrator.Worker.csproj|StellaOps.Orchestrator.Worker|8080
# ── Slot 18: TaskRunner ─────────────────────────────────────────────────────────
taskrunner-web|devops/docker/Dockerfile.hardened.template|src/TaskRunner/StellaOps.TaskRunner/StellaOps.TaskRunner.WebService/StellaOps.TaskRunner.WebService.csproj|StellaOps.TaskRunner.WebService|8080
taskrunner-worker|devops/docker/Dockerfile.hardened.template|src/TaskRunner/StellaOps.TaskRunner/StellaOps.TaskRunner.Worker/StellaOps.TaskRunner.Worker.csproj|StellaOps.TaskRunner.Worker|8080
# ── Slot 19: Scheduler ──────────────────────────────────────────────────────────
scheduler-web|devops/docker/Dockerfile.hardened.template|src/Scheduler/StellaOps.Scheduler.WebService/StellaOps.Scheduler.WebService.csproj|StellaOps.Scheduler.WebService|8080
scheduler-worker|devops/docker/Dockerfile.hardened.template|src/Scheduler/StellaOps.Scheduler.Worker.Host/StellaOps.Scheduler.Worker.Host.csproj|StellaOps.Scheduler.Worker.Host|8080
# ── Slot 20: Graph ──────────────────────────────────────────────────────────────
graph-api|devops/docker/Dockerfile.hardened.template|src/Graph/StellaOps.Graph.Api/StellaOps.Graph.Api.csproj|StellaOps.Graph.Api|8080
# ── Slot 21: Cartographer ───────────────────────────────────────────────────────
cartographer|devops/docker/Dockerfile.hardened.template|src/Cartographer/StellaOps.Cartographer/StellaOps.Cartographer.csproj|StellaOps.Cartographer|8080
# ── Slot 22: ReachGraph ─────────────────────────────────────────────────────────
reachgraph-web|devops/docker/Dockerfile.hardened.template|src/ReachGraph/StellaOps.ReachGraph.WebService/StellaOps.ReachGraph.WebService.csproj|StellaOps.ReachGraph.WebService|8080
# ── Slot 23: Timeline Indexer ───────────────────────────────────────────────────
timeline-indexer-web|devops/docker/Dockerfile.hardened.template|src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.WebService/StellaOps.TimelineIndexer.WebService.csproj|StellaOps.TimelineIndexer.WebService|8080
timeline-indexer-worker|devops/docker/Dockerfile.hardened.template|src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Worker/StellaOps.TimelineIndexer.Worker.csproj|StellaOps.TimelineIndexer.Worker|8080
# ── Slot 24: Timeline ───────────────────────────────────────────────────────────
timeline-web|devops/docker/Dockerfile.hardened.template|src/Timeline/StellaOps.Timeline.WebService/StellaOps.Timeline.WebService.csproj|StellaOps.Timeline.WebService|8080
# ── Slot 25: Findings Ledger ────────────────────────────────────────────────────
findings-ledger-web|devops/docker/Dockerfile.hardened.template|src/Findings/StellaOps.Findings.Ledger.WebService/StellaOps.Findings.Ledger.WebService.csproj|StellaOps.Findings.Ledger.WebService|8080
# ── Slot 26: Doctor ─────────────────────────────────────────────────────────────
doctor-web|devops/docker/Dockerfile.hardened.template|src/Doctor/StellaOps.Doctor.WebService/StellaOps.Doctor.WebService.csproj|StellaOps.Doctor.WebService|8080
doctor-scheduler|devops/docker/Dockerfile.hardened.template|src/Doctor/StellaOps.Doctor.Scheduler/StellaOps.Doctor.Scheduler.csproj|StellaOps.Doctor.Scheduler|8080
# ── Slot 27: OpsMemory ──────────────────────────────────────────────────────────
opsmemory-web|devops/docker/Dockerfile.hardened.template|src/OpsMemory/StellaOps.OpsMemory.WebService/StellaOps.OpsMemory.WebService.csproj|StellaOps.OpsMemory.WebService|8080
# ── Slot 28: Notifier ───────────────────────────────────────────────────────────
notifier-web|devops/docker/Dockerfile.hardened.template|src/Notifier/StellaOps.Notifier/StellaOps.Notifier.WebService/StellaOps.Notifier.WebService.csproj|StellaOps.Notifier.WebService|8080
notifier-worker|devops/docker/Dockerfile.hardened.template|src/Notifier/StellaOps.Notifier/StellaOps.Notifier.Worker/StellaOps.Notifier.Worker.csproj|StellaOps.Notifier.Worker|8080
# ── Slot 29: Notify ─────────────────────────────────────────────────────────────
notify-web|devops/docker/Dockerfile.hardened.template|src/Notify/StellaOps.Notify.WebService/StellaOps.Notify.WebService.csproj|StellaOps.Notify.WebService|8080
# ── Slot 30: Signer ─────────────────────────────────────────────────────────────
signer|devops/docker/Dockerfile.hardened.template|src/Signer/StellaOps.Signer/StellaOps.Signer.WebService/StellaOps.Signer.WebService.csproj|StellaOps.Signer.WebService|8441
# ── Slot 31: SmRemote ───────────────────────────────────────────────────────────
smremote|devops/docker/Dockerfile.hardened.template|src/SmRemote/StellaOps.SmRemote.Service/StellaOps.SmRemote.Service.csproj|StellaOps.SmRemote.Service|8080
# ── Slot 32: AirGap Controller ──────────────────────────────────────────────────
airgap-controller|devops/docker/Dockerfile.hardened.template|src/AirGap/StellaOps.AirGap.Controller/StellaOps.AirGap.Controller.csproj|StellaOps.AirGap.Controller|8080
# ── Slot 33: AirGap Time ────────────────────────────────────────────────────────
airgap-time|devops/docker/Dockerfile.hardened.template|src/AirGap/StellaOps.AirGap.Time/StellaOps.AirGap.Time.csproj|StellaOps.AirGap.Time|8080
# ── Slot 34: PacksRegistry ──────────────────────────────────────────────────────
packsregistry-web|devops/docker/Dockerfile.hardened.template|src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.WebService/StellaOps.PacksRegistry.WebService.csproj|StellaOps.PacksRegistry.WebService|8080
packsregistry-worker|devops/docker/Dockerfile.hardened.template|src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Worker/StellaOps.PacksRegistry.Worker.csproj|StellaOps.PacksRegistry.Worker|8080
# ── Slot 35: Registry Token ─────────────────────────────────────────────────────
registry-token|devops/docker/Dockerfile.hardened.template|src/Registry/StellaOps.Registry.TokenService/StellaOps.Registry.TokenService.csproj|StellaOps.Registry.TokenService|8080
# ── Slot 36: BinaryIndex ────────────────────────────────────────────────────────
binaryindex-web|devops/docker/Dockerfile.hardened.template|src/BinaryIndex/StellaOps.BinaryIndex.WebService/StellaOps.BinaryIndex.WebService.csproj|StellaOps.BinaryIndex.WebService|8080
# ── Slot 37: IssuerDirectory ────────────────────────────────────────────────────
issuer-directory-web|devops/docker/Dockerfile.hardened.template|src/IssuerDirectory/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.WebService/StellaOps.IssuerDirectory.WebService.csproj|StellaOps.IssuerDirectory.WebService|8080
# ── Slot 38: Symbols ────────────────────────────────────────────────────────────
symbols|devops/docker/Dockerfile.hardened.template|src/Symbols/StellaOps.Symbols.Server/StellaOps.Symbols.Server.csproj|StellaOps.Symbols.Server|8080
# ── Slot 39: SbomService ────────────────────────────────────────────────────────
sbomservice|devops/docker/Dockerfile.hardened.template|src/SbomService/StellaOps.SbomService/StellaOps.SbomService.csproj|StellaOps.SbomService|8080
# ── Slot 40: ExportCenter ───────────────────────────────────────────────────────
export|devops/docker/Dockerfile.hardened.template|src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.WebService/StellaOps.ExportCenter.WebService.csproj|StellaOps.ExportCenter.WebService|8080
export-worker|devops/docker/Dockerfile.hardened.template|src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Worker/StellaOps.ExportCenter.Worker.csproj|StellaOps.ExportCenter.Worker|8080
# ── Slot 41: Replay ─────────────────────────────────────────────────────────────
replay-web|devops/docker/Dockerfile.hardened.template|src/Replay/StellaOps.Replay.WebService/StellaOps.Replay.WebService.csproj|StellaOps.Replay.WebService|8080
# ── Slot 42: Integrations ───────────────────────────────────────────────────────
integrations-web|devops/docker/Dockerfile.hardened.template|src/Integrations/StellaOps.Integrations.WebService/StellaOps.Integrations.WebService.csproj|StellaOps.Integrations.WebService|8080
# ── Slot 43: Zastava ────────────────────────────────────────────────────────────
zastava-webhook|devops/docker/Dockerfile.hardened.template|src/Zastava/StellaOps.Zastava.Webhook/StellaOps.Zastava.Webhook.csproj|StellaOps.Zastava.Webhook|8080
# ── Slot 44: Signals ────────────────────────────────────────────────────────────
signals|devops/docker/Dockerfile.hardened.template|src/Signals/StellaOps.Signals/StellaOps.Signals.csproj|StellaOps.Signals|8080
# ── Slot 45: AdvisoryAI ─────────────────────────────────────────────────────────
advisory-ai-web|devops/docker/Dockerfile.hardened.template|src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/StellaOps.AdvisoryAI.WebService.csproj|StellaOps.AdvisoryAI.WebService|8080
advisory-ai-worker|devops/docker/Dockerfile.hardened.template|src/AdvisoryAI/StellaOps.AdvisoryAI.Worker/StellaOps.AdvisoryAI.Worker.csproj|StellaOps.AdvisoryAI.Worker|8080
# ── Slot 46: Unknowns ───────────────────────────────────────────────────────────
unknowns-web|devops/docker/Dockerfile.hardened.template|src/Unknowns/StellaOps.Unknowns.WebService/StellaOps.Unknowns.WebService.csproj|StellaOps.Unknowns.WebService|8080
# ── Console (Angular frontend) ──────────────────────────────────────────────────
console|devops/docker/Dockerfile.console|src/Web/StellaOps.Web|StellaOps.Web|8080