From c30975329c16afd6221cc5a0caeec6d907738683 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Sun, 9 Aug 2026 16:54:36 -0700 Subject: [PATCH] feat: publish a prebuilt OpenLDAP-with-nestgroup base image (#187) Extracts Dockerfile.openldap's `ldapbuild` stage (compile OpenLDAP from source for the nestgroup overlay, ~5 min, dependent on git.openldap.org being reachable) into its own Dockerfile, built and pushed to ghcr.io/theta42/openldap-nestgroup by this workflow whenever the pinned commit changes. This commit only adds the new image + workflow; Dockerfile.openldap itself still compiles from source. A follow-up change switches it to FROM the published image once this workflow has run once and the image exists. Co-authored-by: Claude Sonnet 5 --- .github/workflows/build-openldap-image.yml | 58 +++++++++++++++++ Dockerfile.openldap-builder | 75 ++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 .github/workflows/build-openldap-image.yml create mode 100644 Dockerfile.openldap-builder diff --git a/.github/workflows/build-openldap-image.yml b/.github/workflows/build-openldap-image.yml new file mode 100644 index 0000000..2cbda6b --- /dev/null +++ b/.github/workflows/build-openldap-image.yml @@ -0,0 +1,58 @@ +name: Build OpenLDAP Base Image + +# Publishes ghcr.io/theta42/openldap-nestgroup, the prebuilt slapd-with- +# nestgroup image Dockerfile.openldap's `ldapbuild` stage pulls FROM instead +# of compiling from source on every build (see Dockerfile.openldap-builder +# for why, and the ~5 minute + git.openldap.org-dependent cost it replaces). +# +# Runs only when the builder Dockerfile changes -- bumping OPENLDAP_COMMIT in +# it is the only reason this image should ever need rebuilding -- or on +# manual dispatch. +on: + push: + branches: [master] + paths: + - 'Dockerfile.openldap-builder' + - '.github/workflows/build-openldap-image.yml' + workflow_dispatch: {} + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Single source of truth for the tag: the ARG default in the Dockerfile + # itself, not a value duplicated into this workflow. + - name: Resolve pinned OpenLDAP commit + id: commit + run: | + commit=$(grep -oP '^ARG OPENLDAP_COMMIT=\K[0-9a-f]+' Dockerfile.openldap-builder) + if [ -z "$commit" ]; then + echo "::error::Could not resolve OPENLDAP_COMMIT from Dockerfile.openldap-builder" + exit 1 + fi + echo "commit=$commit" >> "$GITHUB_OUTPUT" + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + file: ./Dockerfile.openldap-builder + build-args: | + OPENLDAP_COMMIT=${{ steps.commit.outputs.commit }} + push: true + tags: | + ghcr.io/theta42/openldap-nestgroup:${{ steps.commit.outputs.commit }} + ghcr.io/theta42/openldap-nestgroup:latest diff --git a/Dockerfile.openldap-builder b/Dockerfile.openldap-builder new file mode 100644 index 0000000..9f8a216 --- /dev/null +++ b/Dockerfile.openldap-builder @@ -0,0 +1,75 @@ +# OpenLDAP-with-nestgroup builder, published to +# ghcr.io/theta42/openldap-nestgroup:. +# +# Extracted out of Dockerfile.openldap's `ldapbuild` stage so the ~5 minute +# from-source compile (which also depends on git.openldap.org being up) +# happens once, here, instead of on every `docker build` of the app image -- +# including every CI run's 3-way test matrix. Dockerfile.openldap's ldapbuild +# stage becomes `FROM ghcr.io/theta42/openldap-nestgroup:` and the +# rest of that file (the COPY --from=ldapbuild lines) is unchanged, since +# COPY --from also accepts an external image, not just a local stage name. +# +# Bumping OPENLDAP_COMMIT is a two-step change: update the ARG below, push +# (the build-openldap-image workflow rebuilds+republishes the tag on changes +# to this file), then update the matching FROM line in Dockerfile.openldap. +# +# See Dockerfile.openldap's own "OpenLDAP from source" comment for *why* +# from-source at all (the nestgroup overlay, ITS#10161) and the LMDB format +# note (master's 1.0.0 vs 2.6.x's 0.9.x). +FROM node:20-alpine AS build + +# groff is not optional despite producing nothing we ship: the build descends +# into doc/man unconditionally and its Makefile calls soelim, which groff +# provides. Without it the whole `make` fails at the man-page stage +# ("soelim: not found") long after slapd itself has compiled fine. +RUN apk add --no-cache \ + build-base autoconf automake libtool \ + openssl-dev cyrus-sasl-dev \ + git make pkgconf util-linux-dev groff + +# Pinned to an exact commit, not a branch tip -- see Dockerfile.openldap for +# why (this is the directory server the whole lab authenticates against). +ARG OPENLDAP_COMMIT=350e9eb38b2270c2bad97c61ee02e85fb8f3196d + +WORKDIR /src +RUN git init -q . \ + && git remote add origin https://git.openldap.org/openldap/openldap.git \ + && git fetch -q --depth 1 origin "${OPENLDAP_COMMIT}" \ + && git checkout -q FETCH_HEAD \ + && git rev-parse HEAD > /opt-openldap-commit.txt + +# Overlays are built as loadable modules (=mod) because docker-entrypoint.sh +# `moduleload`s them individually; nestgroup joins that set. +RUN ./configure \ + --prefix=/opt/openldap \ + --enable-slapd \ + --enable-modules \ + --enable-mdb \ + --enable-memberof=mod \ + --enable-refint=mod \ + --enable-ppolicy=mod \ + --enable-dynlist=mod \ + --enable-nestgroup=mod \ + --enable-syncprov=mod \ + --enable-auditlog=mod \ + --with-tls=openssl \ + --with-cyrus-sasl \ + && make depend \ + && make -j"$(nproc)" \ + && make install + +# pw-sha2 provides {SSHA512}, which every existing user password is stored as. +# It lives in contrib and is not covered by the configure flags above, so it is +# built separately against the just-built tree -- omitting it would make every +# user password unverifiable. +RUN cd contrib/slapd-modules/passwd/sha2 \ + && make prefix=/opt/openldap OPENLDAP_SRC=/src \ + && cp .libs/pw-sha2.so* /opt/openldap/libexec/openldap/ + +# Pure artifact holder -- no shell, no package manager, nothing but the +# compiled tree. Dockerfile.openldap's COPY --from=ldapbuild only ever reads +# files, never RUNs anything in this stage, so scratch is sufficient and +# keeps the published image (and every pull of it) as small as possible. +FROM scratch +COPY --from=build /opt/openldap /opt/openldap +COPY --from=build /opt-openldap-commit.txt /opt-openldap-commit.txt