Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 4 additions & 4 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ dotnet test tests/Informedica.GenUNITS.Tests/

### Docker

The proprietary `GENPRES_URL_ID` is **not** baked into the image any more. Inject it (and `GENPRES_PASSWORD` for admin operations) at container runtime, ideally via a Docker / Kubernetes secret.
`GENPRES_URL_ID` is required at server startup, in both demo and production mode — not just for admin operations. The proprietary production value is **not** baked into the image; inject it (and `GENPRES_PASSWORD` for admin operations) at container runtime, ideally via a Docker / Kubernetes secret. For local testing without production credentials, use the public demo sheet ID documented in `.env.example`.

- `docker build -t halcwb/genpres .`
- `docker run -it -p 8080:8085 -e GENPRES_URL_ID="your_url_id" -e GENPRES_PASSWORD="your_admin_password" halcwb/genpres`
- `dotnet run DockerRun` - Run pre-built Docker image
- `dotnet run DockerBuild` - Build the image, labelled with the version from the root `Directory.Build.props`. Override the image name with `DOCKER_IMAGE` (default `halcwb/genpres`), cross-build a different platform with `DOCKER_PLATFORM`.
- `dotnet run DockerRun` - Run the built image, reading `GENPRES_URL_ID`/`GENPRES_PASSWORD` from the current environment (source `.env` first) and failing fast if either is unset.
- Equivalent manual commands: `docker build -t halcwb/genpres .` / `docker run -it -p 8080:8085 -e GENPRES_URL_ID="your_url_id" -e GENPRES_PASSWORD="your_admin_password" halcwb/genpres`

## Key Code Locations

Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ dotnet test tests/Informedica.GenUNITS.Tests/

### Docker

The proprietary `GENPRES_URL_ID` is **not** baked into the image any more. Inject it (and `GENPRES_PASSWORD` for admin operations) at container runtime, ideally via a Docker / Kubernetes secret.
`GENPRES_URL_ID` is required at server startup, in both demo and production mode — not just for admin operations. The proprietary production value is **not** baked into the image; inject it (and `GENPRES_PASSWORD` for admin operations) at container runtime, ideally via a Docker / Kubernetes secret. For local testing without production credentials, use the public demo sheet ID documented in `.env.example`.
Comment thread
7sharp9 marked this conversation as resolved.
Outdated

- `docker build -t halcwb/genpres .`
- `docker run -it -p 8080:8085 -e GENPRES_URL_ID="your_url_id" -e GENPRES_PASSWORD="your_admin_password" halcwb/genpres`
- `dotnet run DockerRun` - Run pre-built Docker image
- `dotnet run DockerBuild` - Build the image, labelled with the version from the root `Directory.Build.props`. Override the image name with `DOCKER_IMAGE` (default `halcwb/genpres`), cross-build a different platform with `DOCKER_PLATFORM`.
- `dotnet run DockerRun` - Run the built image, reading `GENPRES_URL_ID`/`GENPRES_PASSWORD` from the current environment (source `.env` first) and failing fast if either is unset.
- Equivalent manual commands: `docker build -t halcwb/genpres .` / `docker run -it -p 8080:8085 -e GENPRES_URL_ID="your_url_id" -e GENPRES_PASSWORD="your_admin_password" halcwb/genpres`

## Key Code Locations

Expand Down
75 changes: 74 additions & 1 deletion Build.fs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,80 @@ Target.create
)


Target.create "DockerRun" (fun _ -> run docker [ "run"; "-it"; "p"; "8080:8085"; "halcwb/genpres" ] ".")
let requireEnvVar name =
match System.Environment.GetEnvironmentVariable name with
| null
| "" -> failwithf "%s is not set. Load it from .env first (see DEVELOPMENT.md)." name
| v -> v
Comment thread
7sharp9 marked this conversation as resolved.


// Override via DOCKER_IMAGE if you're pushing to your own registry/namespace
// rather than the project's `halcwb/genpres`.
let dockerImage =
match System.Environment.GetEnvironmentVariable "DOCKER_IMAGE" with
| null
| "" -> "halcwb/genpres"
| 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: <Version> 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
"."
])
"."
)


Target.create
"DockerRun"
(fun _ ->
// Fail fast with a clear message, but don't pass the values as `-e NAME=value` args: `createProcess`
// (Helpers.fs) renders the full argument list into its failure message on any non-zero docker exit,
// which would leak GENPRES_URL_ID/GENPRES_PASSWORD as plain text. `-e NAME` (no `=value`) makes docker
// forward the variable from its own environment instead, so the secrets never appear in the args.
requireEnvVar "GENPRES_URL_ID" |> ignore
requireEnvVar "GENPRES_PASSWORD" |> ignore

run
docker
[
"run"
"-it"
"--rm"
"-p"
"8080:8085"
"-e"
"GENPRES_URL_ID"
"-e"
"GENPRES_PASSWORD"
dockerImage
]
"."
)


open Fake.Core.TargetOperators
Expand Down
57 changes: 35 additions & 22 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ packages for the Fable/Vite dev server).
| `dotnet run TestHeadless` | `TestHeadless` | Build and run tests without launching a browser |
| `dotnet run WatchTests` | `WatchTests` | Run tests in watch mode (re-runs on file changes) |
| `dotnet run Format` | `Format` | Format all F# source files using Fantomas |
| `dotnet run DockerRun` | `DockerRun` | Run the pre-built Docker image locally |
| `dotnet run DockerBuild` | `DockerBuild` | Build the production image (`halcwb/genpres` by default, override with `DOCKER_IMAGE`), labelling it with the version from the root `Directory.Build.props` |
| `dotnet run DockerRun` | `DockerRun` | Run the built image locally, using `GENPRES_URL_ID`/`GENPRES_PASSWORD` from the current environment (source `.env` first) |

