From 92cfe956e41a0beee435305c090ffc9daad72445 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 25 Aug 2026 08:00:34 +0000
Subject: [PATCH] Report the OpenDJ container healthy only once its bootstrap has succeeded (#898)
---
opendj-packages/opendj-docker/run.sh | 36 ++++++++++++++++-
opendj-packages/opendj-docker/Dockerfile-alpine | 11 +++++
opendj-packages/opendj-docker/README.md | 25 ++++++++++++
opendj-packages/opendj-docker/bootstrap/setup.sh | 29 ++++++++++++--
opendj-packages/opendj-docker/Dockerfile | 11 +++++
5 files changed, 102 insertions(+), 10 deletions(-)
diff --git a/opendj-packages/opendj-docker/Dockerfile b/opendj-packages/opendj-docker/Dockerfile
index ab4a45b..9181389 100644
--- a/opendj-packages/opendj-docker/Dockerfile
+++ b/opendj-packages/opendj-docker/Dockerfile
@@ -33,6 +33,8 @@
ENV BACKEND_TYPE="je"
ENV BACKEND_DB_DIRECTORY="db"
#ENV SETUP_ARGS
+# written by run.sh once the bootstrap has succeeded, read by the health check below
+ENV BOOTSTRAP_COMPLETE="/opt/opendj/.bootstrap-complete"
ARG OPENDJ_DIST_FILENAME=opendj.zip
@@ -66,6 +68,13 @@
USER $OPENDJ_USER
-HEALTHCHECK --interval=30s --timeout=30s --start-period=1s --retries=3 CMD opendj/bin/ldapsearch --hostname localhost --port $LDAPS_PORT --bindDN "$ROOT_USER_DN" --bindPassword "${ROOT_PASSWORD:-password}" --useSsl --trustAll --baseDN "" --searchScope base "(objectClass=*)" 1.1 || exit 1
+# "healthy" has to mean the instance is ready to serve, not just that it answers: setup
+# starts the server in the middle of the bootstrap, before the backend of BASE_DN is
+# created and its entries imported, so probing the root DSE alone reports ready while a
+# search of BASE_DN still fails with "No Such Entry". Testing the marker first also keeps
+# the probe from launching a JVM every interval until the bootstrap is through. The start
+# period is what a bootstrap importing SAMPLE_DATA into a small container can take; a
+# probe that succeeds ends it early, and a bootstrap that failed never writes the marker.
+HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 CMD test -f "$BOOTSTRAP_COMPLETE" && opendj/bin/ldapsearch --hostname localhost --port $LDAPS_PORT --bindDN "$ROOT_USER_DN" --bindPassword "${ROOT_PASSWORD:-password}" --useSsl --trustAll --baseDN "" --searchScope base "(objectClass=*)" 1.1 || exit 1
ENTRYPOINT ["/opt/opendj/run.sh"]
diff --git a/opendj-packages/opendj-docker/Dockerfile-alpine b/opendj-packages/opendj-docker/Dockerfile-alpine
index 65f1f0b..5f2635f 100644
--- a/opendj-packages/opendj-docker/Dockerfile-alpine
+++ b/opendj-packages/opendj-docker/Dockerfile-alpine
@@ -33,6 +33,8 @@
ENV BACKEND_TYPE="je"
ENV BACKEND_DB_DIRECTORY="db"
#ENV SETUP_ARGS
+# written by run.sh once the bootstrap has succeeded, read by the health check below
+ENV BOOTSTRAP_COMPLETE="/opt/opendj/.bootstrap-complete"
ARG OPENDJ_DIST_FILENAME=opendj.zip
@@ -70,6 +72,13 @@
USER $OPENDJ_USER
-HEALTHCHECK --interval=30s --timeout=30s --start-period=1s --retries=3 CMD opendj/bin/ldapsearch --hostname localhost --port $LDAPS_PORT --bindDN "$ROOT_USER_DN" --bindPassword "${ROOT_PASSWORD:-password}" --useSsl --trustAll --baseDN "" --searchScope base "(objectClass=*)" 1.1 || exit 1
+# "healthy" has to mean the instance is ready to serve, not just that it answers: setup
+# starts the server in the middle of the bootstrap, before the backend of BASE_DN is
+# created and its entries imported, so probing the root DSE alone reports ready while a
+# search of BASE_DN still fails with "No Such Entry". Testing the marker first also keeps
+# the probe from launching a JVM every interval until the bootstrap is through. The start
+# period is what a bootstrap importing SAMPLE_DATA into a small container can take; a
+# probe that succeeds ends it early, and a bootstrap that failed never writes the marker.
+HEALTHCHECK --interval=30s --timeout=30s --start-period=5m --retries=3 CMD test -f "$BOOTSTRAP_COMPLETE" && opendj/bin/ldapsearch --hostname localhost --port $LDAPS_PORT --bindDN "$ROOT_USER_DN" --bindPassword "${ROOT_PASSWORD:-password}" --useSsl --trustAll --baseDN "" --searchScope base "(objectClass=*)" 1.1 || exit 1
ENTRYPOINT ["/opt/opendj/run.sh"]
diff --git a/opendj-packages/opendj-docker/README.md b/opendj-packages/opendj-docker/README.md
index 9c40fc3..0b85aa0 100644
--- a/opendj-packages/opendj-docker/README.md
+++ b/opendj-packages/opendj-docker/README.md
@@ -12,11 +12,36 @@
docker run -d -p 1389:1389 -p 1636:1636 -p 4444:4444 --name opendj openidentityplatform/opendj
```
+## Health check
+
+The image reports itself `healthy` once the server answers on `LDAPS_PORT` *and* the whole
+bootstrap has succeeded - the instance, the `userRoot` backend over `BASE_DN`, whatever
+`ADD_BASE_ENTRY` and `SAMPLE_DATA` asked to be imported into it, and the replication asked
+for by `MASTER_SERVER`. Waiting for that status is therefore enough before the first search
+of what the bootstrap was told to create:
+
+```bash
+docker run -d --name opendj -e ADD_BASE_ENTRY=--addBaseEntry openidentityplatform/opendj
+timeout 5m bash -c 'until [ "$(docker inspect -f "{{.State.Health.Status}}" opendj)" = healthy ]; do sleep 5; done'
+```
+
+In Compose the same is `depends_on: { opendj: { condition: service_healthy } }`. Note that
+without `ADD_BASE_ENTRY` nothing creates the base entry, so `BASE_DN` is an empty suffix on
+a healthy container - the health check itself searches the root DSE, which every instance
+serves whatever it was set up to hold.
+
+A bootstrap that imports `SAMPLE_DATA` can take minutes on a small container, which is what
+the start period allows for. A bootstrap that fails - or an upgrade that fails when starting
+over an instance that is already there - never reports healthy: what failed is in `docker
+logs`, and where the server is up at all the container is left running to be looked at,
+turning `unhealthy` once the start period is over.
+
## Environment Variables
| Variable | Default Value | Description |
|-------------------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ADD_BASE_ENTRY | | if set --addBaseEntry , creates base DN entry |
+| SAMPLE_DATA | - | with ADD_BASE_ENTRY set, imports that many generated users under BASE_DN instead of the base entry alone |
| PORT | 1389 | LDAP Listener Port |
| LDAPS_PORT | 1636 | LDAPS Listener Port |
| BASE_DN | dc=example,dc=com | OpenDJ Base DN |
diff --git a/opendj-packages/opendj-docker/bootstrap/setup.sh b/opendj-packages/opendj-docker/bootstrap/setup.sh
index a71782c..c571af0 100755
--- a/opendj-packages/opendj-docker/bootstrap/setup.sh
+++ b/opendj-packages/opendj-docker/bootstrap/setup.sh
@@ -1,5 +1,24 @@
#!/usr/bin/env bash
+# The contents of this file are subject to the terms of the Common Development and
+# Distribution License (the License). You may not use this file except in compliance with the
+# License.
+#
+# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+# specific language governing permission and limitations under the License.
+#
+# When distributing Covered Software, include this CDDL Header Notice in each file and include
+# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+# Header, with the fields enclosed by brackets [] replaced by your own identifying
+# information: "Portions copyright [year] [name of copyright owner]".
+#
+# Portions copyright 2026 3A Systems, LLC.
+
# Default setup script
+#
+# What the instance is made of - the server itself, the backend, the entries the backend was
+# asked to hold - is left to fail this script, which is what run.sh reads to decide whether
+# the container may report itself healthy. The optional schema and data LDIFs below keep the
+# tolerance they were written with.
echo "Setting up default OpenDJ instance"
@@ -30,7 +49,7 @@
--acceptLicense \
--no-prompt \
--noPropertiesFile \
- $SETUP_ARGS
+ $SETUP_ARGS || exit 1
BACKEND_TYPE=${BACKEND_TYPE:-je}
BACKEND_DB_DIRECTORY=${BACKEND_DB_DIRECTORY:-db}
@@ -38,21 +57,21 @@
/opt/opendj/bin/dsconfig create-backend -h localhost -p $ADMIN_PORT --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD" \
--backend-name=userRoot --type $BACKEND_TYPE --set base-dn:$BASE_DN --set "db-directory:$BACKEND_DB_DIRECTORY" \
- --set enabled:true --no-prompt --trustAll
+ --set enabled:true --no-prompt --trustAll || exit 1
if [ "$ADD_BASE_ENTRY" = "--addBaseEntry" ]; then
BASE_TEMPLATE=$(mktemp)
if [ ! -z ${SAMPLE_DATA} ]; then
echo "generating sample data..."
- /opt/opendj/bin/makeldif -o $BASE_TEMPLATE -c suffix="$BASE_DN" -c numusers=$SAMPLE_DATA /opt/opendj/template/config/MakeLDIF/example.template
+ /opt/opendj/bin/makeldif -o $BASE_TEMPLATE -c suffix="$BASE_DN" -c numusers=$SAMPLE_DATA /opt/opendj/template/config/MakeLDIF/example.template || exit 1
/opt/opendj/bin/import-ldif --ldifFile $BASE_TEMPLATE \
- --backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD"
+ --backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD" || exit 1
else
echo "creating base entry..."
BASE_TEMPLATE=$(mktemp)
echo "branch: $BASE_DN" > $BASE_TEMPLATE
/opt/opendj/bin/import-ldif --templateFile $BASE_TEMPLATE \
- --backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD"
+ --backendID=userRoot --bindDN "$ROOT_USER_DN" --bindPassword "$ROOT_PASSWORD" || exit 1
fi
rm $BASE_TEMPLATE
fi
diff --git a/opendj-packages/opendj-docker/run.sh b/opendj-packages/opendj-docker/run.sh
index b79e4e6..9e028b2 100755
--- a/opendj-packages/opendj-docker/run.sh
+++ b/opendj-packages/opendj-docker/run.sh
@@ -23,6 +23,17 @@
cd /opt/opendj
+# The health check probes the server only once this marker is there, so that "healthy"
+# means the instance is bootstrapped rather than merely listening: setup starts the
+# server in the middle of the bootstrap, before the backend holding BASE_DN has been
+# created. Nothing below writes it unless the step it stands for reported success, so a
+# bootstrap that failed leaves the container running to be looked at, but never healthy.
+# It is kept outside ./data because it records what this container has done, not what
+# the volume holds - and a restart of a container replays this script over the writable
+# layer the previous run left behind, so it is cleared before anything else.
+BOOTSTRAP_COMPLETE=${BOOTSTRAP_COMPLETE:-/opt/opendj/.bootstrap-complete}
+rm -f "$BOOTSTRAP_COMPLETE"
+
#if default data folder exists do not change it
if [ ! -d ./db ]; then
echo "/opt/opendj/data" >/opt/opendj/instance.loc && \
@@ -31,7 +42,13 @@
# Instance dir does exist? We start opendj without detach
if [ -d ./data/config ]; then
- sh ./upgrade -n
+ # nothing is bootstrapped here, the instance is already there - but a half-migrated one
+ # is not ready to serve either, so the marker follows the upgrade
+ if sh ./upgrade -n; then
+ touch "$BOOTSTRAP_COMPLETE"
+ else
+ echo "Upgrade failed, this container will not report itself healthy"
+ fi
exec ./bin/start-ds --nodetach
exit
fi
@@ -46,11 +63,18 @@
BOOTSTRAP=${BOOTSTRAP:-/opt/opendj/bootstrap/setup.sh}
echo "Running $BOOTSTRAP"
-sh "${BOOTSTRAP}"
+BOOTSTRAPPED=true
+if ! sh "${BOOTSTRAP}"; then
+ BOOTSTRAPPED=false
+ echo "$BOOTSTRAP failed, this container will not report itself healthy"
+fi
# Check if OPENDJ_REPLICATION_TYPE var is set. If it is - replicate to that server
if [ -n "${MASTER_SERVER}" ] && [ -n "${OPENDJ_REPLICATION_TYPE}" ]; then
- /opt/opendj/bootstrap/replicate.sh
+ if ! /opt/opendj/bootstrap/replicate.sh; then
+ BOOTSTRAPPED=false
+ echo "Replication setup failed, this container will not report itself healthy"
+ fi
fi
# Check if keystores are mounted as a volume, and if so
@@ -63,6 +87,12 @@
cp -f ${SECRET_VOLUME}/key* ${SECRET_VOLUME}/trust* ./data/config 2>/dev/null
fi
+# Everything the instance was asked to be set up with - its backend, its base entry, its
+# replication - is in place from here on, so the health check may start probing the server
+if [ "$BOOTSTRAPPED" = true ]; then
+ touch "$BOOTSTRAP_COMPLETE"
+fi
+
# Opendj is probably already started in detach mode at the install
if (bin/status -n | grep Started); then
echo "OpenDJ is started"
--
Gitblit v1.10.0