From d7330d43b51ac272fe6f40fb993b7d41b6f8c814 Mon Sep 17 00:00:00 2001 From: "Daniel J. Holmes (jaitaiwan)" Date: Fri, 4 Sep 2026 20:16:53 +1000 Subject: [PATCH 1/2] Add UBI9 Server and Keeper images for Red Hat certification Extends the existing amd64/arm64 build matrix (rather than a parallel pipeline) with Red Hat UBI9-based variants of clickhouse-server and clickhouse-keeper, needed as certified operand images for the clickhouse-operator's Red Hat OLM certification path. - docker/server/Dockerfile.ubi9, docker/keeper/Dockerfile.ubi9: verify release SHA-512 checksums, preserve UBI's merged /lib -> /usr/lib layout, include certification metadata/license, run as 101:101. Keeper uses its own standalone entrypoint (its archive installs /usr/bin/clickhouse-keeper, not the Server multicall binary); Server uses its built-in `docker-init` subcommand like the existing images. - ci/jobs/docker_server.py: add "ubi9" to --os, build it from the repo root instead of image.path (needs LICENSE), and load it from .tgz packages like Alpine/keeper rather than .deb like the Ubuntu image. - ci/praktika/yaml_additional_templates.py (and the generated master.yml/pull_request.yml/release_builds.yml): add "-ubi9" to the Grype scan matrix alongside "-alpine", for both Server and Keeper. - sign_and_release.yml: add "-ubi9" to the publish variant matrix. - docker_publish.yml: bump docker/login-action v2 -> v3 (v2 is retired); add a redhat-certification job that submits the immutable multiarch manifest digest to Red Hat Preflight via Pyxis, gated on RED_HAT_CERTIFICATION_ENABLED and only for production -ubi9 builds. - actionlint.yml: declare the altinity-on-demand runner label (already used elsewhere in this workflow) so linting doesn't flag it. Verified locally on both architectures: version reporting, UBI 9.8 identity, license presence, UID/GID 101 (non-root), and a full default-entrypoint smoke test against ClickHouse 26.6.2.158 - Server answered a live client query, Keeper answered ruok -> imok. Also validated end-to-end against the clickhouse-regression QA harness (clickhouse_keeper suite, both images, real Keeper protocol commands). Nothing has been submitted to Red Hat from this branch. --- .github/actionlint.yml | 1 + .github/workflows/docker_publish.yml | 63 ++++++++++++++++- .github/workflows/master.yml | 7 +- .github/workflows/pull_request.yml | 7 +- .github/workflows/release_builds.yml | 7 +- .github/workflows/sign_and_release.yml | 2 +- ci/jobs/docker_server.py | 22 ++++-- ci/praktika/yaml_additional_templates.py | 7 +- docker/README.ubi9.md | 33 +++++++++ docker/keeper/Dockerfile.ubi9 | 73 ++++++++++++++++++++ docker/server/Dockerfile.ubi9 | 88 ++++++++++++++++++++++++ 11 files changed, 299 insertions(+), 11 deletions(-) create mode 100644 docker/README.ubi9.md create mode 100644 docker/keeper/Dockerfile.ubi9 create mode 100644 docker/server/Dockerfile.ubi9 diff --git a/.github/actionlint.yml b/.github/actionlint.yml index 904a548dadd5..5fc6a575eff6 100644 --- a/.github/actionlint.yml +++ b/.github/actionlint.yml @@ -1,6 +1,7 @@ self-hosted-runner: labels: - altinity-builder + - altinity-on-demand - altinity-func-tester - altinity-func-tester-aarch64 - fuzzer-unit-tester diff --git a/.github/workflows/docker_publish.yml b/.github/workflows/docker_publish.yml index 1e59aa8b5b8d..ba87edaa6479 100644 --- a/.github/workflows/docker_publish.yml +++ b/.github/workflows/docker_publish.yml @@ -50,14 +50,17 @@ jobs: runs-on: [self-hosted, altinity-on-demand, altinity-style-checker-aarch64] outputs: image_archives_path: ${{ steps.set_path.outputs.image_archives_path }} + component: ${{ steps.image_info.outputs.component }} + published_image: ${{ steps.image_info.outputs.published_image }} steps: - name: Docker Hub Login - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: Set clickhouse-server version as new tag + id: image_info run: | # Determine "clickhouse-server" or "clickhouse-keeper" echo "Input IMAGE: $IMAGE" @@ -86,10 +89,15 @@ jobs: if [[ "$IMAGE" == *-alpine* ]]; then NEW_TAG="${NEW_TAG}-alpine" fi + if [[ "$IMAGE" == *-ubi9* ]]; then + NEW_TAG="${NEW_TAG}-ubi9" + fi echo "New tag: $NEW_TAG" # Export the new tag echo "NEW_TAG=$NEW_TAG" >> $GITHUB_ENV + echo "component=$COMPONENT" >> $GITHUB_OUTPUT + echo "published_image=altinity/$COMPONENT:$NEW_TAG" >> $GITHUB_OUTPUT - name: Process multiarch manifest run: | @@ -148,3 +156,56 @@ jobs: run: | aws s3 sync image_archives/ "${{ inputs.s3_upload_path }}" + redhat-certification: + name: Submit Red Hat container certification + needs: republish + if: ${{ vars.RED_HAT_CERTIFICATION_ENABLED == 'true' && (github.event.inputs.release_environment || inputs.release_environment) == 'production' && contains(github.event.inputs.docker_image || inputs.docker_image, '-ubi9') }} + runs-on: [self-hosted, altinity-on-demand, altinity-style-checker] + env: + PREFLIGHT_VERSION: 1.21.0 + PREFLIGHT_SHA256: 5e653135503c72f8702bbe31d7643197d12937c68086879133dd6b9650a9a449 + PYXIS_API_TOKEN: ${{ secrets.RED_HAT_PYXIS_API_TOKEN }} + SERVER_COMPONENT_ID: ${{ secrets.RED_HAT_CLICKHOUSE_SERVER_CERTIFICATION_COMPONENT_ID }} + KEEPER_COMPONENT_ID: ${{ secrets.RED_HAT_CLICKHOUSE_KEEPER_CERTIFICATION_COMPONENT_ID }} + PUBLISHED_IMAGE: ${{ needs.republish.outputs.published_image }} + COMPONENT: ${{ needs.republish.outputs.component }} + steps: + - name: Install Preflight + run: | + set -euo pipefail + curl -fsSLo /tmp/preflight \ + "https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/${PREFLIGHT_VERSION}/preflight-linux-amd64" + echo "${PREFLIGHT_SHA256} /tmp/preflight" | sha256sum --check + sudo install -m 0755 /tmp/preflight /usr/local/bin/preflight + + - name: Submit immutable multiarch image + run: | + set -euo pipefail + : "${PYXIS_API_TOKEN:?Set the RED_HAT_PYXIS_API_TOKEN secret}" + case "${COMPONENT}" in + clickhouse-server) component_id="${SERVER_COMPONENT_ID}" ;; + clickhouse-keeper) component_id="${KEEPER_COMPONENT_ID}" ;; + *) echo "Unknown component: ${COMPONENT}" >&2; exit 1 ;; + esac + : "${component_id:?Set the matching Red Hat certification component secret}" + digest=$(docker buildx imagetools inspect \ + "${PUBLISHED_IMAGE}" --format '{{.Manifest.Digest}}') + [[ "${digest}" =~ ^sha256:[0-9a-f]{64}$ ]] + pinned_image="${PUBLISHED_IMAGE%:*}@${digest}" + mkdir -p preflight-artifacts + PFLT_ARTIFACTS=preflight-artifacts \ + PFLT_LOGFILE=preflight.log \ + PFLT_PYXIS_API_TOKEN="${PYXIS_API_TOKEN}" \ + PFLT_CERTIFICATION_COMPONENT_ID="${component_id}" \ + preflight check container "${pinned_image}" --submit + + - name: Upload certification evidence + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: redhat-certification-${{ needs.republish.outputs.component }}-${{ github.run_id }} + path: | + preflight-artifacts/ + preflight.log + if-no-files-found: warn + retention-days: 365 diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index fc49ce2ad074..9092d713e8a7 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -6557,7 +6557,7 @@ jobs: strategy: fail-fast: false matrix: - suffix: ['', '-alpine'] + suffix: ['', '-alpine', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: @@ -6567,11 +6567,16 @@ jobs: GrypeScanKeeper: needs: [config_workflow, docker_keeper_image] if: ${{ !cancelled() && !contains(needs.*.outputs.pipeline_status, 'failure') && !contains(fromJson(needs.config_workflow.outputs.data).workflow_config.cache_success_base64, 'RG9ja2VyIGtlZXBlciBpbWFnZQ==') }} + strategy: + fail-fast: false + matrix: + suffix: ['', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: docker_image: altinityinfra/clickhouse-keeper version: ${{ fromJson(needs.config_workflow.outputs.data).workflow_config.custom_data.version.string }} + tag-suffix: ${{ matrix.suffix }} RegressionTestsRelease: needs: [config_workflow, build_amd_binary] diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ba64d1a171e0..14a84091f087 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -6085,7 +6085,7 @@ jobs: strategy: fail-fast: false matrix: - suffix: ['', '-alpine'] + suffix: ['', '-alpine', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: @@ -6095,11 +6095,16 @@ jobs: GrypeScanKeeper: needs: [config_workflow, docker_keeper_image] if: ${{ !cancelled() && !contains(needs.*.outputs.pipeline_status, 'failure') && !contains(fromJson(needs.config_workflow.outputs.data).workflow_config.cache_success_base64, 'RG9ja2VyIGtlZXBlciBpbWFnZQ==') }} + strategy: + fail-fast: false + matrix: + suffix: ['', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: docker_image: altinityinfra/clickhouse-keeper version: ${{ fromJson(needs.config_workflow.outputs.data).workflow_config.custom_data.version.string }} + tag-suffix: ${{ matrix.suffix }} RegressionTestsRelease: needs: [config_workflow, build_amd_binary, stateless_tests_amd_debug_parallel] diff --git a/.github/workflows/release_builds.yml b/.github/workflows/release_builds.yml index 68f510f46dbe..b42d2c9f6f20 100644 --- a/.github/workflows/release_builds.yml +++ b/.github/workflows/release_builds.yml @@ -1541,7 +1541,7 @@ jobs: strategy: fail-fast: false matrix: - suffix: ['', '-alpine'] + suffix: ['', '-alpine', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: @@ -1551,11 +1551,16 @@ jobs: GrypeScanKeeper: needs: [config_workflow, docker_keeper_image] if: ${{ !cancelled() && !contains(needs.*.outputs.pipeline_status, 'failure') && !contains(fromJson(needs.config_workflow.outputs.data).workflow_config.cache_success_base64, 'RG9ja2VyIGtlZXBlciBpbWFnZQ==') }} + strategy: + fail-fast: false + matrix: + suffix: ['', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: docker_image: altinityinfra/clickhouse-keeper version: ${{ fromJson(needs.config_workflow.outputs.data).workflow_config.custom_data.version.string }} + tag-suffix: ${{ matrix.suffix }} FinishCIReport: if: ${{ !cancelled() && needs.config_workflow.outputs.pipeline_status != '' }} diff --git a/.github/workflows/sign_and_release.yml b/.github/workflows/sign_and_release.yml index 21ecd04576fb..d8157e4841e5 100644 --- a/.github/workflows/sign_and_release.yml +++ b/.github/workflows/sign_and_release.yml @@ -276,7 +276,7 @@ jobs: strategy: matrix: image_type: [server, keeper] - variant: ['', '-alpine'] + variant: ['', '-alpine', '-ubi9'] uses: ./.github/workflows/docker_publish.yml with: docker_image: altinityinfra/clickhouse-${{ matrix.image_type }}:${{ needs.extract-package-info.outputs.docker_version }}${{ matrix.variant }} diff --git a/ci/jobs/docker_server.py b/ci/jobs/docker_server.py index c04e4b774bd6..3eebe3ad5658 100644 --- a/ci/jobs/docker_server.py +++ b/ci/jobs/docker_server.py @@ -70,8 +70,8 @@ def docker_login(relogin: bool = True) -> None: def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="A program to build clickhouse-server image, both alpine and " - "ubuntu versions", + description="Build the supported clickhouse-server and clickhouse-keeper " + "container image variants", ) parser.add_argument( "--tag-type", @@ -101,7 +101,11 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument("--reports", default=True, help=argparse.SUPPRESS) parser.add_argument("--push", action="store_true", help=argparse.SUPPRESS) - parser.add_argument("--os", default=["ubuntu", "alpine", "distroless"], help=argparse.SUPPRESS) + parser.add_argument( + "--os", + default=["ubuntu", "alpine", "distroless", "ubi9"], + help=argparse.SUPPRESS, + ) parser.add_argument( "--no-ubuntu", action=DelOS, @@ -123,6 +127,13 @@ def parse_args() -> argparse.Namespace: default=argparse.SUPPRESS, help="don't build distroless image", ) + parser.add_argument( + "--no-ubi9", + action=DelOS, + nargs=0, + default=argparse.SUPPRESS, + help="don't build UBI 9 image", + ) parser.add_argument( "--allow-build-reuse", action="store_true", @@ -219,11 +230,12 @@ def build_and_push_image( arch_tag = f"{tag}-{arch}" metadata_path = temp_path / arch_tag dockerfile = f"{image.path}/Dockerfile.{os}" + build_context = "." if os == "ubi9" else image.path cmd_args = list(init_args) urls = [] if direct_urls: # distroless and ubuntu-server use an Ubuntu builder with dpkg, so they - # need .deb packages. alpine and ubuntu-keeper use .tgz packages. + # need .deb packages. Alpine, UBI, and ubuntu-keeper use .tgz packages. uses_deb = os == "distroless" or ( os == "ubuntu" and "clickhouse-server" in image.name ) @@ -259,7 +271,7 @@ def build_and_push_image( f"--build-arg=VERSION='{version}'", "--progress=plain", f"--file={dockerfile}", - Path(image.path).as_posix(), + Path(build_context).as_posix(), ] ) cmd = " ".join(cmd_args) diff --git a/ci/praktika/yaml_additional_templates.py b/ci/praktika/yaml_additional_templates.py index 06c35e425a9f..b2e8aab2bec2 100644 --- a/ci/praktika/yaml_additional_templates.py +++ b/ci/praktika/yaml_additional_templates.py @@ -44,7 +44,7 @@ class AltinityWorkflowTemplates: strategy: fail-fast: false matrix: - suffix: ['', '-alpine'] + suffix: ['', '-alpine', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: @@ -54,11 +54,16 @@ class AltinityWorkflowTemplates: GrypeScanKeeper: needs: [config_workflow, docker_keeper_image] if: ${{ !cancelled() && !contains(needs.*.outputs.pipeline_status, 'failure') && !contains(fromJson(needs.config_workflow.outputs.data).workflow_config.cache_success_base64, 'RG9ja2VyIGtlZXBlciBpbWFnZQ==') }} + strategy: + fail-fast: false + matrix: + suffix: ['', '-ubi9'] uses: ./.github/workflows/grype_scan.yml secrets: inherit with: docker_image: altinityinfra/clickhouse-keeper version: ${{ fromJson(needs.config_workflow.outputs.data).workflow_config.custom_data.version.string }} + tag-suffix: ${{ matrix.suffix }} """, "RegressionPR": r""" RegressionTestsRelease: diff --git a/docker/README.ubi9.md b/docker/README.ubi9.md new file mode 100644 index 000000000000..0937aabc9908 --- /dev/null +++ b/docker/README.ubi9.md @@ -0,0 +1,33 @@ +# UBI 9 release images + +Release builds produce Red Hat UBI 9 variants of the existing multi-architecture +ClickHouse Server and ClickHouse Keeper images. They use the same release +artifacts as the other image variants and are published with an `-ubi9` suffix: + +- `altinity/clickhouse-server:-ubi9` +- `altinity/clickhouse-keeper:-ubi9` + +The Dockerfiles download the release `.tgz` artifacts and their `.sha512` +files, verify every checksum, and install the files over a fresh UBI base. The +final images run as the existing ClickHouse UID and GID, `101:101`. + +## Release and certification automation + +The normal release build includes `ubi9` in the Server and Keeper OS matrix. +The normal publish workflow preserves the `-ubi9` suffix when it copies the +multi-architecture manifest from the staging registry to Docker Hub. Grype also +scans both UBI variants. + +For production releases, the publish workflow can submit the immutable UBI +manifest digest to Red Hat Preflight. Configure these GitHub Actions settings: + +- Repository variable `RED_HAT_CERTIFICATION_ENABLED` set to `true`. +- Repository secret `RED_HAT_PYXIS_API_TOKEN` containing the Red Hat API token. +- Repository secret `RED_HAT_CLICKHOUSE_SERVER_CERTIFICATION_COMPONENT_ID` + containing the Server project component ID. +- Repository secret `RED_HAT_CLICKHOUSE_KEEPER_CERTIFICATION_COMPONENT_ID` + containing the Keeper project component ID. + +Certification is intentionally skipped for staging releases and non-UBI image +variants. Each submission also uploads the Preflight result as a workflow +artifact for release auditing. diff --git a/docker/keeper/Dockerfile.ubi9 b/docker/keeper/Dockerfile.ubi9 new file mode 100644 index 000000000000..a9e5c70e5970 --- /dev/null +++ b/docker/keeper/Dockerfile.ubi9 @@ -0,0 +1,73 @@ +ARG UBI_IMAGE=registry.access.redhat.com/ubi9/ubi:9.8 +FROM --platform=${BUILDPLATFORM} ${UBI_IMAGE} AS artifacts + +ARG DIRECT_DOWNLOAD_URLS="" + +WORKDIR /tmp/clickhouse-install + +RUN if [ -z "${DIRECT_DOWNLOAD_URLS}" ]; then \ + echo "DIRECT_DOWNLOAD_URLS is required" >&2; exit 1; \ + fi \ + && command -v curl gzip tar sha512sum >/dev/null \ + && for url in ${DIRECT_DOWNLOAD_URLS}; do \ + echo "Downloading ${url}" \ + && curl --fail --location --retry 5 --retry-delay 1 --remote-name "${url}"; \ + done \ + && sed 's:/output/:/tmp/clickhouse-install/:' ./*.tgz.sha512 | sha512sum --check \ + && mkdir -p /opt/clickhouse-root/lib \ + && for archive in ./*.tgz; do \ + tar xzf "${archive}" --strip-components=1 -C /opt/clickhouse-root; \ + done + +FROM ${UBI_IMAGE} + +ARG VERSION +ARG RELEASE=1 + +LABEL name="Altinity ClickHouse Keeper" \ + vendor="Altinity" \ + maintainer="Altinity " \ + version="${VERSION}" \ + release="${RELEASE}" \ + summary="ClickHouse Keeper built and supported by Altinity" \ + description="A Red Hat UBI-based ClickHouse Keeper container image." \ + io.k8s.display-name="Altinity ClickHouse Keeper" \ + io.openshift.tags="clickhouse,keeper,coordination" + +ARG DEFAULT_UID=101 +ARG DEFAULT_GID=101 + +RUN command -v curl gzip tar sha512sum >/dev/null \ + && groupadd --system --gid "${DEFAULT_GID}" clickhouse \ + && useradd --system --uid "${DEFAULT_UID}" --gid clickhouse \ + --home-dir /var/lib/clickhouse --shell /sbin/nologin clickhouse + +COPY --from=artifacts /opt/clickhouse-root/etc/ /etc/ +COPY --from=artifacts /opt/clickhouse-root/lib/ /usr/lib/ +COPY --from=artifacts /opt/clickhouse-root/usr/ /usr/ + +ARG DEFAULT_CONFIG_DIR=/etc/clickhouse-keeper +ARG DEFAULT_DATA_DIR=/var/lib/clickhouse +ARG DEFAULT_LOG_DIR=/var/log/clickhouse-keeper + +RUN clickhouse-keeper --version \ + && mkdir -p "${DEFAULT_CONFIG_DIR}" "${DEFAULT_DATA_DIR}" "${DEFAULT_LOG_DIR}" \ + && chown -R clickhouse:clickhouse \ + "${DEFAULT_CONFIG_DIR}" "${DEFAULT_DATA_DIR}" "${DEFAULT_LOG_DIR}" \ + && chmod -R ugo+Xrw \ + "${DEFAULT_CONFIG_DIR}" "${DEFAULT_DATA_DIR}" "${DEFAULT_LOG_DIR}" + +COPY --chmod=755 docker/keeper/entrypoint.sh /entrypoint.sh +COPY LICENSE /licenses/LICENSE + +ENV CLICKHOUSE_WATCHDOG_ENABLE=0 \ + LANG=C.UTF-8 \ + TZ=UTC + +EXPOSE 9181 9234 +VOLUME ["/var/lib/clickhouse", "/var/log/clickhouse-keeper"] + +USER 101:101 +WORKDIR /var/lib/clickhouse + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/server/Dockerfile.ubi9 b/docker/server/Dockerfile.ubi9 new file mode 100644 index 000000000000..008ef8b375b0 --- /dev/null +++ b/docker/server/Dockerfile.ubi9 @@ -0,0 +1,88 @@ +ARG UBI_IMAGE=registry.access.redhat.com/ubi9/ubi:9.8 +FROM --platform=${BUILDPLATFORM} ${UBI_IMAGE} AS artifacts + +ARG DIRECT_DOWNLOAD_URLS="" + +WORKDIR /tmp/clickhouse-install + +RUN if [ -z "${DIRECT_DOWNLOAD_URLS}" ]; then \ + echo "DIRECT_DOWNLOAD_URLS is required" >&2; exit 1; \ + fi \ + && command -v curl gzip tar sha512sum >/dev/null \ + && for url in ${DIRECT_DOWNLOAD_URLS}; do \ + echo "Downloading ${url}" \ + && curl --fail --location --retry 5 --retry-delay 1 --remote-name "${url}"; \ + done \ + && sed 's:/output/:/tmp/clickhouse-install/:' ./*.tgz.sha512 | sha512sum --check \ + && mkdir -p /opt/clickhouse-root/lib \ + && for archive in ./*.tgz; do \ + tar xzf "${archive}" --strip-components=1 -C /opt/clickhouse-root; \ + done + +FROM ${UBI_IMAGE} + +ARG VERSION +ARG RELEASE=1 + +LABEL name="Altinity ClickHouse Server" \ + vendor="Altinity" \ + maintainer="Altinity " \ + version="${VERSION}" \ + release="${RELEASE}" \ + summary="ClickHouse Server built and supported by Altinity" \ + description="A Red Hat UBI-based ClickHouse Server container image." \ + io.k8s.display-name="Altinity ClickHouse Server" \ + io.openshift.tags="clickhouse,database,analytics" + +ARG DEFAULT_UID=101 +ARG DEFAULT_GID=101 + +RUN command -v curl gzip tar sha512sum >/dev/null \ + && groupadd --system --gid "${DEFAULT_GID}" clickhouse \ + && useradd --system --uid "${DEFAULT_UID}" --gid clickhouse \ + --home-dir /var/lib/clickhouse --shell /sbin/nologin clickhouse + +COPY --from=artifacts /opt/clickhouse-root/etc/ /etc/ +COPY --from=artifacts /opt/clickhouse-root/lib/ /usr/lib/ +COPY --from=artifacts /opt/clickhouse-root/usr/ /usr/ + +ARG DEFAULT_CLIENT_CONFIG_DIR=/etc/clickhouse-client +ARG DEFAULT_SERVER_CONFIG_DIR=/etc/clickhouse-server +ARG DEFAULT_DATA_DIR=/var/lib/clickhouse +ARG DEFAULT_LOG_DIR=/var/log/clickhouse-server + +RUN clickhouse-local -q 'SELECT * FROM system.build_options' \ + && mkdir -p \ + "${DEFAULT_DATA_DIR}" \ + "${DEFAULT_LOG_DIR}" \ + "${DEFAULT_CLIENT_CONFIG_DIR}" \ + "${DEFAULT_SERVER_CONFIG_DIR}/config.d" \ + "${DEFAULT_SERVER_CONFIG_DIR}/users.d" \ + /docker-entrypoint-initdb.d \ + && chown -R clickhouse:clickhouse \ + "${DEFAULT_DATA_DIR}" \ + "${DEFAULT_LOG_DIR}" \ + "${DEFAULT_CLIENT_CONFIG_DIR}" \ + "${DEFAULT_SERVER_CONFIG_DIR}" \ + /docker-entrypoint-initdb.d \ + && chmod -R ugo+Xrw \ + "${DEFAULT_DATA_DIR}" \ + "${DEFAULT_LOG_DIR}" \ + "${DEFAULT_CLIENT_CONFIG_DIR}" \ + "${DEFAULT_SERVER_CONFIG_DIR}" + +COPY docker/server/docker_related_config.xml /etc/clickhouse-server/config.d/ +COPY LICENSE /licenses/LICENSE + +ENV CLICKHOUSE_CONFIG=/etc/clickhouse-server/config.xml \ + CLICKHOUSE_WATCHDOG_ENABLE=0 \ + LANG=C.UTF-8 \ + TZ=UTC + +EXPOSE 9000 8123 9009 +VOLUME ["/var/lib/clickhouse", "/var/log/clickhouse-server"] + +USER 101:101 +WORKDIR /var/lib/clickhouse + +ENTRYPOINT ["/usr/bin/clickhouse", "docker-init"] From 0930357e0d585c661f134d6c2ed045a588a8bb4e Mon Sep 17 00:00:00 2001 From: "Daniel J. Holmes (jaitaiwan)" Date: Mon, 14 Sep 2026 12:38:19 +1000 Subject: [PATCH 2/2] fix: refresh UBI9 packages at build time to clear Grype High CVEs Both UBI9 Dockerfiles copied our binary onto the base image exactly as pulled, with no package refresh. The pinned ubi9:9.8 tag doesn't update on its own, so both images were shipping with whatever CVEs existed in the base image at the time that tag was published: Grype flagged 20 High vulnerabilities on each (0 on the alpine build), all against the redhat:distro:redhat:9 namespace rather than anything we install ourselves. Add dnf update -y && dnf clean all right after FROM in both Dockerfiles. Verified standalone against the exact pinned base image (registry.access.redhat.com/ubi9/ubi:9.8): it actually upgrades 5 packages (coreutils-single, expat, glib2, pam, vim-minimal), confirming the tag was behind on patches. --- docker/keeper/Dockerfile.ubi9 | 4 ++++ docker/server/Dockerfile.ubi9 | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docker/keeper/Dockerfile.ubi9 b/docker/keeper/Dockerfile.ubi9 index a9e5c70e5970..1777030015db 100644 --- a/docker/keeper/Dockerfile.ubi9 +++ b/docker/keeper/Dockerfile.ubi9 @@ -21,6 +21,10 @@ RUN if [ -z "${DIRECT_DOWNLOAD_URLS}" ]; then \ FROM ${UBI_IMAGE} +# Pull in security patches released against this UBI9 stream since the base +# image tag was last published - a pinned tag does not update on its own. +RUN dnf update -y && dnf clean all + ARG VERSION ARG RELEASE=1 diff --git a/docker/server/Dockerfile.ubi9 b/docker/server/Dockerfile.ubi9 index 008ef8b375b0..ac0d52d14a18 100644 --- a/docker/server/Dockerfile.ubi9 +++ b/docker/server/Dockerfile.ubi9 @@ -21,6 +21,10 @@ RUN if [ -z "${DIRECT_DOWNLOAD_URLS}" ]; then \ FROM ${UBI_IMAGE} +# Pull in security patches released against this UBI9 stream since the base +# image tag was last published - a pinned tag does not update on its own. +RUN dnf update -y && dnf clean all + ARG VERSION ARG RELEASE=1