From b453cee032344cb5c31a6ac3ffeac04b7e91ce6c Mon Sep 17 00:00:00 2001
From: "Marcelo M. Maciel" <4993482+marcelo-maciel@users.noreply.github.com>
Date: Mon, 14 Sep 2026 14:45:28 -0300
Subject: [PATCH 1/3] build(deps): bump Testcontainers to 4.14.0 and SourceLink
past their advisories
`dotnet restore` fails for the whole solution under `TreatWarningsAsErrors`, on
`main` and on every open PR alike. Advisory-database drift, not a regression from
any change: a commit green on 2026-08-10 is red today with no edits.
- `Testcontainers.PostgreSql` / `.Redis` / `.Minio` 4.11.0 -> 4.14.0 (NU1903,
GHSA-q939-rpr3-3284). 4.11.0 depends on `SSH.NET` 2025.1.0; 4.14.0 already
depends on the patched 2026.0.0, so the advisory clears with no transitive pin
to remember to remove later. Same fix as #1369, so the two do not conflict.
- `Microsoft.SourceLink.GitHub` 8.0.0 -> 10.0.401 (NU1902,
GHSA-23fw-v26w-5fgq). 8.0.0 drags in `Microsoft.Build.Tasks.Git` 8.0.0 and the
8.x line has no patched release, so a transitive pin cannot fix it; the package
itself has to move. 10.0.401 depends on `Microsoft.Build.Tasks.Git` 10.0.401,
past the patched 10.0.303. Build-time only (`PrivateAssets="all"`), referenced
only where `IsPackable == true`, which is the CLI alone - and `src/Tools/**` is
excluded from the template, so the scaffold never sees it.
Verified: `dotnet restore src/FSH.Starter.slnx` exits 0 with no NU19xx, and
`dotnet build src/FSH.Starter.slnx -c Release -warnaserror` reports 0 warnings
and 0 errors.
---
src/Directory.Packages.props | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 0d38b28190..854deb9530 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -9,7 +9,8 @@
-
+
+
@@ -122,9 +123,10 @@
-
-
-
+
+
+
+
From f1478e644ecb2e5cbb83b2e8d2f7803e9832b4c1 Mon Sep 17 00:00:00 2001
From: "Marcelo M. Maciel" <4993482+marcelo-maciel@users.noreply.github.com>
Date: Mon, 14 Sep 2026 14:45:29 -0300
Subject: [PATCH 2/3] fix(infra): pull MinIO from quay.io on a pinned tag, not
Docker Hub
MinIO withdrew `minio/minio` from Docker Hub. Docker Hub's API now answers
`object not found` for the repository, and a pull fails with:
pull access denied for minio/minio, repository does not exist or may
require 'docker login'
That takes down every Testcontainers-backed integration test (the harness boots
a MinIO container per fixture, so all 724 tests in `Integration.Tests` fail at
container start), the Aspire AppHost, and the Docker Compose deployment. The
image is still published at `quay.io/minio/minio`:
- `Integration.Tests` and `Integration.Middleware.Tests` harnesses
- `AppHost.cs`, via Aspire's `WithImageRegistry` / `WithImageTag`
- `deploy/docker/docker-compose.yml` and the image table in its README
The tag is pinned to `RELEASE.2025-09-07T16-13-09Z` rather than `:latest`. quay
has not moved `:latest` since 2025-09-07, so the two resolve to the same digest
today; pinning only removes the surprise of a silent move later, and keeps the
test harness off a floating tag. Whether to track a newer release, or a different
S3-compatible image, is a separate call.
While in the README's image table: `postgres` and `redis` rows had drifted from
what compose actually ships (`postgres:18-alpine`, `valkey/valkey:9.1.0-alpine`).
Verified: `docker pull minio/minio:latest` fails with the error above;
`docker pull quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z` succeeds
(`sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e`, the
same digest `:latest` resolves to). `dotnet test Integration.Tests -c Release`
passes against the pinned image, and the Aspire manifest renders the container
as `quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z`.
---
deploy/docker/README.md | 6 +++---
deploy/docker/docker-compose.yml | 3 ++-
src/Host/FSH.Starter.AppHost/AppHost.cs | 3 +++
.../Infrastructure/MiddlewareWebApplicationFactory.cs | 3 ++-
.../Infrastructure/FshWebApplicationFactory.cs | 3 ++-
5 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/deploy/docker/README.md b/deploy/docker/README.md
index bcb1304593..0219164b7c 100644
--- a/deploy/docker/README.md
+++ b/deploy/docker/README.md
@@ -8,9 +8,9 @@ This brings up the full stack on a single host:
| `admin` | `fsh/admin:local` | `FSH_ADMIN_PORT` (default 8081) | Operator console (nginx + React) |
| `dashboard` | `fsh/dashboard:local` | `FSH_DASHBOARD_PORT` (default 8082) | Tenant dashboard (nginx + React) |
| `migrator` | `fsh/dbmigrator:local` | — | One-shot: applies EF migrations + seeds the root tenant + creates the default admin user |
-| `postgres` | `postgres:17-alpine` | (internal) | Identity, tenant catalog, module schemas |
-| `redis` | `redis:7-alpine` | (internal) | HybridCache L2, Data Protection keys, idempotency store |
-| `minio` | `minio/minio:latest` | (internal) | S3-compatible blob store for the Files module |
+| `postgres` | `postgres:18-alpine` | (internal) | Identity, tenant catalog, module schemas |
+| `redis` | `valkey/valkey:9.1.0-alpine` | (internal) | HybridCache L2, Data Protection keys, idempotency store |
+| `minio` | `quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z` | (internal) | S3-compatible blob store for the Files module |
The compose file does **not** include a reverse proxy or TLS terminator. You bring your own edge — Cloudflare Tunnel, AWS ALB, Tailscale Funnel, your existing nginx, anything that can route a TLS subdomain to a host:port on this machine.
diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml
index d43c744f5b..9457a61232 100644
--- a/deploy/docker/docker-compose.yml
+++ b/deploy/docker/docker-compose.yml
@@ -54,7 +54,8 @@ services:
# - "6379:6379"
minio:
- image: minio/minio:latest
+ # quay.io: minio/minio is gone from Docker Hub. Tag pinned; quay stopped moving :latest.
+ image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
container_name: fsh-minio
restart: unless-stopped
command: ["server", "/data", "--console-address", ":9001"]
diff --git a/src/Host/FSH.Starter.AppHost/AppHost.cs b/src/Host/FSH.Starter.AppHost/AppHost.cs
index e7a70abd05..fb3506ab42 100644
--- a/src/Host/FSH.Starter.AppHost/AppHost.cs
+++ b/src/Host/FSH.Starter.AppHost/AppHost.cs
@@ -52,7 +52,10 @@
var minioUser = builder.AddParameter("minio-user", "minioadmin");
var minioPassword = builder.AddParameter("minio-password", "minioadmin", secret: true);
+// quay.io: minio/minio is gone from Docker Hub. Tag pinned; quay stopped moving :latest.
var minio = builder.AddContainer("minio", "minio/minio")
+ .WithImageRegistry("quay.io")
+ .WithImageTag("RELEASE.2025-09-07T16-13-09Z")
.WithArgs("server", "/data", "--console-address", ":9001")
.WithHttpEndpoint(port: 9000, targetPort: 9000, name: "api")
.WithHttpEndpoint(port: 9001, targetPort: 9001, name: "console")
diff --git a/src/Tests/Integration.Middleware.Tests/Infrastructure/MiddlewareWebApplicationFactory.cs b/src/Tests/Integration.Middleware.Tests/Infrastructure/MiddlewareWebApplicationFactory.cs
index 4c2939c454..e8b7898023 100644
--- a/src/Tests/Integration.Middleware.Tests/Infrastructure/MiddlewareWebApplicationFactory.cs
+++ b/src/Tests/Integration.Middleware.Tests/Infrastructure/MiddlewareWebApplicationFactory.cs
@@ -55,7 +55,8 @@ public sealed class MiddlewareWebApplicationFactory : WebApplicationFactory, I
.WithCleanUp(true)
.Build();
- private readonly MinioContainer _minio = new MinioBuilder("minio/minio:latest")
+ // quay.io: minio/minio is gone from Docker Hub. Tag pinned; quay stopped moving :latest.
+ private readonly MinioContainer _minio = new MinioBuilder("quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z")
.WithUsername(MinioAccessKey)
.WithPassword(MinioSecretKey)
.WithAutoRemove(true)
From 4e00ef9801d122e4692c7dec3fbf6f8514aafcb6 Mon Sep 17 00:00:00 2001
From: "Marcelo M. M." <4993482+marcelo-maciel@users.noreply.github.com>
Date: Thu, 17 Sep 2026 14:08:22 -0300
Subject: [PATCH 3/3] fix(infra): pull minio/mc from quay.io too, not Docker
Hub
The compose service and the Aspire init container still pointed at
minio/mc on Docker Hub, which is as gone as minio/minio: the Hub API
answers "object not found" for the repository. docker compose up died
in minio-init, and api/migrator never started behind their
service_completed_successfully gate; Aspire failed the same way through
WaitForCompletion.
Tag pinned to the newest release on quay.io (RELEASE.2025-08-13T08-35-41Z)
for the same reason the server image is pinned: quay stopped moving :latest.
Verified by running the path the original change never exercised:
docker compose up minio minio-init -> minio-init exits 0 with
"Bucket created successfully `local/fsh`"; dotnet build of the AppHost
exits 0.
---
deploy/docker/README.md | 1 +
deploy/docker/docker-compose.yml | 3 ++-
src/Host/FSH.Starter.AppHost/AppHost.cs | 2 ++
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/deploy/docker/README.md b/deploy/docker/README.md
index 0219164b7c..3b803a877d 100644
--- a/deploy/docker/README.md
+++ b/deploy/docker/README.md
@@ -11,6 +11,7 @@ This brings up the full stack on a single host:
| `postgres` | `postgres:18-alpine` | (internal) | Identity, tenant catalog, module schemas |
| `redis` | `valkey/valkey:9.1.0-alpine` | (internal) | HybridCache L2, Data Protection keys, idempotency store |
| `minio` | `quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z` | (internal) | S3-compatible blob store for the Files module |
+| `minio-init` | `quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z` | — | One-shot: creates the `fsh` bucket the Files module writes to |
The compose file does **not** include a reverse proxy or TLS terminator. You bring your own edge — Cloudflare Tunnel, AWS ALB, Tailscale Funnel, your existing nginx, anything that can route a TLS subdomain to a host:port on this machine.
diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml
index 9457a61232..dea9a817ff 100644
--- a/deploy/docker/docker-compose.yml
+++ b/deploy/docker/docker-compose.yml
@@ -80,7 +80,8 @@ services:
# policy is set — objects are served via the API / presigned URLs, not a
# public bucket.
minio-init:
- image: minio/mc:latest
+ # quay.io: minio/mc is gone from Docker Hub too. Tag pinned; quay stopped moving :latest.
+ image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z
container_name: fsh-minio-init
restart: "no"
depends_on:
diff --git a/src/Host/FSH.Starter.AppHost/AppHost.cs b/src/Host/FSH.Starter.AppHost/AppHost.cs
index fb3506ab42..4fc689599d 100644
--- a/src/Host/FSH.Starter.AppHost/AppHost.cs
+++ b/src/Host/FSH.Starter.AppHost/AppHost.cs
@@ -76,6 +76,8 @@
""").ReplaceLineEndings("\n");
var minioInit = builder.AddContainer("minio-init", "minio/mc")
+ .WithImageRegistry("quay.io")
+ .WithImageTag("RELEASE.2025-08-13T08-35-41Z")
.WithEntrypoint("/bin/sh")
.WithArgs("-c", minioInitScript)
.WithEnvironment("MC_USER", minioUser)