Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.git/
.github/
artifacts/
TestResults/
runtime-state/

**/bin/
**/obj/
**/.vs/
**/.vscode/

*.user
*.suo
*.tmp
*.log
18 changes: 18 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
97 changes: 97 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
70 changes: 70 additions & 0 deletions DOCKER.md
Original file line number Diff line number Diff line change
@@ -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.
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
48 changes: 42 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,20 @@ 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 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

```text
.
|-- ApiHealthDashboard.sln
|-- Dockerfile
|-- docker-compose.yml
|-- docker-compose.windows.yml
|-- DOCKER.md
|-- README.md
|-- src/
| `-- ApiHealthDashboard/
Expand Down Expand Up @@ -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 validates and is included for Docker engines switched to Windows containers

### GitHub Actions And Dependabot

Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -430,20 +462,24 @@ 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 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

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`
Expand Down Expand Up @@ -536,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
Expand Down
22 changes: 22 additions & 0 deletions docker-compose.windows.yml
Original file line number Diff line number Diff line change
@@ -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:
22 changes: 22 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
Loading