#### Target Dependency Chains

Expand Down Expand Up @@ -237,38 +238,50 @@ dotnet run

##### Docker wrappers

None of these wrappers bake `GENPRES_URL_ID` into the image — that constraint is enforced by the `Dockerfile` itself and described in [Environment Configuration](#environment-configuration).
Building and running the image no longer needs a hand-copied shell script: the `DockerBuild`
and `DockerRun` FAKE targets (see [FAKE Build Targets Reference](#fake-build-targets-reference))
cover both, work identically from PowerShell, Git Bash, or any POSIX shell, and are tracked in
`Build.fs` rather than living only as documentation. Neither target bakes `GENPRES_URL_ID` into
the image — that constraint is enforced by the `Dockerfile` itself and described in
[Environment Configuration](#environment-configuration).
Comment thread
7sharp9 marked this conversation as resolved.
Outdated

**`docker-local.sh`** — build for the local processor architecture (Apple Silicon → arm64; Intel/Linux → amd64). Save at the repo root:
**Build** — `dotnet run DockerBuild` reads the app's single curated version number from the root
`Directory.Build.props` and passes it to `docker build --build-arg APP_VERSION=...`, so the image's
`org.opencontainers.image.version` label always matches what was built. To cross-build for
a different platform set `DOCKER_PLATFORM`; to tag/push under your own name instead of the
project's `halcwb/genpres` default, set `DOCKER_IMAGE` (both `DockerBuild` and `DockerRun` read it).

```bash
#!/usr/bin/env bash
docker build -t halcwb/genpres .
```
# local architecture
dotnet run DockerBuild

**`docker-amd64.sh`** — cross-build an amd64 image on Apple Silicon for deployment to a typical Linux host. Save at the repo root:
# cross-build amd64
DOCKER_PLATFORM=linux/amd64 dotnet run DockerBuild
```

```bash
#!/usr/bin/env bash
docker build --platform linux/amd64 -t halcwb/genpres .
```powershell
# cross-build amd64 (PowerShell)
$env:DOCKER_PLATFORM = "linux/amd64"
dotnet run DockerBuild
```

**`docker-run.sh`** — source `.env`, validate that both `GENPRES_URL_ID` and `GENPRES_PASSWORD` are set (the `:` parameter expansion fails fast if either is missing), then run the container with the right `-e` flags. Save at the repo root:
**Run** — `dotnet run DockerRun` reads `GENPRES_URL_ID` and `GENPRES_PASSWORD` from the current
environment and fails fast with an error if either is missing, rather than starting an
unauthenticated container that the in-server `validateProductionPassword` would refuse later.
Source `.env` first (single source of truth — same as `prod.sh` / `debug.sh`):

```bash
#!/usr/bin/env bash
# Load env vars from .env (single source of truth — same as prod.sh / debug.sh).
set -a; source .env; set +a
dotnet run DockerRun
```

# Fail fast if .env did not provide the secrets, so we don't start an
# unauthenticated container that the in-server validateProductionPassword
# would refuse later anyway.
: "${GENPRES_URL_ID:?GENPRES_URL_ID is not set in .env}"
: "${GENPRES_PASSWORD:?GENPRES_PASSWORD is not set in .env}"

docker run -e GENPRES_URL_ID="${GENPRES_URL_ID}" \
-e GENPRES_PASSWORD="${GENPRES_PASSWORD}" \
-p 8080:8085 halcwb/genpres
```powershell
Get-Content .env | ForEach-Object {
if ($_ -match '^\s*([^#=]+)=(.*)$') {
[Environment]::SetEnvironmentVariable($Matches[1].Trim(), $Matches[2].Trim())
}
}
dotnet run DockerRun
```

If you find yourself wanting to commit one of these local scripts (e.g. because the team agrees it should be standardized), add a `!`-prefixed allow-line for the file to `.gitignore` in the same PR — otherwise the opt-in strategy will silently keep it untracked.
Expand Down
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ RUN dotnet tool restore
COPY .paket .paket
COPY paket.references paket.references
COPY paket.dependencies paket.lock ./
# Each library's own Directory.Build.props imports this root file (via
# GetPathOfFileAbove) to share the single curated <Version>. Without it
# present at /workspace, that Import resolves to an empty path and MSBuild
# fails with MSB4020.
COPY Directory.Build.props .

FROM build AS app-build

Expand Down Expand Up @@ -34,6 +39,14 @@ RUN dotnet run bundle


FROM mcr.microsoft.com/dotnet/aspnet:10.0

# Curated single version number for the whole app (server, client, libraries).
# Sourced from the root Directory.Build.props by the caller (see docker-local.sh /
# docker-amd64.sh templates in DEVELOPMENT.md) so the image label always matches
# what was actually built, without duplicating the version here.
Comment thread
7sharp9 marked this conversation as resolved.
Outdated
ARG APP_VERSION=0.0.0
LABEL org.opencontainers.image.version="${APP_VERSION}"

COPY --from=app-build /workspace/deploy /app

ENV GENPRES_LOG=0
Expand Down