From a020697549b61ca44b9a7d8c6b7e8925b4ba4eec Mon Sep 17 00:00:00 2001 From: Dave Thomas <7sharp9@mail.com> Date: Tue, 4 Aug 2026 18:10:53 +0100 Subject: [PATCH 1/2] feat(docker): auto-build image before DockerRun Extract Docker build logic into a reusable `buildDockerImage` function. Add `dockerImageExistsLocally` check to the `DockerRun` target so it automatically builds the image if not present locally, instead of failing with a missing image error. --- Build.fs | 74 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/Build.fs b/Build.fs index 967715f0..3597dbfe 100644 --- a/Build.fs +++ b/Build.fs @@ -293,36 +293,46 @@ let dockerImage = | image -> image -Target.create - "DockerBuild" - (fun _ -> - let version = - System.Xml.Linq.XDocument.Load("Directory.Build.props").Descendants(System.Xml.Linq.XName.Get "Version") - |> Seq.map (fun e -> e.Value) - |> Seq.tryHead - |> Option.defaultWith (fun () -> failwith "Directory.Build.props: element not found") - - // Cross-build for a different target platform, e.g. amd64 from Apple - // Silicon, via: DOCKER_PLATFORM=linux/amd64 dotnet run DockerBuild - let platformArgs = - match System.Environment.GetEnvironmentVariable "DOCKER_PLATFORM" with - | null - | "" -> [] - | platform -> [ "--platform"; platform ] - - run - docker - ([ "build" ] - @ platformArgs - @ [ - "--build-arg" - $"APP_VERSION={version}" - "-t" - dockerImage - "." - ]) - "." - ) +let buildDockerImage () = + let version = + System.Xml.Linq.XDocument.Load("Directory.Build.props").Descendants(System.Xml.Linq.XName.Get "Version") + |> Seq.tryHead + |> Option.map (fun e -> e.Value.Trim()) + |> Option.filter (System.String.IsNullOrWhiteSpace >> not) + |> Option.defaultWith (fun () -> failwith "Directory.Build.props: element is missing or empty.") + + // Cross-build for a different target platform, e.g. amd64 from Apple + // Silicon, via: DOCKER_PLATFORM=linux/amd64 dotnet run DockerBuild + let platformArgs = + match System.Environment.GetEnvironmentVariable "DOCKER_PLATFORM" with + | null + | "" -> [] + | platform -> [ "--platform"; platform ] + + run + docker + ([ "build" ] + @ platformArgs + @ [ + "--build-arg" + $"APP_VERSION={version}" + "-t" + dockerImage + "." + ]) + "." + + +// `docker` wraps CreateProcess with addOnExited, which raises on any non-zero exit. +// This is unusable here since "no such image" is an expected outcome we need to branch on, not a build failure. +let dockerImageExistsLocally () = + CreateProcess.fromRawCommand "docker" [ "image"; "inspect"; dockerImage ] + |> CreateProcess.redirectOutput + |> Proc.run + |> fun result -> result.ExitCode = 0 + + +Target.create "DockerBuild" (fun _ -> buildDockerImage ()) Target.create @@ -335,6 +345,10 @@ Target.create requireEnvVar "GENPRES_URL_ID" |> ignore requireEnvVar "GENPRES_PASSWORD" |> ignore + if dockerImageExistsLocally () |> not then + Trace.traceImportant $"Docker image '{dockerImage}' not found locally, building it..." + buildDockerImage () + run docker [ From ee47750c63fd3888f637a62d9fcd4e123c6f3825 Mon Sep 17 00:00:00 2001 From: Dave Thomas <7sharp9@mail.com> Date: Tue, 4 Aug 2026 18:38:49 +0100 Subject: [PATCH 2/2] build(logging): surface docker inspect errors Update `dockerImageExistsLocally` to treat only "No such image" as a missing-image case. Any other `docker image inspect` failure (for example daemon/context/permission issues) now fails fast with the Docker error output instead of being misclassified as a first-build scenario. --- Build.fs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Build.fs b/Build.fs index 3597dbfe..ff49a572 100644 --- a/Build.fs +++ b/Build.fs @@ -326,10 +326,20 @@ let buildDockerImage () = // `docker` wraps CreateProcess with addOnExited, which raises on any non-zero exit. // This is unusable here since "no such image" is an expected outcome we need to branch on, not a build failure. let dockerImageExistsLocally () = - CreateProcess.fromRawCommand "docker" [ "image"; "inspect"; dockerImage ] - |> CreateProcess.redirectOutput - |> Proc.run - |> fun result -> result.ExitCode = 0 + let result = + CreateProcess.fromRawCommand "docker" [ "image"; "inspect"; dockerImage ] + |> CreateProcess.redirectOutput + |> Proc.run + + if result.ExitCode = 0 then + true + // Only "no such image" means missing. Any other failure (daemon down, permission + // denied, wrong context) is a real Docker problem, not something a build can fix, + // so surface it immediately instead of letting it masquerade as a routine first build. + elif result.Result.Error.Contains "No such image" then + false + else + failwithf "docker image inspect failed:\n%s" result.Result.Error Target.create "DockerBuild" (fun _ -> buildDockerImage ())