From 5cad9dc8217f0aa8139eb68ac1e32c6ab2af0df6 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Wed, 2 Sep 2026 23:23:20 +0530 Subject: [PATCH 1/7] fix(docker): make the Server startup timeout configurable The entrypoint ran start-hugegraph.sh with a literal -t 120, so a Server that needed longer than 120 seconds to answer on its REST port was terminated by its own container, no matter how much startup budget the orchestrator's probe allowed. Read the timeout from HG_SERVER_STARTUP_TIMEOUT_S instead, keep 120 as the default, and reject values that are not positive whole numbers before init-store runs. Covered by docker-entrypoint-test.sh: default passthrough, an explicit override, and rejection of an invalid value. Documented in the Server docker README. Closes #3186 --- .../hugegraph-dist/docker/README.md | 17 ++++++++++++++ .../docker/docker-entrypoint-test.sh | 22 +++++++++++++++++++ .../docker/docker-entrypoint.sh | 15 ++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/hugegraph-server/hugegraph-dist/docker/README.md b/hugegraph-server/hugegraph-dist/docker/README.md index 9214aa830e..2cfc101bf0 100644 --- a/hugegraph-server/hugegraph-dist/docker/README.md +++ b/hugegraph-server/hugegraph-dist/docker/README.md @@ -144,3 +144,20 @@ native `HEALTHCHECK` instructions. `docker ps` shows real health status: | `hugegraph/hugegraph-store` | `GET /v1/health` on port 8520 | The entrypoints supervise the Java process directly — when Java exits, the container exits. If started with a restart policy (the provided compose files use `restart: unless-stopped`), Docker will bring it back automatically. The old cron-based monitor (`-m true`) is for VM/bare-metal deployments only and is not used in Docker images. + +## 7. Server Startup Timeout + +The entrypoint gives the Server a fixed budget to answer on its REST port and +ends the container when the budget runs out. It is 120 seconds by default. Set +`HG_SERVER_STARTUP_TIMEOUT_S` to a positive whole number of seconds to change +it: + +```bash +docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hugegraph/hugegraph:1.7.0 +``` + +Raise it on slow or contended hosts, and wherever an orchestrator already owns +the startup budget through a probe of its own: a startup probe cannot extend a +container that has already ended the JVM it was waiting for. A value that is +not a positive whole number stops the container at startup instead of silently +falling back to the default. diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh index 6e22885ebe..6a712cc688 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh @@ -35,6 +35,7 @@ backend=rocksdb EOF cat > "${TEST_HOME}/bin/start-hugegraph.sh" <<'EOF' #!/usr/bin/env bash +printf '%s\n' "$*" >> ./docker/start-hugegraph-args exit 0 EOF cat > "${TEST_HOME}/bin/init-store.sh" <<'EOF' @@ -232,4 +233,25 @@ rm -f "${TEST_HOME}/docker/init_complete" ) grep -Fqx -- '-n' "${TEST_HOME}/docker/init-store-password" +last_start_args() { tail -n 1 "${TEST_HOME}/docker/start-hugegraph-args"; } + +[[ "$(last_start_args)" == *"-t 120"* ]] + +( + cd "${TEST_HOME}" + HG_SERVER_STARTUP_TIMEOUT_S=450 bash ./docker-entrypoint.sh +) +[[ "$(last_start_args)" == *"-t 450"* ]] + +start_calls_before_invalid_timeout=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args") +if ( + cd "${TEST_HOME}" + HG_SERVER_STARTUP_TIMEOUT_S=2m bash ./docker-entrypoint.sh +); then + echo "invalid startup timeout unexpectedly succeeded" >&2 + exit 1 +fi +[[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args")" -eq \ + "${start_calls_before_invalid_timeout}" ]] + echo "PASS: Docker entrypoint configures HStore discovery and authentication" diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh index fe9974c430..e02cbf5791 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh @@ -98,6 +98,19 @@ if [[ -n "${HG_SERVER_AUTH_TOKEN_SECRET:-}" ]]; then fi fi +# How long the entrypoint lets the server take to answer on its REST port +# before it gives up and ends the container. An orchestrator that already +# owns this budget through a startup probe needs to raise it, otherwise the +# container terminates a JVM that is still starting and the probe never gets +# to decide. Validated here so a typo fails before init-store runs, rather +# than reaching the arithmetic in wait_for_startup. +SERVER_STARTUP_TIMEOUT_S="${HG_SERVER_STARTUP_TIMEOUT_S:-120}" +if [[ ! "${SERVER_STARTUP_TIMEOUT_S}" =~ ^[1-9][0-9]*$ ]]; then + log "ERROR: HG_SERVER_STARTUP_TIMEOUT_S must be a positive whole number" \ + "of seconds, got '${SERVER_STARTUP_TIMEOUT_S}'" + exit 1 +fi + if [[ -n "${PASSWORD:-}" && "${HG_SERVER_REQUIRE_AUTH_TOKEN_SECRET:-false}" == "true" && -z "${HG_SERVER_AUTH_TOKEN_SECRET:-}" ]]; then @@ -217,7 +230,7 @@ else ./bin/init-store.sh fi -./bin/start-hugegraph.sh -j "${JAVA_OPTS:-}" -t 120 +./bin/start-hugegraph.sh -j "${JAVA_OPTS:-}" -t "${SERVER_STARTUP_TIMEOUT_S}" # Post-startup cluster stabilization check (hstore only — rocksdb has no partitions) ACTUAL_BACKEND=$(grep -E '^[[:space:]]*backend[[:space:]]*=' "${GRAPH_CONF}" | head -n 1 | sed 's/.*=//' | tr -d '[:space:]' || true) From 6ca3df360bb0ea81013f0653ddfd493c0c1127b3 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Thu, 3 Sep 2026 18:06:10 +0530 Subject: [PATCH 2/7] fix(docker): bound the startup timeout and reject an empty value Review follow-up on the configurable Server startup timeout. The guard accepted any positive integer, but wait_for_startup computes its deadline as $((now_s + timeout_s)). Near the 64-bit ceiling that sum wraps negative, the wait loop exits before its first probe, and the container reports a startup timeout immediately: the failure the variable exists to prevent. The accepted range is now 1 to 86400 seconds, bounded first by a five-digit pattern so the comparison itself cannot overflow. The default also used ':-', which treats an explicitly empty value as unset and silently restores 120. Compose writes exactly that whenever an interpolated host variable is missing, so a deployment that believed it had set 450 still died at 120, and the README said such a value would stop the container. Plain '-' keeps the default for an unset variable and rejects an empty one. Tests now cover the accepted upper bound, an unset variable, and seven rejected values including empty, whitespace, 86401 and INT64_MAX, and assert that a rejected value runs neither start-hugegraph nor init-store, which pins the ordering the guard's comment claims. The new assertions exit explicitly rather than relying on set -e with [[ ]], which bash 3.2 ignores. The README documents the range and that the container health check keeps its own budget, which does not move with this variable. --- .../hugegraph-dist/docker/README.md | 20 +++++-- .../docker/docker-entrypoint-test.sh | 53 +++++++++++++++---- .../docker/docker-entrypoint.sh | 22 +++++--- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/docker/README.md b/hugegraph-server/hugegraph-dist/docker/README.md index 2cfc101bf0..07f5c5ae08 100644 --- a/hugegraph-server/hugegraph-dist/docker/README.md +++ b/hugegraph-server/hugegraph-dist/docker/README.md @@ -149,8 +149,8 @@ The entrypoints supervise the Java process directly — when Java exits, the con The entrypoint gives the Server a fixed budget to answer on its REST port and ends the container when the budget runs out. It is 120 seconds by default. Set -`HG_SERVER_STARTUP_TIMEOUT_S` to a positive whole number of seconds to change -it: +`HG_SERVER_STARTUP_TIMEOUT_S` to a whole number of seconds between 1 and 86400 +to change it: ```bash docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hugegraph/hugegraph:1.7.0 @@ -158,6 +158,16 @@ docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hug Raise it on slow or contended hosts, and wherever an orchestrator already owns the startup budget through a probe of its own: a startup probe cannot extend a -container that has already ended the JVM it was waiting for. A value that is -not a positive whole number stops the container at startup instead of silently -falling back to the default. +container that has already ended the JVM it was waiting for. Anything outside +the accepted range, an empty value included, stops the container at startup +instead of silently falling back to the default. + +Raising this budget does not move the health check described in section 6, +which runs on a clock of its own. The images set `--interval=15s +--start-period=90s --retries=3`, so a container given a longer startup budget +is reported `unhealthy` around 135 seconds while the entrypoint is still +legitimately waiting; raise it with `--health-start-period` on `docker run`. +The Compose files replace those values with their own (`start_period: 60s`, +`interval: 10s`, `retries: 30`, so roughly 360 seconds), and anything gated on +`depends_on: condition: service_healthy`, Hubble included, waits on that budget +rather than on this variable. Move the two together. diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh index 6a712cc688..6a38520bb3 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh @@ -243,15 +243,50 @@ last_start_args() { tail -n 1 "${TEST_HOME}/docker/start-hugegraph-args"; } ) [[ "$(last_start_args)" == *"-t 450"* ]] -start_calls_before_invalid_timeout=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args") -if ( +( cd "${TEST_HOME}" - HG_SERVER_STARTUP_TIMEOUT_S=2m bash ./docker-entrypoint.sh -); then - echo "invalid startup timeout unexpectedly succeeded" >&2 - exit 1 -fi -[[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args")" -eq \ - "${start_calls_before_invalid_timeout}" ]] + HG_SERVER_STARTUP_TIMEOUT_S=86400 bash ./docker-entrypoint.sh +) +[[ "$(last_start_args)" == *"-t 86400"* ]] + +# An empty value is a set value, not an absent one: Compose writes it whenever +# an interpolated host variable is missing. 2m is the shape of a typo, and the +# two large values bracket the point where the deadline arithmetic in +# wait_for_startup would wrap negative and end the wait before its first probe. +for invalid_timeout in "" " " 0 +5 2m 86401 9223372036854775807; do + start_calls_before_invalid=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args") + init_calls_before_invalid=$(wc -l < "${TEST_HOME}/docker/init-store-calls") + if ( + cd "${TEST_HOME}" + HG_SERVER_STARTUP_TIMEOUT_S="${invalid_timeout}" \ + bash ./docker-entrypoint.sh + ); then + echo "startup timeout '${invalid_timeout}' unexpectedly succeeded" >&2 + exit 1 + fi + # The server must not have started, and the guard must have run ahead of + # init-store, as the comment above it in the entrypoint claims. Spelled + # with an explicit exit rather than a bare [[ ]]: bash 3.2, still the + # /bin/bash of macOS, does not apply set -e to a failing [[ ]], so a bare + # assertion passes silently there while CI catches the regression. + [[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args")" -eq \ + "${start_calls_before_invalid}" ]] || { + echo "startup timeout '${invalid_timeout}' started the server" >&2 + exit 1 + } + [[ "$(wc -l < "${TEST_HOME}/docker/init-store-calls")" -eq \ + "${init_calls_before_invalid}" ]] || { + echo "startup timeout '${invalid_timeout}' was rejected only after" \ + "init-store ran" >&2 + exit 1 + } +done + +# An unset variable still keeps the historical default. +( + cd "${TEST_HOME}" + bash ./docker-entrypoint.sh +) +[[ "$(last_start_args)" == *"-t 120"* ]] echo "PASS: Docker entrypoint configures HStore discovery and authentication" diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh index e02cbf5791..b5ba2de34f 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh @@ -102,12 +102,22 @@ fi # before it gives up and ends the container. An orchestrator that already # owns this budget through a startup probe needs to raise it, otherwise the # container terminates a JVM that is still starting and the probe never gets -# to decide. Validated here so a typo fails before init-store runs, rather -# than reaching the arithmetic in wait_for_startup. -SERVER_STARTUP_TIMEOUT_S="${HG_SERVER_STARTUP_TIMEOUT_S:-120}" -if [[ ! "${SERVER_STARTUP_TIMEOUT_S}" =~ ^[1-9][0-9]*$ ]]; then - log "ERROR: HG_SERVER_STARTUP_TIMEOUT_S must be a positive whole number" \ - "of seconds, got '${SERVER_STARTUP_TIMEOUT_S}'" +# to decide. Validated here so a bad value fails before init-store runs, +# rather than reaching the arithmetic in wait_for_startup: that deadline is +# $((now_s + timeout_s)), which wraps negative near the 64-bit ceiling and +# makes the wait exit before its first probe, the very failure this variable +# exists to avoid. The five-digit bound keeps this comparison in range too, +# and a day is already far past any real start. Plain '-' rather than ':-', +# so an explicitly empty value is rejected instead of quietly becoming the +# default: Compose interpolation such as ${SOME_VAR:-} yields empty, not +# unset, whenever the host variable is missing. +SERVER_STARTUP_TIMEOUT_MAX_S=86400 +SERVER_STARTUP_TIMEOUT_S="${HG_SERVER_STARTUP_TIMEOUT_S-120}" +if [[ ! "${SERVER_STARTUP_TIMEOUT_S}" =~ ^[1-9][0-9]{0,4}$ ]] || + (( SERVER_STARTUP_TIMEOUT_S > SERVER_STARTUP_TIMEOUT_MAX_S )); then + log "ERROR: HG_SERVER_STARTUP_TIMEOUT_S must be a whole number of" \ + "seconds from 1 to ${SERVER_STARTUP_TIMEOUT_MAX_S}," \ + "got '${SERVER_STARTUP_TIMEOUT_S}'" exit 1 fi From 0ba30a1e780eb35ab77a95c83f32ec3255847069 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sat, 5 Sep 2026 11:07:05 +0530 Subject: [PATCH 3/7] fix(docker): propagate the startup timeout through Compose and tighten its tests Second review follow-up. No Compose topology declared HG_SERVER_STARTUP_TIMEOUT_S, so a rendered Server still took the entrypoint default and the container budget could not be aligned with the healthcheck start period. All three topologies now pass it through as ${HG_SERVER_STARTUP_TIMEOUT_S-120}: unset-only, so an absent host value renders 120 while an explicitly empty one reaches the entrypoint and is rejected there rather than silently becoming the default. The render test asserts both the default and a host override on every Server service, and renders with the variable stripped so the baseline cannot be coloured by the developer's environment. The entrypoint tests had three ways to pass while broken. They matched -t with a substring against a flattened "$*", where -t 1200 satisfies an assertion for 120; the stub now records one argument per line and the assertion reads the exact value after -t. The positive cases were bare [[ ]], which bash 3.2, still the /bin/bash of macOS, does not fail under set -e; they now exit explicitly and report the value they saw. The unset case only started a child shell, which inherits an exported value, so it now runs under env -u. Verified on bash 3.2.57 and 5.3.15: the suite passes on both, and on 3.2 each of the three regressions above is now reported instead of passing. --- docker/docker-compose-3pd-3store-3server.yml | 1 + docker/docker-compose-hstore.yml | 1 + docker/docker-compose.yml | 4 + docker/test-compose.sh | 47 +++++++++++- .../hugegraph-dist/docker/README.md | 5 ++ .../docker/docker-entrypoint-test.sh | 73 ++++++++++++++----- 6 files changed, 111 insertions(+), 20 deletions(-) diff --git a/docker/docker-compose-3pd-3store-3server.yml b/docker/docker-compose-3pd-3store-3server.yml index 6f599c6870..833dde8682 100644 --- a/docker/docker-compose-3pd-3store-3server.yml +++ b/docker/docker-compose-3pd-3store-3server.yml @@ -68,6 +68,7 @@ x-server-environment: &server-environment HG_SERVER_MIN_FREE_MEMORY: "0" HG_SERVER_INIT_STORE_ENABLED: "false" HG_SERVER_REQUIRE_AUTH_TOKEN_SECRET: "true" + HG_SERVER_STARTUP_TIMEOUT_S: ${HG_SERVER_STARTUP_TIMEOUT_S-120} HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-} PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:-} diff --git a/docker/docker-compose-hstore.yml b/docker/docker-compose-hstore.yml index d201430692..e15f7e7d0f 100644 --- a/docker/docker-compose-hstore.yml +++ b/docker/docker-compose-hstore.yml @@ -92,6 +92,7 @@ services: HG_SERVER_REST_URL: http://server:8080 HG_SERVER_MIN_FREE_MEMORY: "0" HG_SERVER_INIT_STORE_ENABLED: "false" + HG_SERVER_STARTUP_TIMEOUT_S: ${HG_SERVER_STARTUP_TIMEOUT_S-120} HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-} PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:-} ports: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 828ffe42b1..d85cf5b1c6 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -34,6 +34,10 @@ services: PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:-} HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-} HG_SERVER_MIN_FREE_MEMORY: "0" + # Unset-only default: a host value overrides it, an absent one + # renders the entrypoint default. ":-" would turn an empty host + # value into 120 silently, which the entrypoint rejects on purpose. + HG_SERVER_STARTUP_TIMEOUT_S: ${HG_SERVER_STARTUP_TIMEOUT_S-120} ports: - "8080:8080" volumes: diff --git a/docker/test-compose.sh b/docker/test-compose.sh index ecd5ab5b2d..327613cd83 100644 --- a/docker/test-compose.sh +++ b/docker/test-compose.sh @@ -30,7 +30,8 @@ ACTIVE_FILES=() RENDER_DIR="" compose_auth() { - env HUGEGRAPH_VERSION="${VERSION}" \ + env -u HG_SERVER_STARTUP_TIMEOUT_S \ + HUGEGRAPH_VERSION="${VERSION}" \ HUBBLE_IMAGE="${RENDER_HUBBLE_IMAGE}" \ HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \ HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \ @@ -43,6 +44,33 @@ render() { compose_auth "$@" config --format json > "${output}" } +# Same render with an explicit host value, to prove the variable reaches the +# Server environment rather than only defaulting there. +render_with_timeout() { + local output="$1" timeout="$2" + shift 2 + env HG_SERVER_STARTUP_TIMEOUT_S="${timeout}" \ + HUGEGRAPH_VERSION="${VERSION}" \ + HUBBLE_IMAGE="${RENDER_HUBBLE_IMAGE}" \ + HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \ + HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \ + docker compose "$@" config --format json > "${output}" +} + +assert_startup_timeout() { + local rendered="$1" expected="$2" service + shift 2 + for service in "$@"; do + jq -e --arg s "${service}" --arg v "${expected}" \ + '.services[$s].environment.HG_SERVER_STARTUP_TIMEOUT_S == $v' \ + "${rendered}" >/dev/null || { + echo "expected ${service} HG_SERVER_STARTUP_TIMEOUT_S=${expected}" \ + "in ${rendered}" >&2 + return 1 + } + done +} + assert_file_property() { local file="$1" local property="$2" @@ -263,6 +291,23 @@ run_render() { assert_ha "${RENDER_DIR}/ha.json" assert_dev_override "${RENDER_DIR}/dev.json" \ "${RENDER_DIR}/override.json" + + # An absent host value renders the entrypoint default in every topology, + # and a host value overrides it, so the container budget can be aligned + # with the healthcheck start period without editing the Compose files. + assert_startup_timeout "${RENDER_DIR}/standalone.json" 120 server + assert_startup_timeout "${RENDER_DIR}/hstore.json" 120 server + assert_startup_timeout "${RENDER_DIR}/ha.json" 120 server0 server1 server2 + render_with_timeout "${RENDER_DIR}/standalone-timeout.json" 450 \ + -f "${DOCKER_DIR}/docker-compose.yml" + render_with_timeout "${RENDER_DIR}/hstore-timeout.json" 450 \ + -f "${DOCKER_DIR}/docker-compose-hstore.yml" + render_with_timeout "${RENDER_DIR}/ha-timeout.json" 450 \ + -f "${DOCKER_DIR}/docker-compose-3pd-3store-3server.yml" + assert_startup_timeout "${RENDER_DIR}/standalone-timeout.json" 450 server + assert_startup_timeout "${RENDER_DIR}/hstore-timeout.json" 450 server + assert_startup_timeout "${RENDER_DIR}/ha-timeout.json" 450 \ + server0 server1 server2 echo "Compose render contracts passed" } diff --git a/hugegraph-server/hugegraph-dist/docker/README.md b/hugegraph-server/hugegraph-dist/docker/README.md index 07f5c5ae08..58ee8f18dd 100644 --- a/hugegraph-server/hugegraph-dist/docker/README.md +++ b/hugegraph-server/hugegraph-dist/docker/README.md @@ -156,6 +156,11 @@ to change it: docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hugegraph/hugegraph:1.7.0 ``` +The Compose topologies under `docker/` pass the variable through to every +Server with an unset-only default, so `HG_SERVER_STARTUP_TIMEOUT_S=450 docker +compose up -d` raises the budget without editing a Compose file, while leaving +it unset renders the same 120. + Raise it on slow or contended hosts, and wherever an orchestrator already owns the startup budget through a probe of its own: a startup probe cannot extend a container that has already ended the JVM it was waiting for. Anything outside diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh index 6a38520bb3..42137dc3b5 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh @@ -35,7 +35,10 @@ backend=rocksdb EOF cat > "${TEST_HOME}/bin/start-hugegraph.sh" <<'EOF' #!/usr/bin/env bash -printf '%s\n' "$*" >> ./docker/start-hugegraph-args +# One argument per line, so an assertion can read the exact -t value rather +# than substring-matching a flattened "$*", where -t 1200 contains -t 120. +printf '%s\n' "$@" > ./docker/start-hugegraph-argv +printf 'called\n' >> ./docker/start-hugegraph-calls exit 0 EOF cat > "${TEST_HOME}/bin/init-store.sh" <<'EOF' @@ -233,28 +236,63 @@ rm -f "${TEST_HOME}/docker/init_complete" ) grep -Fqx -- '-n' "${TEST_HOME}/docker/init-store-password" -last_start_args() { tail -n 1 "${TEST_HOME}/docker/start-hugegraph-args"; } +# The value start-hugegraph.sh actually received for -t, read from the +# recorded argument vector so that -t 1200 can never satisfy an assertion +# that wants 120. +last_start_timeout() { + local previous="" argument + while IFS= read -r argument; do + if [[ "${previous}" == "-t" ]]; then + printf '%s\n' "${argument}" + return 0 + fi + previous="${argument}" + done < "${TEST_HOME}/docker/start-hugegraph-argv" + return 1 +} -[[ "$(last_start_args)" == *"-t 120"* ]] +# Spelled with an explicit exit rather than a bare [[ ]]: bash 3.2, still the +# /bin/bash of macOS, does not apply set -e to a failing [[ ]], so a bare +# assertion reports PASS there while CI catches the regression. +assert_start_timeout() { + local expected="$1" actual + if ! actual=$(last_start_timeout); then + echo "start-hugegraph.sh received no -t argument" >&2 + exit 1 + fi + if [[ "${actual}" != "${expected}" ]]; then + echo "expected start-hugegraph.sh -t ${expected}, got -t ${actual}" >&2 + exit 1 + fi +} + +# An absent variable keeps the historical default. env -u rather than a bare +# subshell: a child shell inherits an exported HG_SERVER_STARTUP_TIMEOUT_S, so +# without it this case would silently exercise whatever the developer exported. +( + cd "${TEST_HOME}" + env -u HG_SERVER_STARTUP_TIMEOUT_S bash ./docker-entrypoint.sh +) +assert_start_timeout 120 ( cd "${TEST_HOME}" HG_SERVER_STARTUP_TIMEOUT_S=450 bash ./docker-entrypoint.sh ) -[[ "$(last_start_args)" == *"-t 450"* ]] +assert_start_timeout 450 ( cd "${TEST_HOME}" HG_SERVER_STARTUP_TIMEOUT_S=86400 bash ./docker-entrypoint.sh ) -[[ "$(last_start_args)" == *"-t 86400"* ]] +assert_start_timeout 86400 # An empty value is a set value, not an absent one: Compose writes it whenever # an interpolated host variable is missing. 2m is the shape of a typo, and the # two large values bracket the point where the deadline arithmetic in # wait_for_startup would wrap negative and end the wait before its first probe. for invalid_timeout in "" " " 0 +5 2m 86401 9223372036854775807; do - start_calls_before_invalid=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args") + start_calls_before_invalid=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-calls") init_calls_before_invalid=$(wc -l < "${TEST_HOME}/docker/init-store-calls") if ( cd "${TEST_HOME}" @@ -265,28 +303,25 @@ for invalid_timeout in "" " " 0 +5 2m 86401 9223372036854775807; do exit 1 fi # The server must not have started, and the guard must have run ahead of - # init-store, as the comment above it in the entrypoint claims. Spelled - # with an explicit exit rather than a bare [[ ]]: bash 3.2, still the - # /bin/bash of macOS, does not apply set -e to a failing [[ ]], so a bare - # assertion passes silently there while CI catches the regression. - [[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args")" -eq \ - "${start_calls_before_invalid}" ]] || { + # init-store, as the comment above it in the entrypoint claims. + if [[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-calls")" -ne \ + "${start_calls_before_invalid}" ]]; then echo "startup timeout '${invalid_timeout}' started the server" >&2 exit 1 - } - [[ "$(wc -l < "${TEST_HOME}/docker/init-store-calls")" -eq \ - "${init_calls_before_invalid}" ]] || { + fi + if [[ "$(wc -l < "${TEST_HOME}/docker/init-store-calls")" -ne \ + "${init_calls_before_invalid}" ]]; then echo "startup timeout '${invalid_timeout}' was rejected only after" \ "init-store ran" >&2 exit 1 - } + fi done -# An unset variable still keeps the historical default. +# Still the default once the rejected values are out of the way. ( cd "${TEST_HOME}" - bash ./docker-entrypoint.sh + env -u HG_SERVER_STARTUP_TIMEOUT_S bash ./docker-entrypoint.sh ) -[[ "$(last_start_args)" == *"-t 120"* ]] +assert_start_timeout 120 echo "PASS: Docker entrypoint configures HStore discovery and authentication" From f2c6a1ff4474cfb9d26506bb6d378d4963636f2d Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 6 Sep 2026 12:34:58 +0530 Subject: [PATCH 4/7] fix(docker): cover the empty startup timeout in the render test and document it Third review follow-up. The render assertions could not see the "-" against ":-" choice the rest of the change rests on: unset renders 120 and 450 renders 450 under both spellings, and the whole suite passed against a ":-" rewrite. Only an explicitly empty host value separates them, and that value has to reach the container for the entrypoint to reject it. Every topology is now rendered with an empty value and asserted to carry one. render_with_timeout no longer repeats compose_auth's environment. The two differ by one variable, so that is now an array the override rebinds in a subshell, and a variable added to compose_auth reaches both renders instead of only the baseline. The comment above the assertions claimed alignment with the healthcheck budget in both directions. The healthcheck values are literals giving every Server about 360 seconds, and the documented "up -d --wait" gives up there, so the knob aligns downward only and the comment now says so. The two Compose files that carried a bare unset-only default without the reason now carry the same note as the standalone file, and docker/README.md, the file Compose users read, documents the variable beside the other host knobs. Verified: the render suite fails a ":-" rewrite with the expected empty-value message, a variable added to compose_auth alone now reaches all three renders, and both suites pass on bash 3.2.57 and 5.3.15. --- docker/README.md | 18 ++++++++ docker/docker-compose-3pd-3store-3server.yml | 2 + docker/docker-compose-hstore.yml | 2 + docker/test-compose.sh | 45 +++++++++++++++----- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/docker/README.md b/docker/README.md index 0bb74cf81f..bd1763b154 100644 --- a/docker/README.md +++ b/docker/README.md @@ -243,6 +243,24 @@ The Hubble `latest` image is expected to work with HugeGraph Server 1.7 and Server `latest`; compatibility with versions older than 1.7 is not promised. Pin immutable image references when reproducibility is required. +### Server startup timeout + +Every topology gives each Server 120 seconds to answer on its REST port before +the container gives up. Raise it on a slow or contended host: + +```bash +HG_SERVER_STARTUP_TIMEOUT_S=450 \ +docker compose -f docker-compose-hstore.yml up -d +``` + +Leaving the variable unset keeps 120. Setting it to an empty value is an error +rather than a silent default, so a missing value in your own script is not +mistaken for a deliberate one. The Server health check keeps a separate budget +of roughly 360 seconds that this variable does not move, and `up -d --wait` +gives up there, so raising the startup timeout past that budget needs a Compose +edit as well. See [the Server docker README](../hugegraph-server/hugegraph-dist/docker/README.md#7-server-startup-timeout) +for the accepted range and the health check details. + ### Data persistence Each topology creates its own normal Compose network and named volumes. No diff --git a/docker/docker-compose-3pd-3store-3server.yml b/docker/docker-compose-3pd-3store-3server.yml index 833dde8682..9c066242e4 100644 --- a/docker/docker-compose-3pd-3store-3server.yml +++ b/docker/docker-compose-3pd-3store-3server.yml @@ -68,6 +68,8 @@ x-server-environment: &server-environment HG_SERVER_MIN_FREE_MEMORY: "0" HG_SERVER_INIT_STORE_ENABLED: "false" HG_SERVER_REQUIRE_AUTH_TOKEN_SECRET: "true" + # Unset-only default: ":-" would turn an empty host value into 120 + # silently, which the entrypoint rejects on purpose. HG_SERVER_STARTUP_TIMEOUT_S: ${HG_SERVER_STARTUP_TIMEOUT_S-120} HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-} PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:-} diff --git a/docker/docker-compose-hstore.yml b/docker/docker-compose-hstore.yml index e15f7e7d0f..f6f20bd896 100644 --- a/docker/docker-compose-hstore.yml +++ b/docker/docker-compose-hstore.yml @@ -92,6 +92,8 @@ services: HG_SERVER_REST_URL: http://server:8080 HG_SERVER_MIN_FREE_MEMORY: "0" HG_SERVER_INIT_STORE_ENABLED: "false" + # Unset-only default: ":-" would turn an empty host value into 120 + # silently, which the entrypoint rejects on purpose. HG_SERVER_STARTUP_TIMEOUT_S: ${HG_SERVER_STARTUP_TIMEOUT_S-120} HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-} PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:-} diff --git a/docker/test-compose.sh b/docker/test-compose.sh index 327613cd83..467e98e8a0 100644 --- a/docker/test-compose.sh +++ b/docker/test-compose.sh @@ -29,8 +29,14 @@ ACTIVE_PROJECT="" ACTIVE_FILES=() RENDER_DIR="" +# How the render sees HG_SERVER_STARTUP_TIMEOUT_S. Stripped by default, so a +# baseline render shows the Compose default whatever the developer exported; +# render_with_timeout swaps in a value. Kept as one array rather than a second +# copy of the environment below, so a variable added there reaches both renders. +STARTUP_TIMEOUT_ENV=(-u HG_SERVER_STARTUP_TIMEOUT_S) + compose_auth() { - env -u HG_SERVER_STARTUP_TIMEOUT_S \ + env "${STARTUP_TIMEOUT_ENV[@]}" \ HUGEGRAPH_VERSION="${VERSION}" \ HUBBLE_IMAGE="${RENDER_HUBBLE_IMAGE}" \ HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \ @@ -45,16 +51,16 @@ render() { } # Same render with an explicit host value, to prove the variable reaches the -# Server environment rather than only defaulting there. +# Server environment rather than only defaulting there. An empty value is a +# set value and must survive to the container, which is what separates the +# Compose files' "-" from ":-". render_with_timeout() { local output="$1" timeout="$2" shift 2 - env HG_SERVER_STARTUP_TIMEOUT_S="${timeout}" \ - HUGEGRAPH_VERSION="${VERSION}" \ - HUBBLE_IMAGE="${RENDER_HUBBLE_IMAGE}" \ - HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \ - HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \ - docker compose "$@" config --format json > "${output}" + ( + STARTUP_TIMEOUT_ENV=(HG_SERVER_STARTUP_TIMEOUT_S="${timeout}") + compose_auth "$@" config --format json + ) > "${output}" } assert_startup_timeout() { @@ -292,9 +298,12 @@ run_render() { assert_dev_override "${RENDER_DIR}/dev.json" \ "${RENDER_DIR}/override.json" - # An absent host value renders the entrypoint default in every topology, - # and a host value overrides it, so the container budget can be aligned - # with the healthcheck start period without editing the Compose files. + # An absent host value renders the entrypoint default in every topology and + # a host value overrides it, so the container budget can be lowered to meet + # the healthcheck budget without editing the Compose files. Raising it past + # that budget still needs a file edit: the healthcheck literals give every + # Server about 360 seconds, and the documented "up -d --wait" gives up + # there, so this knob aligns the two downward only. assert_startup_timeout "${RENDER_DIR}/standalone.json" 120 server assert_startup_timeout "${RENDER_DIR}/hstore.json" 120 server assert_startup_timeout "${RENDER_DIR}/ha.json" 120 server0 server1 server2 @@ -308,6 +317,20 @@ run_render() { assert_startup_timeout "${RENDER_DIR}/hstore-timeout.json" 450 server assert_startup_timeout "${RENDER_DIR}/ha-timeout.json" 450 \ server0 server1 server2 + + # The unset and override cases above hold under ":-" too. Only an empty + # host value separates the spellings, and it has to reach the container so + # the entrypoint can reject it, as README section 7 promises. + render_with_timeout "${RENDER_DIR}/standalone-empty.json" "" \ + -f "${DOCKER_DIR}/docker-compose.yml" + render_with_timeout "${RENDER_DIR}/hstore-empty.json" "" \ + -f "${DOCKER_DIR}/docker-compose-hstore.yml" + render_with_timeout "${RENDER_DIR}/ha-empty.json" "" \ + -f "${DOCKER_DIR}/docker-compose-3pd-3store-3server.yml" + assert_startup_timeout "${RENDER_DIR}/standalone-empty.json" "" server + assert_startup_timeout "${RENDER_DIR}/hstore-empty.json" "" server + assert_startup_timeout "${RENDER_DIR}/ha-empty.json" "" \ + server0 server1 server2 echo "Compose render contracts passed" } From 585b099493ebf004621075e0bda442ac0ce1ab1d Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 6 Sep 2026 21:11:50 +0530 Subject: [PATCH 5/7] docs(docker): keep the timeout example inside the health check budget The example raised HG_SERVER_STARTUP_TIMEOUT_S to 450 while the Server health check in every Compose topology allows roughly 360 seconds, so on the slow host the example is written for the pasted command fails: Hubble gates on depends_on: condition: service_healthy, which a plain "up -d" honours, and Compose reports the Server dependency unhealthy at 360. Lower the example to 300 and say that the plain "up -d" path hits the same wall as "up -d --wait". Also strip HG_SERVER_STARTUP_TIMEOUT_S in compose_active, matching the render path. Without it a developer who exports the variable empty gets Server containers stopped by the new guard during "test-compose.sh smoke", with no sign that their shell caused it. --- docker/README.md | 10 ++++++---- docker/test-compose.sh | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docker/README.md b/docker/README.md index bd1763b154..d849c00c37 100644 --- a/docker/README.md +++ b/docker/README.md @@ -249,16 +249,18 @@ Every topology gives each Server 120 seconds to answer on its REST port before the container gives up. Raise it on a slow or contended host: ```bash -HG_SERVER_STARTUP_TIMEOUT_S=450 \ +HG_SERVER_STARTUP_TIMEOUT_S=300 \ docker compose -f docker-compose-hstore.yml up -d ``` Leaving the variable unset keeps 120. Setting it to an empty value is an error rather than a silent default, so a missing value in your own script is not mistaken for a deliberate one. The Server health check keeps a separate budget -of roughly 360 seconds that this variable does not move, and `up -d --wait` -gives up there, so raising the startup timeout past that budget needs a Compose -edit as well. See [the Server docker README](../hugegraph-server/hugegraph-dist/docker/README.md#7-server-startup-timeout) +of roughly 360 seconds that this variable does not move. `up -d --wait` gives +up there, and so does a plain `up -d`, because Hubble waits on the Server with +`depends_on: condition: service_healthy` in every topology. Keep the startup +timeout inside that budget, or raise the Server health check in the Compose +file alongside it. See [the Server docker README](../hugegraph-server/hugegraph-dist/docker/README.md#7-server-startup-timeout) for the accepted range and the health check details. ### Data persistence diff --git a/docker/test-compose.sh b/docker/test-compose.sh index 467e98e8a0..5ac61d6967 100644 --- a/docker/test-compose.sh +++ b/docker/test-compose.sh @@ -335,7 +335,8 @@ run_render() { } compose_active() { - env HUGEGRAPH_VERSION="${HUGEGRAPH_VERSION:-latest}" \ + env -u HG_SERVER_STARTUP_TIMEOUT_S \ + HUGEGRAPH_VERSION="${HUGEGRAPH_VERSION:-latest}" \ HUBBLE_IMAGE="${HUBBLE_IMAGE:-hugegraph/hubble:latest}" \ HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \ HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \ From 7e2818c7b77bb5641d2f62c2d3a2913a54561ce9 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 6 Sep 2026 21:19:58 +0530 Subject: [PATCH 6/7] docs(docker): align the Compose timeout example across READMEs The Server docker README still showed 450 in its Compose example, while docker/README.md now shows 300. The Compose files give the Server health check roughly 360 seconds, and Hubble gates on it with depends_on: condition: service_healthy, so a 450 second startup budget makes docker compose up -d exit non-zero. Match the two READMEs at 300. The docker run example keeps 450. That path does not block on health, and the section already explains the 135 second image health check and how to raise it with --health-start-period. --- hugegraph-server/hugegraph-dist/docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hugegraph-server/hugegraph-dist/docker/README.md b/hugegraph-server/hugegraph-dist/docker/README.md index 58ee8f18dd..27897f4b6d 100644 --- a/hugegraph-server/hugegraph-dist/docker/README.md +++ b/hugegraph-server/hugegraph-dist/docker/README.md @@ -157,7 +157,7 @@ docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hug ``` The Compose topologies under `docker/` pass the variable through to every -Server with an unset-only default, so `HG_SERVER_STARTUP_TIMEOUT_S=450 docker +Server with an unset-only default, so `HG_SERVER_STARTUP_TIMEOUT_S=300 docker compose up -d` raises the budget without editing a Compose file, while leaving it unset renders the same 120. From f24eaaf597fe216f2de89acb320b79ca69bb9951 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Mon, 7 Sep 2026 11:11:37 +0530 Subject: [PATCH 7/7] docs(docker): fold the startup timeout into the health check section A whole top-level section for one environment variable was more room than the knob deserves, and it wrapped at about 70 characters where the rest of the file runs long. Section 7 is gone: the variable is now two sentences at the end of section 6, next to the health check it interacts with, and the budget arithmetic and the docker run example sit in a collapsed block for readers who need them. docker/README.md gets the same treatment. Net 26 lines lighter. The dead #7-server-startup-timeout anchor and the stale "section 7" comment in test-compose.sh are updated with it. --- docker/README.md | 21 ++++-------- docker/test-compose.sh | 2 +- .../hugegraph-dist/docker/README.md | 33 +++++-------------- 3 files changed, 15 insertions(+), 41 deletions(-) diff --git a/docker/README.md b/docker/README.md index d849c00c37..8ed7ea8faf 100644 --- a/docker/README.md +++ b/docker/README.md @@ -245,23 +245,14 @@ Pin immutable image references when reproducibility is required. ### Server startup timeout -Every topology gives each Server 120 seconds to answer on its REST port before -the container gives up. Raise it on a slow or contended host: +Every topology gives each Server 120 seconds to answer on its REST port before the container gives up. Raise it on a slow or contended host with `HG_SERVER_STARTUP_TIMEOUT_S=300 docker compose -f docker-compose-hstore.yml up -d`. Leaving it unset keeps 120; an empty value is rejected rather than treated as a silent default, so a missing value in your own script is not mistaken for a deliberate one. -```bash -HG_SERVER_STARTUP_TIMEOUT_S=300 \ -docker compose -f docker-compose-hstore.yml up -d -``` +
+Keeping it inside the health check budget + +The Server health check keeps a separate budget of roughly 360 seconds that this variable does not move. `up -d --wait` gives up there, and so does a plain `up -d`, because Hubble waits on the Server with `depends_on: condition: service_healthy` in every topology. Keep the startup timeout inside that budget, or raise the Server health check in the Compose file alongside it. [The Server docker README](../hugegraph-server/hugegraph-dist/docker/README.md#6-process-supervision--health-checks) has the accepted range and the `docker run` equivalents. -Leaving the variable unset keeps 120. Setting it to an empty value is an error -rather than a silent default, so a missing value in your own script is not -mistaken for a deliberate one. The Server health check keeps a separate budget -of roughly 360 seconds that this variable does not move. `up -d --wait` gives -up there, and so does a plain `up -d`, because Hubble waits on the Server with -`depends_on: condition: service_healthy` in every topology. Keep the startup -timeout inside that budget, or raise the Server health check in the Compose -file alongside it. See [the Server docker README](../hugegraph-server/hugegraph-dist/docker/README.md#7-server-startup-timeout) -for the accepted range and the health check details. +
### Data persistence diff --git a/docker/test-compose.sh b/docker/test-compose.sh index 5ac61d6967..7673036481 100644 --- a/docker/test-compose.sh +++ b/docker/test-compose.sh @@ -320,7 +320,7 @@ run_render() { # The unset and override cases above hold under ":-" too. Only an empty # host value separates the spellings, and it has to reach the container so - # the entrypoint can reject it, as README section 7 promises. + # the entrypoint can reject it, as README section 6 promises. render_with_timeout "${RENDER_DIR}/standalone-empty.json" "" \ -f "${DOCKER_DIR}/docker-compose.yml" render_with_timeout "${RENDER_DIR}/hstore-empty.json" "" \ diff --git a/hugegraph-server/hugegraph-dist/docker/README.md b/hugegraph-server/hugegraph-dist/docker/README.md index 27897f4b6d..aa76092b82 100644 --- a/hugegraph-server/hugegraph-dist/docker/README.md +++ b/hugegraph-server/hugegraph-dist/docker/README.md @@ -145,34 +145,17 @@ native `HEALTHCHECK` instructions. `docker ps` shows real health status: The entrypoints supervise the Java process directly — when Java exits, the container exits. If started with a restart policy (the provided compose files use `restart: unless-stopped`), Docker will bring it back automatically. The old cron-based monitor (`-m true`) is for VM/bare-metal deployments only and is not used in Docker images. -## 7. Server Startup Timeout +`HG_SERVER_STARTUP_TIMEOUT_S` sets how long the Server entrypoint waits for that REST port before ending the container: whole seconds from 1 to 86400, 120 by default. Anything outside the range, an empty value included, stops the container at startup rather than falling back silently. The Compose topologies under `docker/` pass it through with an unset-only default, so `HG_SERVER_STARTUP_TIMEOUT_S=300 docker compose up -d` raises it without editing a Compose file. -The entrypoint gives the Server a fixed budget to answer on its REST port and -ends the container when the budget runs out. It is 120 seconds by default. Set -`HG_SERVER_STARTUP_TIMEOUT_S` to a whole number of seconds between 1 and 86400 -to change it: +
+Startup budget and health check budget are two clocks + +Raise the startup budget on slow or contended hosts, and wherever an orchestrator already owns it through a probe of its own: a startup probe cannot extend a container that has already ended the JVM it was waiting for. ```bash docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hugegraph/hugegraph:1.7.0 ``` -The Compose topologies under `docker/` pass the variable through to every -Server with an unset-only default, so `HG_SERVER_STARTUP_TIMEOUT_S=300 docker -compose up -d` raises the budget without editing a Compose file, while leaving -it unset renders the same 120. - -Raise it on slow or contended hosts, and wherever an orchestrator already owns -the startup budget through a probe of its own: a startup probe cannot extend a -container that has already ended the JVM it was waiting for. Anything outside -the accepted range, an empty value included, stops the container at startup -instead of silently falling back to the default. - -Raising this budget does not move the health check described in section 6, -which runs on a clock of its own. The images set `--interval=15s ---start-period=90s --retries=3`, so a container given a longer startup budget -is reported `unhealthy` around 135 seconds while the entrypoint is still -legitimately waiting; raise it with `--health-start-period` on `docker run`. -The Compose files replace those values with their own (`start_period: 60s`, -`interval: 10s`, `retries: 30`, so roughly 360 seconds), and anything gated on -`depends_on: condition: service_healthy`, Hubble included, waits on that budget -rather than on this variable. Move the two together. +Raising it does not move the health check above. The images set `--interval=15s --start-period=90s --retries=3`, so a container given a longer startup budget is reported `unhealthy` around 135 seconds while the entrypoint is still legitimately waiting; raise it with `--health-start-period` on `docker run`. The Compose files replace those values with their own (`start_period: 60s`, `interval: 10s`, `retries: 30`, so roughly 360 seconds), and anything gated on `depends_on: condition: service_healthy`, Hubble included, waits on that budget rather than on this variable. Move the two together. + +