- 1. What the context path is
- 2. Building with your own context path
- 3. How it works
- 3.1. Build-time, not runtime
- 3.2. Why it is not in
gateway.yamleither - 3.3. Why the seam works through a carrier key
- 3.4. When the third key acts
- 3.5. The management interface does not move with the application
- 3.6. Off-context requests answer with a plain 404
- 3.7. Route selection consumes the raw path, prefix included
- 3.8. The host side: what derives the path, and what cannot
- 4. Where the rest of this lives
Where the gateway serves its data plane, where it serves health and metrics, and what it costs to move either.
A context path is fixed when the artifact is built. Moving it is a rebuild, never an environment variable on a running container. Everything else on this page follows from that one fact.
| If you want | Read |
|---|---|
the definitions and the defaults |
|
a build under your own path, end to end |
|
why the mechanism is shaped the way it is |
API Sheriff serves on two independent listeners, each with its own prefix. Both prefixes are
declared in the shipped application.properties, at their current effective Quarkus defaults.
| Key | Default | Port | What it places |
|---|---|---|---|
|
|
|
The application context path — the prefix beneath which the data-plane edge serves every route
declared by |
|
|
|
The management context path — the prefix beneath which |
|
|
|
Non-application routes on the main HTTP port. Inert while the management interface is enabled — see When the third key acts. |
So a stock build answers the data plane at https://<host>:8443/…; and the probes at
https://<host>:9000/q/health/ready.
quarkus.management.root-path is an absolute key. It does not move when the application root
path moves. A build carrying only a new application path leaves health and metrics exactly where
your monitoring already looks, which is almost always what a deployment behind an ingress prefix
wants. Moving the probes takes a second, separate property.
(quarkus.http.non-application-root-path is the exception: it is declared non-absolute — q,
with no leading slash — so it resolves relative to quarkus.http.root-path and moves with it.)
All three sit on Quarkus BuildTimeConfig interfaces, so each value is fixed at *augmentation — when the application is built, not when it starts.
|
Important
|
The same holds for |
Three consequences, all of them yours to handle. Each is expanded in How it works.
| Consequence | Short form |
|---|---|
Your route prefixes move too |
Route selection matches the raw inbound path, prefix included. On a |
Off-context requests lose the error envelope |
A request outside the context path never reaches the gateway. Quarkus' main router answers it with
a plain |
Host-side probe wiring is not derived |
Compose labels, scrape configs and probe URLs are declarations by the deployment. Nothing derives them from the image. |
Unaffected: the image’s baked HEALTHCHECK, which is a protocol-blind TCP accept against the
management port and reads no URL at all.
Two routes, depending on whether you own a build or a checkout. Both produce the same thing.
| Route A — your own project | Route B — from this repository | |
|---|---|---|
For |
A deployer who does not clone this repository |
Someone working in this repository |
Seam |
A POM property, inherited from the published build parent |
A |
Application path |
|
|
Management path |
|
|
Both properties are optional and independent — supply one, the other, or neither. Supplying neither
ships the stock / and /q.
This is the supported route for a third party. The worked example below moves both paths, to /gw
and /mgmt, and runs end to end.
Inherit the published build parent and set the properties. You declare no dependency, no plugin and
no profile: the parent supplies the api-sheriff dependency, the Quarkus platform, the
quarkus-maven-plugin binding and the native profile.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.cuioss.sheriff.gateway</groupId>
<artifactId>api-sheriff-build-parent</artifactId>
<!-- The API Sheriff release you are adopting. Bumping this bumps the gateway with it:
the parent declares the api-sheriff dependency at ${project.parent.version}. -->
<version>0.2.2</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>my-gateway</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<sheriff.context-path>/gw</sheriff.context-path>
<!-- Optional and independent. Omit it and health/metrics stay at /q. -->
<sheriff.management-context-path>/mgmt</sheriff.management-context-path>
</properties>
</project>mvn package -PnativeProduces target/my-gateway-1.0.0-SNAPSHOT-runner.
The inherited native profile sets quarkus.native.container-build=true, so the compile runs
inside the Mandrel builder container and you need a container runtime rather than a local GraalVM.
The result is a Linux binary: on macOS or Windows it does not execute directly, and step 3 is how
you run it.
For a JVM-mode build instead, drop -Pnative; the same properties apply and the result is
target/quarkus-app/quarkus-run.jar.
The build parent ships no Dockerfile, deliberately — base image, registry, user and filesystem
posture, scanning and signing are deployment-owned choices. Copy this one into your project and own
it. It is this repository’s api-sheriff/src/main/docker/Dockerfile.native, reduced to the parts a
consumer needs:
DockerfileFROM quay.io/quarkus/quarkus-distroless-image:2.0
WORKDIR /app
COPY --chmod=0755 target/*-runner /app/application
EXPOSE 8443 9000
# Exec (JSON) form is required, not a style preference: this base carries no shell, so the string
# form -- which Docker rewrites to CMD-SHELL -- would resolve /bin/sh and fail permanently.
# The probe is a protocol-blind TCP accept against 127.0.0.1:9000. It carries no URL, so neither
# context path reaches it; the management PORT does.
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s --retries=3 \
CMD ["/app/application", "--health-probe"]
USER nonrootdocker build -t my-gateway:1.0.0 .This is the step that is easy to miss and it is what makes the build actually serve. Route
selection consumes the raw inbound path, so every anchor and route path_prefix must carry the new
prefix. The gateway does not reconcile the two for you.
The smallest configuration directory that boots is three files:
config/gateway.yamlversion: 1
metadata:
config_version: "my-gateway"
allowed_methods: ["GET", "HEAD"]
security_defaults:
profile: strict
anchors:
api:
path_prefix: /gw/api # <-- the context path, then your own prefix
type: proxy
access: public
token_validation:
issuers:
- name: static
issuer: https://api-sheriff.test/ctxpath
jwks:
source: file
file: /app/certificates/test-jwks.jsonconfig/endpoints/demo.yamlendpoint:
id: demo-api
enabled: true
base_url: UPSTREAM
anchor: api
auth:
require: none
routes:
- id: demo-api-get
match:
path_prefix: /gw/api # <-- and hereconfig/topology.propertiesUPSTREAM=${TOPOLOGY_UPSTREAM:-http://upstream:8080}|
Note
|
Three details in that document are load-bearing, and each one costs a failed boot to discover:
|
The smoke-test posture below is plain HTTP on both listeners, which is the smallest thing that runs. For the production posture — terminated TLS on both, certificates supplied by the deployment — see Container Image.
docker run -d --name my-gateway \
--cap-drop=ALL --security-opt=no-new-privileges \
-p 8080:8080 -p 9000:9000 \
-v "$PWD/config:/app/sheriff-config:ro" \
-v "$PWD/certs:/app/certificates:ro" \
-e SHERIFF_CONFIG_DIR=/app/sheriff-config \
-e QUARKUS_HTTP_INSECURE_REQUESTS=enabled \
-e QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME=plain-management \
my-gateway:1.0.0QUARKUS_MANAGEMENT_TLS_CONFIGURATION_NAME=plain-management is the named opt-out that takes the
management interface back to plain HTTP; it is audited, and boot logs ApiSheriff-115 at WARNING
naming the port and what it costs. certs/ holds the test-jwks.json the configuration names — it is mounted even on a plain-HTTP run, because it is named by gateway.yaml, not by a listener.
curl -i http://localhost:8080/gw/api # in context, route matches
curl -i http://localhost:8080/gw/no-such-route # in context, no route
curl -i http://localhost:8080/no-such-route # off context
curl -i http://localhost:9000/mgmt/health/ready # probes, at the moved management path
curl -i http://localhost:9000/q/health/ready # the old management path
docker inspect --format '{{json .State.Health}}' my-gatewayWhat these answer, and why each one matters:
| Request | Expected |
|---|---|
|
|
|
|
|
|
|
|
|
|
baked |
|
If GET /gw/api answers 404 with problem+json rather than 502, the build is fine and step 4
is not. The request reached the gateway and failed route selection, which means a path_prefix
still lacks the prefix.
Same seam, supplied as -D properties. Each property activates a profile in api-sheriff/pom.xml by
its own presence, so an unset property leaves that profile inactive and the build ships the stock
default.
./mvnw package -pl api-sheriff -am -Dsheriff.context-path=/gw./mvnw clean install -Pnative -pl api-sheriff -am -DskipTests \
-Dsheriff.context-path=/gw -Dsheriff.management-context-path=/mgmt
docker build -f api-sheriff/src/main/docker/Dockerfile.native -t api-sheriff:gw api-sheriff/The Dockerfile.native build context is api-sheriff/ because the image copies target/*-runner
from it — the executable must already be built.
Steps 4 to 6 of route A apply unchanged.
|
Warning
|
That is why route A exists at all, and why the published build parent uses an unconditional property mapping rather than a profile. |
A rebuilt artifact is half the job. These are declarations by the deployment — nothing derives them from the image, and there is no mechanism by which an image reports its own context path.
| What | Moves with | Why it is not derived |
|---|---|---|
Anchor and route |
the application path |
Route selection matches the raw inbound path; the two are configured independently. |
Ingress / reverse-proxy rules, and any client base URL |
the application path |
Outside the artifact entirely. |
The |
the management path |
It is what host-side readiness gates read; it is a declaration, not a reflection of the image. |
Prometheus |
the management path |
Prometheus interpolates no environment variables into a scrape config. There is no templating mechanism that reaches the value. |
Orchestrator liveness/readiness probe paths |
the management path |
Same reason as the label. |
The image’s |
neither — but moving the management port breaks it |
The probe is a URL-free TCP accept against |
In this repository the same list is guarded rather than left to reviewer vigilance:
ManagementRootPathLabelIT compares the Prometheus metrics_path,
integration-tests/scripts/verify-invalid-config-fails.sh and `BaseIntegrationTest’s default
constant against the Compose label, and the release workflow asserts its own smoke-probe constant
against the shipped declaration. See The host side: what derives the path, and what cannot for
what that does and does not buy you.
No, and adding one would nest a container build inside a container build.
The native compile already runs in a container. -Pnative sets
quarkus.native.container-build=true, so mvn package -Pnative starts the Mandrel builder image,
compiles inside it, and leaves a Linux executable in target/. The pipeline is therefore already
two-stage — it is just orchestrated by Maven rather than by Docker — and the portability argument
for multi-staging (no local GraalVM toolchain) is already paid for.
What the single-stage Dockerfile buys on top of that:
-
The
docker buildstep is seconds, not minutes, and it re-runs without recompiling. -
The runtime stage is distroless with no build tooling in it, which is the property a multi-stage build is usually adopted to obtain in the first place.
-
Your CI caches the Maven repository the way it already caches it, rather than through Docker layer caching of a
mvninvocation.
If you want one self-contained docker build anyway — a hermetic CI with no Maven step is the
honest reason to — the builder stage must set -Dquarkus.native.container-build=false, because
inside the builder container there is no Docker daemon to start a nested one. That variant is not
exercised by this repository and is not covered by its verification runbook.
The mechanism, its costs, and the behaviours that surprise people. Everything here is background for the two sections above.
Each of the three keys sits on a Quarkus *BuildTimeConfig interface, so its value is fixed at
augmentation. The environment variables are routing-inert against an already-built image.
It is not necessarily silent, though, and this page does not claim it is. The built value is
recorded in the artifact and compared against the runtime one by
io.quarkus.deployment.configuration.ConfigGenerationBuildStep’s build-time-mismatch check,
governed by `quarkus.config.build-time-mismatch-at-runtime — left at its warn default here. A
divergent override may therefore produce a startup warning naming the key. The routing claim is
measured; the absence of a log line is not, so treat a mismatch warning as expected noise rather than
as evidence the override took effect.
Under ADR-0025's
policy / deployment-bound / build-time triage the context path is a build-time knob, so it is not
part of gateway.yaml’s neutral vocabulary. That absence is a structural impossibility rather than a
stylistic choice: `gateway.yaml is a runtime source, read at startup by ConfigProducer, so a key
already fixed at augmentation cannot be projected from it. A context_path written there could never
take effect, and putting it there would only make it look settable.
The class-by-class operator view of that triage is Environment-Variable Overrides.
This is the non-obvious part, and the part most likely to be "simplified" away by a later reader, so it is stated at length. The two configurable keys are declared as expressions over a carrier key rather than as plain literals:
api-sheriff/src/main/resources/application.propertiesquarkus.http.root-path=${quarkus.sheriff-context-path:/}
quarkus.management.root-path=${quarkus.sheriff-mgmt-path:/q}api-sheriff/pom.xml (inside the property-activated profiles)<quarkus.sheriff-context-path>${sheriff.context-path}</quarkus.sheriff-context-path>
<quarkus.sheriff-mgmt-path>${sheriff.management-context-path}</quarkus.sheriff-mgmt-path>Two facts, both established by measurement rather than by reading documentation, force that shape.
The build property must be quarkus.-prefixed to survive at all. quarkus-maven-plugin forwards
the effective POM’s properties into augmentation through getBuildSystemProperties(quarkusOnly),
called with quarkusOnly = true, which keeps only quarkus.-prefixed names. A profile that set
sheriff.context-path itself as the build property would be filtered out and never reach the
configuration. That is why the profile maps the operator-facing sheriff. property onto a
quarkus. one.
The carrier is needed because application.properties outranks the forwarded property. Forwarded
build-system properties land in a PropertiesConfigSource named Build system at the default
ordinal 100; src/main/resources/application.properties is ordinal 250 and therefore wins for
every key it declares. A build seam that set quarkus.http.root-path directly would consequently be
overridden by the very declaration it was trying to change. This is not a theory about ordinals: with
the direct mapping in place, package -Dsheriff.context-path=/gw produced zero occurrences of
/gw in generated-bytecode.jar. The seam was inert while every build stayed green.
Declaring the winning value as an expression inverts that. The ordinal-250 declaration still wins,
but what it wins with is a reference: the carrier key resolves from whatever source supplies it — the ordinal-100 build-system source when the seam sets it, and the expression’s own inline default
when nothing does. So the seam acts, and an unconfigured build still ships / and /q.
|
Warning
|
Do not flatten these declarations back to literals. The literal form reads more obviously and
It is not, however, invisible to the suite: two tests assert the declared form directly.
|
quarkus.http.non-application-root-path takes no expression: it is not part of the seam, so it stays
a literal.
quarkus.sheriff-context-path and quarkus.sheriff-mgmt-path are our key names on Quarkus'
namespace. Quarkus owns no such keys, so they are not a real configuration surface: no validation, no
documentation, no tooling completion, and no framework guarantee that a typo in either will be
reported rather than silently ignored.
That cost is deliberate and accepted, not an oversight to be tidied away. It is the price of the
only shape in which the seam acts, and it is cheaper than the alternative: a @ConfigMapping (or any
other construct) added solely to make them look official would claim these keys as a configuration
surface when they are pure carriers — a documented key nobody should ever set.
|
Note
|
The cost is structural rather than visible. It was expected to surface as an unrecognized-key warning on every boot, and it does not: no such warning was emitted by the seam-active JVM augmentation, the seam-active native augmentation, or the resulting container at boot. The likely reason is that at runtime the carrier is only ever referenced by an expression, never provided by a config source, and an unset expression key produces no report. Recorded with the rest of the measurements in the verification runbook. Read that as a reason to be more careful with these two names, not less: there is no warning to catch a rename or a typo, which is exactly what `BuildParentContractTest’s carrier-coupling assertion exists to catch instead. |
Quarkus normalises quarkus.http.root-path by prepending a / when the value does not already start
with one — and in the @QuarkusTest augmentation path it applies that normalisation to the raw
string, before the expression is expanded. ${quarkus.sheriff-context-path:/} does not start with
/, so it becomes /${quarkus.sheriff-context-path:/} and expands to //.
A @QuarkusTest therefore reads // back from
ConfigProvider.getConfig().getValue("quarkus.http.root-path", String.class), and
ContextPathDefaultsTest asserts exactly that rather than pretending otherwise.
The packaged artifact is not affected, and that was measured rather than assumed. The
quarkus-maven-plugin augmentation expands first and normalises after, so
BuildTimeRunTimeFixedConfigSourceBuilder in target/quarkus-app/quarkus/generated-bytecode.jar
records / for a default build and /gw for -Dsheriff.context-path=/gw. The Vert.x router mounts
at / and /q/ by default, or /gw/ and /gw/q/ under the seam — identical to the literal-valued
declaration this replaced. The divergence is confined to the harness’s own read-back of one key.
The ordering matters if you are tempted to "fix" the //. Flattening the value back to a literal
disables the seam outright. Emptying the default (${…:}) does clear the //, but at the cost of
application.properties no longer declaring the acting / at all — the packaged artifact then
records the empty string and the effective / comes back from the framework’s own default, which is
exactly the implicitness this declaration exists to remove. All three costs were built and measured;
the // read-back is the cheapest, and it is the one taken deliberately.
quarkus.http.non-application-root-path acts when the management interface is disabled. This stack
runs with quarkus.management.enabled=true, and while the management interface is enabled every
non-application route — health, metrics — is served by the management router under
quarkus.management.root-path on the management port. The third key governs nothing there.
Set quarkus.management.enabled=false and the non-application endpoints move back onto the main HTTP
port, where they are served at {quarkus.http.root-path}/{quarkus.http.non-application-root-path} — and that is the condition under which this key places them. It is declared so that a reader meets a
key with a documented condition rather than a key that appears to do nothing.
The router registers the composed prefix either way: a /gw build’s native executable contains
/gw/q/ as well as /gw/, inert but present.
That said, disabling the management interface to obtain a plain-HTTP probe endpoint is a trap with
its own consequences; TLS Edge documents why the plain-management TLS bucket is
the supported route instead.
quarkus.management.root-path is an independent absolute key. Concretely, a build carrying only
-Dsheriff.context-path=/gw:
-
serves the data plane at
https://<host>:8443/gw/…;; -
still serves health and metrics at
https://<host>:9000/q/health,…/q/health/ready,…/q/health/liveand…/q/metrics, unchanged.
Moving the probes as well takes the second property. The two are orthogonal on purpose: a deployment that relocates its public data plane behind an ingress prefix usually wants its operational surface to stay exactly where its monitoring already looks.
The management interface’s port, scheme and single-port constraint are separate concerns and are deployment-bound rather than build-time; they are covered in TLS Edge and Environment-Variable Overrides.
Choosing a non-root application context path changes the error surface at the edge, and the change is easy to misread as a defect. It is not one.
The gateway’s data plane is a single catch-all Vert.x route registered last on the Quarkus main
router (router.route().last().handler(…)), and that router is itself mounted beneath
quarkus.http.root-path. Everything the gateway does — deny-by-default route selection, the
inbound-validation pipeline, the RFC 9457 application/problem+json error envelope — lives inside
that catch-all, so it only ever sees requests that fall within the configured context path.
The consequence, for a build carrying -Dsheriff.context-path=/gw:
| Request | Answer |
|---|---|
|
In context. The request reaches the gateway’s catch-all, fails deny-by-default route selection,
and is answered with the gateway’s own |
|
Off context. The request never reaches the gateway at all. Quarkus' main router answers it with a
plain |
This is a documented consequence of choosing a non-root path, not a gap to be patched. Restoring the envelope for off-context requests would mean registering a second catch-all outside the configured root path — that is, deliberately serving something at a prefix the deployment said the gateway does not occupy, on the assumption that nothing else is ever mounted there. That assumption is exactly the one a context path exists to avoid making. A deployment that wants a uniform error shape at the edge owns that at its ingress, where it already owns the prefix.
Both shapes are recorded as executed probes in the verification runbook.
GatewayEdgeRoute.buildPipelineRequest builds its pipeline request from HttpServerRequest.uri() — the raw inbound URI — and the route table is matched against that. It is never handed a
root-path-relative path.
So a build carrying -Dsheriff.context-path=/gw does not make path_prefix: /api answer at
/gw/api; that request reaches the gateway and then fails deny-by-default route selection. The
anchor and route path_prefix values must carry the new prefix too — path_prefix: /gw/api. Both
halves of that were measured; see
the
verification runbook.
A context path that only the artifact knows about is not much use to the scripts and probes that have to reach it. Three mechanisms bridge that gap, and they are deliberately unequal.
Each labelled Compose service publishes its management root path as
de.cuioss.sheriff.management-root-path, alongside the existing de.cuioss.sheriff.management-scheme
label. Host-side bring-up gates read both, so a probe URL is composed from the model rather than
restated in a script:
labels:
de.cuioss.sheriff.management-scheme: "https"
de.cuioss.sheriff.management-root-path: "/q"This extends the derivation rule of
ADR-0031
rather than departing from it — that ADR already requires the scheme to come from an explicit
per-service label rather than from an inference, and the root path is the same kind of fact carried
the same way. The endpoint names beneath the derived root path (/health, /health/ready,
/health/live, /metrics) are Quarkus' own fixed suffixes rather than deployment-bound values,
which is why they alone stay spelled out in the consuming scripts.
The label is a declaration by the deployment, not something the image reports about itself. A build that moves the management context path must move the label to match; nothing derives one from the other.
|
Note
|
The |
integration-tests/prometheus.yml still restates the management root path in its metrics_path.
Nothing can derive it there: the file is bind-mounted into the Prometheus container verbatim, and
Prometheus interpolates no environment variables into a scrape config — there is no templating
mechanism that reaches the value. Changing the gateway’s management root path therefore means editing
that literal by hand.
Three further hand-maintained literals share that constraint:
-
integration-tests/scripts/verify-invalid-config-fails.sh— it drives a baredocker runrather than bringing up the stack, so there is no resolved Compose model to read the label from. -
integration-tests/src/test/java/…/BaseIntegrationTest.java— the suite’s own default constant. -
.github/workflows/release.yml’s `MANAGEMENT_CONTEXT_PATH— the path the published-image smoke probe is composed beneath. That lane runsdocker runagainst a pulled image, so it too has no resolved Compose model, andDockerfile.nativedeclares no root-path OCI label to inspect either.
None of the four is left to reviewer vigilance. The three under integration-tests/ each have an
assertion leg in ManagementRootPathLabelIT — prometheusScrapePathAgreesWithLabel,
invalidConfigScriptRootPathAgreesWithLabel and baseIntegrationTestDefaultAgreesWithLabel — comparing the literal against the Compose label after the same trailing-slash normalisation. Editing
one without the others turns the integration suite red rather than producing a silently blind
Prometheus, a failure that reads as a gateway fault, or a suite probing the wrong path.
The fourth is asserted where it lives, because the release lane never brings the stack up and so has
no label to compare against. Its Assert the smoke-probe path matches the shipped management root
path step compares the workflow constant against application.properties’ declared default — the
acting value, since that lane’s `./mvnw verify -Pintegration-tests passes no
-Dsheriff.management-context-path and leaves the seam profile inactive — and fails the release
before the image is built.
Being asserted is not the same as being derived, and the distinction is why all four still have to be edited by hand. Prometheus interpolates nothing into a scrape config, the script and the release lane have no resolved Compose model, and a test default cannot read a label that only exists once the stack is up — so each value must be written, and the assertions are what make writing it wrong a build failure rather than a silent drift. Prefer deriving where a resolved model exists; where one does not, expect to edit by hand and to be caught when you forget.
The image’s HEALTHCHECK is a protocol-blind TCP accept against the management port. It carries no
URL and reads no configuration — deliberately, so that it can answer before the application has read
any — so neither context path affects it. It also proves correspondingly less: it is a liveness
signal, and a healthy container has not told you the gateway is ready to serve. See
Reading the container health signal.
The knob that does break it is the management port: the probe targets 9000 as a compiled-in
constant, so a deployment moving QUARKUS_MANAGEMENT_PORT makes the baked check fail closed — never
healthy — and the only remedy is an image rebuilt against the new port. Overriding the HEALTHCHECK
does not reach it, because the override still names the one executable the image carries and that
binary probes the constant it was built with.
| Document | What it adds |
|---|---|
The published build parent in full — what it declares on your behalf, how it composes with
|
|
The six-case procedure that proves the seam, with the responses that were observed rather than predicted. |
|
The full policy / deployment-bound / build-time classification, and every knob the deployment owns. |
|
Running the published image in its production posture, and the baked health check’s liveness semantics. |
|
A runnable stack, its two-layer readiness gate, and how to read the container health signal. |
|
The management interface’s scheme, its single-port constraint, and the plain-HTTP opt-out. |
|
The neutral |