#!/usr/bin/env bash # 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=() ROOT=${ROOT:-"$(git rev-parse --show-toplevel)"} 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-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 exit 1 fi echo "Building services from ${MATRIX} -> ${REGISTRY}/:${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 echo "skipping ${service}: dockerfile missing (${df_path})" >&2 continue fi if [[ "${dockerfile}" == *"Dockerfile.console"* ]]; then 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 \ -f "${df_path}" "${ROOT}" \ --build-arg SDK_IMAGE="${SDK_IMAGE}" \ --build-arg RUNTIME_IMAGE="${RUNTIME_IMAGE}" \ --build-arg APP_PROJECT="${project}" \ --build-arg APP_BINARY="${binary}" \ --build-arg APP_PORT="${port}" \ -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