Build Confluent Platform for Apache Flink Container Images from Published Artifacts
Confluent Platform for Apache Flink® runs on Kubernetes as a set of container images: the Flink image, the Confluent Platform for Apache Flink Kubernetes Operator image, and the Confluent Manager for Apache Flink (CMF) image. Confluent publishes these images on Docker Hub, and also publishes the underlying component artifacts as .tar.gz archives on packages.confluent.io/cp-flink/.
Most deployments use the published images directly, as described in Install Confluent Manager for Apache Flink with Helm. You can also build your own images from the published archives, when required. For example:
Build on a base image that meets your organization’s compliance or hardening requirements.
Add your own certificates, packages, or JARs at build time.
Important
Confluent does not provide support for issues that arise from custom base images. Support is limited to the Confluent Platform for Apache Flink components themselves. The images that Confluent publishes receive regular security and bug fixes, so keep your custom images up to date and test them thoroughly.
This topic shows how to build container images for the following components from their published archives:
Flink (
cp-flink)Confluent Platform for Apache Flink Kubernetes Operator (
cp-flink-kubernetes-operator)CMF (
cp-cmf)
Important
The component versions must be compatible with each other and with the CMF release you are deploying. The versions in the examples on this page are examples only. Replace them with the versions for your target release. For compatible versions, see Versions and Interoperability for Confluent Manager for Apache Flink.
The Dockerfiles on this page mirror the images that Confluent publishes:
A multi-stage build with a Red Hat Universal Base Image (UBI)
ubi-minimalbuilder stage.A
ubi-microruntime stage.An Azul Zulu Java Runtime Environment (JRE).
For brevity and clarity, these examples omit some details that the published images include, such as s390x support and signature verification of downloaded binaries. They also install the latest available JRE patch instead of pinning one, so your image can carry a newer JRE than the published image. The resulting images are functionally equivalent on the x86_64 and ARM64 platforms. You can substitute any base image that provides a compatible JRE for the component.
Step 1: Confirm prerequisites
Install a container build tool such as Docker.
To verify that Docker is available, the following command should complete without error:
docker versionEither mirror the required artifacts into your environment, or confirm that the machine where you build the images can access the following:
https://packages.confluent.io/cp-flink/for the component archives.The Red Hat UBI images on
registry.access.redhat.com.The Azul Zulu package repository on
cdn.azul.com.https://dl.fedoraproject.orgfor the EPEL repository, which provides jemalloc (Flink image only).https://mirror.stream.centos.orgfor the Snappy package (Flink image only).https://github.comfor thegosubinary (Flink image only).
Step 2: Download and verify the archives
Confluent publishes each component under https://packages.confluent.io/cp-flink/<component>/archive/<version>/, together with a .sha512 checksum file.
The following table lists the archives and the JRE major version that each component requires. The versions shown are examples.
Component | Archive | JRE |
|---|---|---|
Flink |
| 11 |
Kubernetes Operator |
| 17 |
CMF |
| 21 |
Set the version of each component you want to build. Replace the values with the versions for your target release.
export FLINK_VERSION=2.2.0-cp2 export OPERATOR_VERSION=1.15.0-cp2 export CMF_VERSION=2.4.1
Download each archive and its checksum file.
BASE=https://packages.confluent.io/cp-flink curl -fSLO ${BASE}/cp-flink/archive/${FLINK_VERSION}/cp-flink-${FLINK_VERSION}.tar.gz curl -fSLO ${BASE}/cp-flink/archive/${FLINK_VERSION}/cp-flink-${FLINK_VERSION}.tar.gz.sha512 curl -fSLO ${BASE}/cp-flink-kubernetes-operator/archive/${OPERATOR_VERSION}/cp-flink-kubernetes-operator-${OPERATOR_VERSION}.tar.gz curl -fSLO ${BASE}/cp-flink-kubernetes-operator/archive/${OPERATOR_VERSION}/cp-flink-kubernetes-operator-${OPERATOR_VERSION}.tar.gz.sha512 curl -fSLO ${BASE}/cp-cmf/archive/${CMF_VERSION}/cmf-${CMF_VERSION}.tar.gz curl -fSLO ${BASE}/cp-cmf/archive/${CMF_VERSION}/cmf-${CMF_VERSION}.tar.gz.sha512
Verify the checksums before you build. On Linux, use
sha512sum. On macOS, useshasum -a 512.sha512sum -c cp-flink-${FLINK_VERSION}.tar.gz.sha512 sha512sum -c cp-flink-kubernetes-operator-${OPERATOR_VERSION}.tar.gz.sha512 sha512sum -c cmf-${CMF_VERSION}.tar.gz.sha512
Each command should print
OK. Do not build from an archive that fails verification.
Step 3: Build the Flink image
The cp-flink archive is Confluent’s Flink distribution and includes its own bin/docker-entrypoint.sh.
The published cp-flink images default to Java 11. Confluent also publishes a -java17 variant for every supported Flink version, and a -java21 variant for Flink 2.x.
Create a build directory and add the Flink archive to it.
mkdir -p cp-flink-image cp cp-flink-${FLINK_VERSION}.tar.gz cp-flink-image/
Create
cp-flink-image/Dockerfilewith the following content. TheJAVA_MAJORbuild argument selects the JRE version. It defaults to11(the default published image); set it to17or21to match the-java17or-java21variants. The configuration step handles bothconfig.yaml(Flink 1.19 and later) andflink-conf.yaml(Flink 1.18).ARG SRC_UBI_TAG=latest ARG INSTALL_ROOT=/mnt/rootfs FROM registry.access.redhat.com/ubi9/ubi-minimal:${SRC_UBI_TAG} AS builder ARG JAVA_MAJOR=11 ARG INSTALL_ROOT ARG FLINK_VERSION=2.2.0-cpX ARG GOSU_VERSION=1.19 ARG ARCH=amd64 ENV FLINK_HOME=/opt/flink # config-parser-utils.sh needs JAVA_HOME, through bin/bash-java-utils.sh. ENV JAVA_HOME=/usr/lib/jvm/jre-${JAVA_MAJOR} USER root RUN microdnf install -y shadow-utils tar gzip wget yum findutils \ && yum --nodocs install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm \ && yum --nodocs install -y https://cdn.azul.com/zulu/bin/zulu-repo-1.0.0-1.noarch.rpm \ && microdnf install -y zulu${JAVA_MAJOR}-ca-jre-headless \ && ln -sf /usr/lib/jvm/zulu${JAVA_MAJOR} ${JAVA_HOME} ADD cp-flink-${FLINK_VERSION}.tar.gz /opt/ RUN mv /opt/cp-flink-${FLINK_VERSION} ${FLINK_HOME} RUN groupadd --system --gid=9999 flink \ && useradd --system --home-dir ${FLINK_HOME} --uid=9999 --gid=flink flink # Set the OpenShift permissions in the builder. Doing it in the runtime stage would # add a second copy of every file in a new layer. RUN chmod -R g+rwX ${FLINK_HOME} # Bind endpoints to all interfaces. Versions 1.19 and later keep this in # config.yaml, which config-parser-utils.sh rewrites; 1.18 uses flink-conf.yaml. RUN set -ex; \ chown -R flink:flink ${FLINK_HOME}; \ CONF_FILE="${FLINK_HOME}/conf/flink-conf.yaml"; \ if [ ! -e "${CONF_FILE}" ]; then \ /bin/bash "${FLINK_HOME}/bin/config-parser-utils.sh" \ "${FLINK_HOME}/conf" "${FLINK_HOME}/bin" "${FLINK_HOME}/lib" \ "-repKV" "rest.address,localhost,0.0.0.0" \ "-repKV" "rest.bind-address,localhost,0.0.0.0" \ "-repKV" "jobmanager.bind-host,localhost,0.0.0.0" \ "-repKV" "taskmanager.bind-host,localhost,0.0.0.0" \ "-rmKV" "taskmanager.host=localhost"; \ else \ sed -i -e 's/rest.address: localhost/rest.address: 0.0.0.0/g' \ -e 's/rest.bind-address: localhost/rest.bind-address: 0.0.0.0/g' \ -e 's/jobmanager.bind-host: localhost/jobmanager.bind-host: 0.0.0.0/g' \ -e 's/taskmanager.bind-host: localhost/taskmanager.bind-host: 0.0.0.0/g' \ -e '/taskmanager.host: localhost/d' "${CONF_FILE}"; \ fi RUN mkdir -p ${INSTALL_ROOT} /etc/dnf /var/cache/microdnf; touch /etc/dnf/dnf.conf; \ microdnf install -y --noplugins --config=/etc/dnf/dnf.conf --setopt=cachedir=/var/cache/microdnf \ --setopt=reposdir=/etc/yum.repos.d --setopt=varsdir=/etc/dnf \ --installroot=${INSTALL_ROOT} --releasever=35 \ hostname findutils util-linux gettext shadow-utils procps jemalloc zulu${JAVA_MAJOR}-ca-jre-headless \ && microdnf clean all && rm -rf ${INSTALL_ROOT}/var/cache/* \ && mkdir -p ${INSTALL_ROOT}/usr/lib/jvm \ && ln -sf /usr/lib/jvm/zulu${JAVA_MAJOR} ${INSTALL_ROOT}${JAVA_HOME} # Snappy native library. It cannot be installed into ${INSTALL_ROOT}, so install the # RPM into the builder filesystem here and copy the .so into the runtime stage below. RUN case "${ARCH}" in \ amd64) RPM_ARCH=x86_64 ;; \ arm64) RPM_ARCH=aarch64 ;; \ *) RPM_ARCH=${ARCH} ;; \ esac; \ set -ex; \ wget -q https://mirror.stream.centos.org/9-stream/BaseOS/${RPM_ARCH}/os/Packages/snappy-1.1.8-8.el9.${RPM_ARCH}.rpm \ && rpm -Uvh --noscripts snappy-*.rpm \ && rm -f snappy-*.rpm RUN set -ex; mkdir -p ${INSTALL_ROOT}/usr/local/bin; \ wget -nv -O ${INSTALL_ROOT}/usr/local/bin/gosu \ "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-${ARCH}"; \ chmod +x ${INSTALL_ROOT}/usr/local/bin/gosu FROM registry.access.redhat.com/ubi9/ubi-micro:${SRC_UBI_TAG} ARG INSTALL_ROOT ARG JAVA_MAJOR=11 USER root RUN mkdir /licenses COPY --from=builder /opt/flink/LICENSE /licenses/ COPY --from=builder ${INSTALL_ROOT}/ / # jemalloc is included in ${INSTALL_ROOT} above; copy the Snappy library separately. COPY --from=builder /usr/lib64/libsnappy.so.1.1.8 /usr/lib64/libsnappy.so.1 ENV FLINK_HOME=/opt/flink ENV JAVA_HOME=/usr/lib/jvm/jre-${JAVA_MAJOR} ENV PATH=$FLINK_HOME/bin:$JAVA_HOME/bin:$PATH # For OpenShift support, define the flink user numerically. RUN useradd --system --home-dir $FLINK_HOME --uid=9999 flink COPY --from=builder --chown=flink:0 /opt/flink/ $FLINK_HOME WORKDIR $FLINK_HOME # The entrypoint script ships in the archive. COPY --from=builder /opt/flink/bin/docker-entrypoint.sh /docker-entrypoint.sh RUN set -ex; chmod +x /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 6123 8081 USER 9999 CMD ["help"]
Build the image. Set
FLINK_VERSIONto your release,JAVA_MAJORto the Java version you want (11,17, or21), andARCHtoamd64orarm64.docker build \ --build-arg FLINK_VERSION=${FLINK_VERSION} \ --build-arg JAVA_MAJOR=11 \ --build-arg ARCH=amd64 \ -t <your-registry>/cp-flink:${FLINK_VERSION} \ cp-flink-image/
Step 4: Build the Kubernetes Operator image
The cp-flink-kubernetes-operator archive includes the operator JARs, the metrics reporter plugins, and its own bin/docker-entrypoint.sh.
Create a build directory and add the operator archive to it.
mkdir -p cp-flink-kubernetes-operator-image cp cp-flink-kubernetes-operator-${OPERATOR_VERSION}.tar.gz cp-flink-kubernetes-operator-image/
Create
cp-flink-kubernetes-operator-image/Dockerfilewith the following content.ARG SRC_UBI_TAG=latest ARG INSTALL_ROOT=/mnt/rootfs FROM registry.access.redhat.com/ubi9/ubi-minimal:${SRC_UBI_TAG} AS builder ARG ZULU_JAVA17_JRE_VERSION=zulu17-ca-jre-headless ARG INSTALL_ROOT ARG OPERATOR_VERSION=1.15.0-cpX USER root RUN microdnf install -y yum findutils tar gzip \ && yum --nodocs install -y https://cdn.azul.com/zulu/bin/zulu-repo-1.0.0-1.noarch.rpm \ && microdnf install -y ${ZULU_JAVA17_JRE_VERSION} RUN mkdir -p ${INSTALL_ROOT} /etc/dnf /var/cache/microdnf; touch /etc/dnf/dnf.conf; \ microdnf install -y --noplugins --config=/etc/dnf/dnf.conf --setopt=cachedir=/var/cache/microdnf \ --setopt=reposdir=/etc/yum.repos.d --setopt=varsdir=/etc/dnf \ --installroot=${INSTALL_ROOT} --releasever=35 \ shadow-utils ${ZULU_JAVA17_JRE_VERSION} \ && microdnf clean all && rm -rf ${INSTALL_ROOT}/var/cache/* # Unpack the published archive. ADD cp-flink-kubernetes-operator-${OPERATOR_VERSION}.tar.gz /opt/ # The runtime image. FROM registry.access.redhat.com/ubi9/ubi-micro:${SRC_UBI_TAG} ARG INSTALL_ROOT ARG OPERATOR_VERSION=1.15.0-cpX USER root COPY --from=builder ${INSTALL_ROOT}/ / RUN mkdir /licenses COPY --from=builder \ /opt/cp-flink-kubernetes-operator-${OPERATOR_VERSION}/share/doc/cp-flink-kubernetes-operator/LICENSE \ /licenses/ RUN ln -sf /usr/lib/jvm/java-17-zulu-openjdk-ca /usr/lib/jvm/java-17 ENV FLINK_HOME=/opt/flink ENV JAVA_HOME=/usr/lib/jvm/java-17 ENV FLINK_PLUGINS_DIR=$FLINK_HOME/plugins ENV OPERATOR_LIB=$FLINK_HOME/operator-lib ENV OPERATOR_JAR=flink-kubernetes-operator-${OPERATOR_VERSION}-shaded.jar ENV WEBHOOK_JAR=flink-kubernetes-webhook-${OPERATOR_VERSION}-shaded.jar ENV KUBERNETES_STANDALONE_JAR=flink-kubernetes-standalone-${OPERATOR_VERSION}.jar ENV PATH=$FLINK_HOME/bin:$JAVA_HOME/bin:$PATH RUN mkdir -p $OPERATOR_LIB $FLINK_PLUGINS_DIR RUN groupadd --system --gid=9999 flink \ && useradd --system --home-dir $FLINK_HOME --uid=9999 --gid=flink flink WORKDIR /flink-kubernetes-operator # Lay out the operator jars, metrics plugins, license files, and entrypoint from # the archive. COPY --from=builder --chown=flink:flink \ /opt/cp-flink-kubernetes-operator-${OPERATOR_VERSION}/share/java/cp-flink-kubernetes-operator/*.jar \ /flink-kubernetes-operator/ COPY --from=builder --chown=flink:flink \ /opt/cp-flink-kubernetes-operator-${OPERATOR_VERSION}/share/java/cp-flink-kubernetes-operator/plugins/ \ ${FLINK_PLUGINS_DIR}/ COPY --from=builder --chown=flink:flink \ /opt/cp-flink-kubernetes-operator-${OPERATOR_VERSION}/share/doc/cp-flink-kubernetes-operator/NOTICE \ /opt/cp-flink-kubernetes-operator-${OPERATOR_VERSION}/share/doc/cp-flink-kubernetes-operator/LICENSE \ /flink-kubernetes-operator/ COPY --from=builder --chown=flink:flink \ /opt/cp-flink-kubernetes-operator-${OPERATOR_VERSION}/share/doc/cp-flink-kubernetes-operator/licenses/ \ /flink-kubernetes-operator/licenses/ COPY --from=builder \ /opt/cp-flink-kubernetes-operator-${OPERATOR_VERSION}/bin/docker-entrypoint.sh \ /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh && chown -R flink:flink $FLINK_HOME /flink-kubernetes-operator USER flink ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["help"]
Build the image.
docker build \ --build-arg OPERATOR_VERSION=${OPERATOR_VERSION} \ -t <your-registry>/cp-flink-kubernetes-operator:${OPERATOR_VERSION} \ cp-flink-kubernetes-operator-image/
Step 5: Build the CMF image
The cp-cmf archive includes the CMF application JAR, the SQL statement service JARs, the Flink filesystem plugins, and its own bin/docker-entrypoint.sh entrypoint. The entrypoint expects the CMF JAR at /app/cmf.jar, the statement service JARs in /app/sql, and any JDBC drivers that you supply in /opt/cmf/jdbc-drivers.
Create a build directory and add the CMF archive to it.
mkdir -p cp-cmf-image cp cmf-${CMF_VERSION}.tar.gz cp-cmf-image/
Create
cp-cmf-image/Dockerfilewith the following content.ARG SRC_UBI_TAG=latest ARG INSTALL_ROOT=/mnt/rootfs FROM registry.access.redhat.com/ubi9/ubi-minimal:${SRC_UBI_TAG} AS builder ARG ZULU_JAVA21_JRE_VERSION=zulu21-ca-jre-headless ARG INSTALL_ROOT ARG CMF_VERSION=2.4.X USER root RUN microdnf install -y yum findutils tar gzip \ && yum --nodocs install -y https://cdn.azul.com/zulu/bin/zulu-repo-1.0.0-1.noarch.rpm \ && microdnf install -y ${ZULU_JAVA21_JRE_VERSION} RUN mkdir -p ${INSTALL_ROOT} /etc/dnf /var/cache/microdnf; touch /etc/dnf/dnf.conf; \ microdnf install -y --noplugins --config=/etc/dnf/dnf.conf --setopt=cachedir=/var/cache/microdnf \ --setopt=reposdir=/etc/yum.repos.d --setopt=varsdir=/etc/dnf \ --installroot=${INSTALL_ROOT} --releasever=35 \ shadow-utils ${ZULU_JAVA21_JRE_VERSION} \ && microdnf clean all && rm -rf ${INSTALL_ROOT}/var/cache/* \ && mkdir -p ${INSTALL_ROOT}/usr/lib/jvm \ && ln -sf /usr/lib/jvm/java-21-zulu-openjdk-ca ${INSTALL_ROOT}/usr/lib/jvm/jre-21 # Unpack the published archive and lay out the application under /app. ADD cmf-${CMF_VERSION}.tar.gz /opt/ RUN set -eux; \ SRC=/opt/cmf-${CMF_VERSION}; \ mkdir -p /app/sql /opt/cmf/plugins /opt/cmf/user-libs /opt/cmf/jdbc-drivers; \ cp "$SRC"/share/java/cmf/cmf-app-*.jar /app/cmf.jar; \ cp "$SRC"/share/java/cmf-statement-service/*.jar /app/sql/; \ cp -r "$SRC"/share/java/cmf-plugins/. /opt/cmf/plugins/; \ cp "$SRC"/bin/docker-entrypoint.sh /app/docker-entrypoint.sh; \ chmod +x /app/docker-entrypoint.sh; \ mkdir -p /licenses; \ cp "$SRC"/share/doc/cmf/LICENSE /licenses/ # The runtime image. FROM registry.access.redhat.com/ubi9/ubi-micro:${SRC_UBI_TAG} ARG INSTALL_ROOT USER root COPY --from=builder ${INSTALL_ROOT}/ / ENV JAVA_HOME=/usr/lib/jvm/jre-21 ENV PATH="$PATH:$JAVA_HOME/bin" COPY --from=builder /licenses /licenses # Dedicated application user (matches the published image UID and GID). ARG UID=1058 ARG GID=1058 RUN groupadd -g ${GID} appuser \ && useradd -u ${UID} -g ${GID} -m -d /home/appuser -s /bin/bash appuser # Let the entrypoint self-register a passwd entry when the container runs as an # arbitrary UID (OpenShift pattern). Required for OS-user lookups, which artifact # I/O to s3://, gs://, and abfs:// depends on. RUN chmod g=u /etc/passwd COPY --from=builder --chown=appuser:appuser /app /app COPY --from=builder --chown=appuser:appuser /opt/cmf/plugins /opt/cmf/plugins COPY --from=builder --chown=appuser:appuser /opt/cmf/user-libs /opt/cmf/user-libs COPY --from=builder --chown=appuser:appuser /opt/cmf/jdbc-drivers /opt/cmf/jdbc-drivers WORKDIR /app USER appuser RUN mkdir local && chmod 777 local EXPOSE 8080 ENTRYPOINT ["./docker-entrypoint.sh"]
Build the image.
docker build \ --build-arg CMF_VERSION=${CMF_VERSION} \ -t <your-registry>/cp-cmf:${CMF_VERSION} \ cp-cmf-image/
Step 6: Verify the images
Confirm that each image starts. Each image prints its version on startup.
The Flink and Kubernetes Operator images accept a
helpargument that prints usage:docker run --rm <your-registry>/cp-flink:${FLINK_VERSION} help docker run --rm <your-registry>/cp-flink-kubernetes-operator:${OPERATOR_VERSION} help
The CMF image starts the CMF application. Run it and confirm the startup banner appears in the logs, then stop the container:
docker run --rm <your-registry>/cp-cmf:${CMF_VERSION}
Note
Outside a Kubernetes cluster, the Kubernetes Operator and CMF cannot reach a Kubernetes API server, so they exit after logging their startup information. This is expected. The purpose of this step is to confirm that the image is built correctly and the application starts.
Step 7: Use your images
Before you use these images with Helm, push them to a registry that your Kubernetes cluster can pull from. Then point each chart at your registry instead of the default Confluent images.
For the Kubernetes Operator, set the
imagevalues when you install theflink-kubernetes-operatorchart.For CMF, set the
image.repository,image.name, andimage.tagvalues, as described in Install Confluent Manager for Apache Flink with Helm.
CMF deploys your Flink image based on the image you reference in your Flink environment, application, or statement resources.
If your registry requires authentication, create an image pull secret and reference it through the imagePullSecretRef Helm value.
