# Docker hardening blueprint (DOCKER-44-001) Use this template for core services (API, Console, Orchestrator, Task Runner, Concelier, Excititor, Policy, Notify, Export, AdvisoryAI). The reusable multi-stage scaffold lives at `ops/devops/docker/Dockerfile.hardened.template` and expects: - .NET 10 SDK/runtime images provided via offline mirror (`SDK_IMAGE` / `RUNTIME_IMAGE`). - `APP_PROJECT` path to the service csproj. - `healthcheck.sh` copied from `ops/devops/docker/` (already referenced by the template). - Optional: `APP_BINARY` (assembly name, defaults to `StellaOps.Service`) and `APP_PORT`. Copy the template next to the service and set build args in CI (per-service matrix) to avoid maintaining divergent Dockerfiles. ```Dockerfile # syntax=docker/dockerfile:1.7 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 APP_PROJECT=src/Service/Service.csproj ARG CONFIGURATION=Release ARG APP_USER=stella ARG APP_UID=10001 ARG APP_GID=10001 ARG APP_PORT=8080 FROM ${SDK_IMAGE} AS build ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1 SOURCE_DATE_EPOCH=1704067200 WORKDIR /src COPY . . RUN dotnet restore ${APP_PROJECT} --packages /src/local-nugets && \ dotnet publish ${APP_PROJECT} -c ${CONFIGURATION} -o /app/publish /p:UseAppHost=true /p:PublishTrimmed=false FROM ${RUNTIME_IMAGE} AS runtime RUN groupadd -r -g ${APP_GID} ${APP_USER} && \ useradd -r -u ${APP_UID} -g ${APP_GID} -d /var/lib/${APP_USER} ${APP_USER} WORKDIR /app COPY --from=build --chown=${APP_UID}:${APP_GID} /app/publish/ ./ COPY --chown=${APP_UID}:${APP_GID} ops/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 USER ${APP_UID}:${APP_GID} EXPOSE ${APP_PORT} HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 CMD /usr/local/bin/healthcheck.sh RUN chmod 500 /app && find /app -maxdepth 1 -type f -exec chmod 400 {} \; && find /app -maxdepth 1 -type d -exec chmod 500 {} \; ENTRYPOINT ["sh","-c","exec ./\"$APP_BINARY\""] ``` Build stage (per service) should: - Use `mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim` (or mirror) with `DOTNET_CLI_TELEMETRY_OPTOUT=1`. - Restore from `local-nugets/` (offline) and run `dotnet publish -c Release -o /app/out`. - Set `SOURCE_DATE_EPOCH` to freeze timestamps. Required checks: - No `root` user in final image. - `CAP_NET_RAW` dropped (default with non-root). - Read-only rootfs enforced at deploy time (`securityContext.readOnlyRootFilesystem: true` in Helm/Compose). - Health endpoints exposed: `/health/liveness`, `/health/readiness`, `/version`, `/metrics`. - Image SBOM generated (syft) in pipeline; attach cosign attestations (see DOCKER-44-002). Service matrix & helper: - Build args for the core services are enumerated in `ops/devops/docker/services-matrix.env` (API, Console, Orchestrator, Task Runner, Concelier, Excititor, Policy, Notify, Export, AdvisoryAI). - `ops/devops/docker/build-all.sh` reads the matrix and builds/tag images from the shared template with consistent non-root/health defaults. Override `REGISTRY` and `TAG_SUFFIX` to publish. Console (Angular) image: - Use `ops/devops/docker/Dockerfile.console` for the UI (Angular v17). It builds with `node:20-bullseye-slim`, serves via `nginxinc/nginx-unprivileged`, includes `healthcheck-frontend.sh`, and runs as non-root UID 101. Build with `docker build -f ops/devops/docker/Dockerfile.console --build-arg APP_DIR=src/Web/StellaOps.Web .`. SBOM & attestation helper (DOCKER-44-002): - Script: `ops/devops/docker/sbom_attest.sh [out-dir] [cosign-key]` - Emits SPDX (`*.spdx.json`) and CycloneDX (`*.cdx.json`) with `SOURCE_DATE_EPOCH` pinned for reproducibility. - Attaches both as cosign attestations (`--type spdx` / `--type cyclonedx`); supports keyless when `COSIGN_EXPERIMENTAL=1` or explicit PEM key. - Integrate in CI after image build/push; keep registry creds offline-friendly (use local registry mirror during air-gapped builds). Health endpoint verification (DOCKER-44-003): - Script: `ops/devops/docker/verify_health_endpoints.sh [port]` spins container, checks `/health/liveness`, `/health/readiness`, `/version`, `/metrics`, and warns if `/capabilities.merge` is not `false` (for Concelier/Excititor). - Run in CI after publishing the image; requires `docker` and `curl` (or `wget`). - Endpoint contract and ASP.NET wiring examples live in `ops/devops/docker/health-endpoints.md`; service owners should copy the snippet and ensure readiness checks cover DB/cache/bus.