From 9683a291fda7caef7bf5ff7e0b1b40e4cfd62dc4 Mon Sep 17 00:00:00 2001 From: Alan Gali Date: Sun, 12 Apr 2026 18:34:35 +0800 Subject: [PATCH 1/2] feat: add Docker Compose support for Linux and Windows environments, including deployment instructions and configuration files --- .dockerignore | 15 ++++++ .env.example | 18 +++++++ .github/workflows/release.yml | 97 +++++++++++++++++++++++++++++++++++ DOCKER.md | 70 +++++++++++++++++++++++++ Dockerfile | 25 +++++++++ README.md | 38 +++++++++++++- docker-compose.windows.yml | 22 ++++++++ docker-compose.yml | 22 ++++++++ 8 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 DOCKER.md create mode 100644 Dockerfile create mode 100644 docker-compose.windows.yml create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e546c3b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git/ +.github/ +artifacts/ +TestResults/ +runtime-state/ + +**/bin/ +**/obj/ +**/.vs/ +**/.vscode/ + +*.user +*.suo +*.tmp +*.log diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7c6e659 --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +# Copy this file to .env, or set these variables in your shell before running docker compose. + +# Host port exposed by Docker Compose. +APIHEALTHDASHBOARD_PORT=8080 + +# Local image tag used by docker compose build/up. +APIHEALTHDASHBOARD_IMAGE=apihealthdashboard:local + +# ASP.NET Core environment inside the container. +ASPNETCORE_ENVIRONMENT=Production + +# Default Linux container images used by docker-compose.yml. +APIHEALTHDASHBOARD_SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0 +APIHEALTHDASHBOARD_RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:8.0 + +# Windows container images used by docker-compose.windows.yml. +APIHEALTHDASHBOARD_WINDOWS_SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 +APIHEALTHDASHBOARD_WINDOWS_RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-ltsc2022 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1b82e0..33b50d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -140,3 +140,100 @@ jobs: files: | ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-${{ matrix.runtime }}.${{ matrix.archive_extension }} ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-${{ matrix.runtime }}.${{ matrix.archive_extension }}.sha256 + + docker-compose: + name: Docker Compose Release Bundle + needs: + - prepare + - verify + runs-on: ubuntu-latest + + env: + APIHEALTHDASHBOARD_IMAGE: apihealthdashboard:release-${{ github.run_id }} + APIHEALTHDASHBOARD_PORT: 18080 + COMPOSE_PROJECT_NAME: apihealthdashboard-release + RELEASE_VERSION: ${{ needs.prepare.outputs.release_version }} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Validate Docker Compose config + run: docker compose -f docker-compose.yml config + + - name: Validate Windows Docker Compose config + run: docker compose -f docker-compose.windows.yml config + + - name: Build Linux Docker Compose image + run: docker compose -f docker-compose.yml build + + - name: Start Docker Compose app + run: docker compose -f docker-compose.yml up -d --no-build + + - name: Probe Docker Compose app + run: | + for attempt in {1..30}; do + if curl --fail --silent --show-error "http://localhost:${APIHEALTHDASHBOARD_PORT}/" > /dev/null; then + exit 0 + fi + + sleep 2 + done + + docker compose -f docker-compose.yml logs + exit 1 + + - name: Stop Docker Compose app + if: ${{ always() }} + run: docker compose -f docker-compose.yml down --volumes --remove-orphans + + - name: Package Docker Compose source bundle + shell: pwsh + run: | + $bundlePath = "./artifacts/docker-compose-bundle" + New-Item -ItemType Directory -Path $bundlePath -Force | Out-Null + New-Item -ItemType Directory -Path "$bundlePath/src" -Force | Out-Null + + Copy-Item "Dockerfile" "$bundlePath/Dockerfile" + Copy-Item ".dockerignore" "$bundlePath/.dockerignore" + Copy-Item "docker-compose.yml" "$bundlePath/docker-compose.yml" + Copy-Item "docker-compose.windows.yml" "$bundlePath/docker-compose.windows.yml" + Copy-Item ".env.example" "$bundlePath/.env.example" + Copy-Item "DOCKER.md" "$bundlePath/DOCKER.md" + Copy-Item "src/ApiHealthDashboard" "$bundlePath/src/ApiHealthDashboard" -Recurse + + - name: Package Docker Compose zip + run: zip -r "../ApiHealthDashboard-${RELEASE_VERSION}-docker-compose.zip" . + working-directory: ./artifacts/docker-compose-bundle + + - name: Package Docker Compose tarball + run: tar -C "./artifacts/docker-compose-bundle" -czf "./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.tar.gz" . + + - name: Generate Docker Compose bundle checksums + run: | + sha256sum "./artifacts/ApiHealthDashboard-${RELEASE_VERSION}-docker-compose.zip" > "./artifacts/ApiHealthDashboard-${RELEASE_VERSION}-docker-compose.zip.sha256" + sha256sum "./artifacts/ApiHealthDashboard-${RELEASE_VERSION}-docker-compose.tar.gz" > "./artifacts/ApiHealthDashboard-${RELEASE_VERSION}-docker-compose.tar.gz.sha256" + + - name: Upload Docker Compose bundle to workflow run + uses: actions/upload-artifact@v4 + with: + name: release-docker-compose + path: | + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.zip + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.zip.sha256 + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.tar.gz + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.tar.gz.sha256 + if-no-files-found: error + + - name: Upload Docker Compose bundle to GitHub Release + if: ${{ needs.prepare.outputs.should_upload_release == 'true' }} + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.RELEASE_VERSION }} + generate_release_notes: true + fail_on_unmatched_files: true + files: | + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.zip + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.zip.sha256 + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.tar.gz + ./artifacts/ApiHealthDashboard-${{ env.RELEASE_VERSION }}-docker-compose.tar.gz.sha256 diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..0e53ca7 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,70 @@ +# Docker Compose + +The default compose file builds and runs the ASP.NET Core dashboard in a Linux .NET 8 container. The Windows compose file uses Windows .NET 8 container images for Docker engines switched to Windows containers. + +## Configure The Port + +The host port is controlled by `APIHEALTHDASHBOARD_PORT`. + +PowerShell: + +```powershell +$env:APIHEALTHDASHBOARD_PORT="9090" +docker compose up -d --build +``` + +Bash: + +```bash +APIHEALTHDASHBOARD_PORT=9090 docker compose up -d --build +``` + +You can also copy `.env.example` to `.env` and edit `APIHEALTHDASHBOARD_PORT`. + +## Run On Linux Containers + +From the repository root: + +```powershell +docker compose up -d --build +``` + +Open `http://localhost:8080`, or the port you set through `APIHEALTHDASHBOARD_PORT`. + +Stop the app: + +```powershell +docker compose down +``` + +Remove the persisted runtime-state volume as well: + +```powershell +docker compose down -v +``` + +## Run On Windows Containers + +Switch Docker to Windows containers first, then run: + +```powershell +docker compose -f .\docker-compose.windows.yml up -d --build +``` + +Use the same `APIHEALTHDASHBOARD_PORT` variable to choose the host port: + +```powershell +$env:APIHEALTHDASHBOARD_PORT="9090" +docker compose -f .\docker-compose.windows.yml up -d --build +``` + +The default Windows images target LTSC 2022. Override `APIHEALTHDASHBOARD_WINDOWS_SDK_IMAGE` and `APIHEALTHDASHBOARD_WINDOWS_RUNTIME_IMAGE` if your Docker host requires a different Windows container base. + +## Runtime State + +Compose stores runtime state in a named Docker volume: + +- Linux containers: `/app/runtime-state` +- Windows containers: `C:\app\runtime-state` + +The app continues to use `dashboard.yaml` and the `endpoints` folder copied into the image during publish. Rebuild the image after changing those files, or override `APIHEALTHDASHBOARD_BOOTSTRAP__DASHBOARDCONFIGPATH` and mount your own config path in a deployment-specific compose file. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..558fcea --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +ARG SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:8.0 +ARG RUNTIME_IMAGE=mcr.microsoft.com/dotnet/aspnet:8.0 + +FROM ${SDK_IMAGE} AS build +WORKDIR /src + +COPY ["src/ApiHealthDashboard/ApiHealthDashboard.csproj", "src/ApiHealthDashboard/"] +RUN dotnet restore "src/ApiHealthDashboard/ApiHealthDashboard.csproj" + +COPY . . +RUN dotnet publish "src/ApiHealthDashboard/ApiHealthDashboard.csproj" \ + -c Release \ + --no-restore \ + --self-contained false \ + -o /app/publish + +FROM ${RUNTIME_IMAGE} AS final +WORKDIR /app + +ENV ASPNETCORE_HTTP_PORTS=8080 +EXPOSE 8080 + +COPY --from=build /app/publish . + +ENTRYPOINT ["dotnet", "ApiHealthDashboard.dll"] diff --git a/README.md b/README.md index 2a25cd5..8e83665 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Implemented so far: - Post-v1: mini trend visuals and short status history from retained runtime samples - Post-v1: SMTP email notifications with dashboard defaults and per-endpoint recipients - Post-v1: persisted notification dispatch history in runtime state +- Post-v1: Docker Compose deployment support for Linux and Windows container environments Not implemented yet: - Backlog items tracked for post-v1 work @@ -45,6 +46,10 @@ Not implemented yet: ```text . |-- ApiHealthDashboard.sln +|-- Dockerfile +|-- docker-compose.yml +|-- docker-compose.windows.yml +|-- DOCKER.md |-- README.md |-- src/ | `-- ApiHealthDashboard/ @@ -323,6 +328,8 @@ Validated deployment behavior: - bundled CSS assets load from the published folders without relying on external CDNs - no database package or runtime dependency is required - no Node.js, npm, yarn, or frontend build tool dependency is required +- Docker Compose can build and run the app as a Linux container with a configurable host port +- a Windows-container compose file is included for Docker engines switched to Windows containers ### GitHub Actions And Dependabot @@ -339,6 +346,7 @@ Current automation behavior: - CI restores, builds in Release mode, runs tests, and uploads TRX test results - CodeQL runs for C# on pushes to `master`, `develop`, and `enhancements`, pull requests targeting those branches, and manual dispatches - Release automation verifies the solution, publishes self-contained artifacts for `win-x64` and `linux-x64`, packages them, generates checksums, and uploads them to the GitHub release +- Release automation validates Docker Compose config, builds and probes the Linux compose image, and packages a Docker Compose source bundle with checksums - Dependabot monitors both NuGet dependencies and GitHub Actions workflow dependencies on a weekly schedule ## Running The App @@ -376,6 +384,30 @@ $env:APIHEALTHDASHBOARD_BOOTSTRAP__DASHBOARDCONFIGPATH="D:\path\to\dashboard.yam dotnet run --project .\src\ApiHealthDashboard\ApiHealthDashboard.csproj ``` +## Running With Docker Compose + +Linux containers: + +```powershell +docker compose up -d --build +``` + +Use `APIHEALTHDASHBOARD_PORT` to choose the host port: + +```powershell +$env:APIHEALTHDASHBOARD_PORT="9090" +docker compose up -d --build +``` + +Windows containers: + +```powershell +$env:APIHEALTHDASHBOARD_PORT="9090" +docker compose -f .\docker-compose.windows.yml up -d --build +``` + +Docker Compose stores runtime state in a named volume. More deployment notes are in [`DOCKER.md`](DOCKER.md). + ## Running The CLI Run the full suite and print JSON to stdout: @@ -430,6 +462,8 @@ Deployment notes: - the published folder is runnable on its own with the included `dashboard.yaml` and endpoint YAML files - local UI assets under `wwwroot/adminlte` remain bundled after publish - no additional database or Node.js setup is required for the published app +- Docker Compose deployment is available through [`docker-compose.yml`](docker-compose.yml) for Linux containers and [`docker-compose.windows.yml`](docker-compose.windows.yml) for Windows containers +- the host port is controlled by `APIHEALTHDASHBOARD_PORT`, for example `$env:APIHEALTHDASHBOARD_PORT="9090"; docker compose up -d --build` ## CI/CD Automation @@ -437,13 +471,15 @@ Repository automation now includes: - CI build and test workflow for `master`, `develop`, and `enhancements` - CodeQL SAST workflow for C# - release packaging workflow for self-contained GitHub release artifacts +- Docker Compose release bundle validation and packaging - Dependabot configuration for NuGet and GitHub Actions dependencies Release flow: 1. Create and push a version tag such as `v1.0.1` 2. GitHub Actions runs the release workflow automatically 3. The workflow verifies build and tests, publishes `win-x64` and `linux-x64` self-contained outputs, packages them, and generates `.sha256` checksum files -4. The packaged artifacts are attached to the GitHub release +4. The workflow validates Docker Compose, builds and probes the Linux container path, and packages a Docker Compose source bundle as `.zip` and `.tar.gz` +5. The packaged artifacts are attached to the GitHub release Manual release flow: - Run the `Release` workflow from GitHub Actions with a `release_version` diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml new file mode 100644 index 0000000..d8c3d26 --- /dev/null +++ b/docker-compose.windows.yml @@ -0,0 +1,22 @@ +services: + apihealthdashboard: + image: ${APIHEALTHDASHBOARD_IMAGE:-apihealthdashboard:windows-local} + build: + context: . + dockerfile: Dockerfile + args: + SDK_IMAGE: ${APIHEALTHDASHBOARD_WINDOWS_SDK_IMAGE:-mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022} + RUNTIME_IMAGE: ${APIHEALTHDASHBOARD_WINDOWS_RUNTIME_IMAGE:-mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-ltsc2022} + environment: + ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Production} + ASPNETCORE_HTTP_PORTS: 8080 + APIHEALTHDASHBOARD_BOOTSTRAP__DASHBOARDCONFIGPATH: dashboard.yaml + APIHEALTHDASHBOARD_RUNTIMESTATE__DIRECTORYPATH: 'C:\app\runtime-state\endpoints' + ports: + - "${APIHEALTHDASHBOARD_PORT:-8080}:8080" + volumes: + - 'apihealthdashboard-runtime-state:C:\app\runtime-state' + restart: unless-stopped + +volumes: + apihealthdashboard-runtime-state: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2cdecfd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + apihealthdashboard: + image: ${APIHEALTHDASHBOARD_IMAGE:-apihealthdashboard:local} + build: + context: . + dockerfile: Dockerfile + args: + SDK_IMAGE: ${APIHEALTHDASHBOARD_SDK_IMAGE:-mcr.microsoft.com/dotnet/sdk:8.0} + RUNTIME_IMAGE: ${APIHEALTHDASHBOARD_RUNTIME_IMAGE:-mcr.microsoft.com/dotnet/aspnet:8.0} + environment: + ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Production} + ASPNETCORE_HTTP_PORTS: 8080 + APIHEALTHDASHBOARD_BOOTSTRAP__DASHBOARDCONFIGPATH: dashboard.yaml + APIHEALTHDASHBOARD_RUNTIMESTATE__DIRECTORYPATH: runtime-state/endpoints + ports: + - "${APIHEALTHDASHBOARD_PORT:-8080}:8080" + volumes: + - apihealthdashboard-runtime-state:/app/runtime-state + restart: unless-stopped + +volumes: + apihealthdashboard-runtime-state: From 591665fa995932797f4caad97606242fd838da42 Mon Sep 17 00:00:00 2001 From: Alan Gali Date: Sun, 12 Apr 2026 18:41:49 +0800 Subject: [PATCH 2/2] =?UTF-8?q?Updated=20README.md=20to=20align=20the=20Cu?= =?UTF-8?q?rrent=20Status=20wording=20with=20what=E2=80=99s=20actually=20i?= =?UTF-8?q?mplemented.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8e83665..8afa5bf 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ Implemented so far: - Post-v1: mini trend visuals and short status history from retained runtime samples - Post-v1: SMTP email notifications with dashboard defaults and per-endpoint recipients - Post-v1: persisted notification dispatch history in runtime state -- Post-v1: Docker Compose deployment support for Linux and Windows container environments +- Post-v1: Docker Compose deployment support with a validated Linux container flow and a Windows-container compose file -Not implemented yet: -- Backlog items tracked for post-v1 work +Future backlog: +- Optional enhancements that are not part of the current implemented surface are tracked under Future Plans. ## Solution Layout @@ -329,7 +329,7 @@ Validated deployment behavior: - no database package or runtime dependency is required - no Node.js, npm, yarn, or frontend build tool dependency is required - Docker Compose can build and run the app as a Linux container with a configurable host port -- a Windows-container compose file is included for Docker engines switched to Windows containers +- a Windows-container compose file validates and is included for Docker engines switched to Windows containers ### GitHub Actions And Dependabot @@ -462,7 +462,7 @@ Deployment notes: - the published folder is runnable on its own with the included `dashboard.yaml` and endpoint YAML files - local UI assets under `wwwroot/adminlte` remain bundled after publish - no additional database or Node.js setup is required for the published app -- Docker Compose deployment is available through [`docker-compose.yml`](docker-compose.yml) for Linux containers and [`docker-compose.windows.yml`](docker-compose.windows.yml) for Windows containers +- Docker Compose deployment is available through [`docker-compose.yml`](docker-compose.yml) for validated Linux container builds and [`docker-compose.windows.yml`](docker-compose.windows.yml) for Windows container hosts - the host port is controlled by `APIHEALTHDASHBOARD_PORT`, for example `$env:APIHEALTHDASHBOARD_PORT="9090"; docker compose up -d --build` ## CI/CD Automation @@ -572,9 +572,9 @@ Test file: ## Future Plans -These are planned enhancements after the current v1 path: -- add configurable retention controls for future persisted history files once trend capture is introduced -- optionally add per-endpoint history files once the embedded recent-sample window is no longer sufficient +These are planned enhancements beyond the current implemented surface: +- optionally add long-term persisted history files beyond the embedded recent-sample window +- optionally add retention controls for any future long-term history stores - optionally add external email API delivery in addition to the current SMTP implementation ## Notes For Ongoing Updates