From af2cdfb7392151313336bd6eecd02e268e6ee309 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 4 May 2022 14:38:31 +0200 Subject: [PATCH] Docker multi-arch builds (#2021) Replaces the current docker build with a multi arch build powered by buildx. The new build creates two scm-manager docker image variants. One based on alpine which uses the openjdk distribution and the other based on debian and eclipse temurin: scmmanager/scm-manager:-alpine - linux/amd64 - linux/arm64 scmmanager/scm-manager:-debian - linux/amd64 - linux/arm64 - linux/arm/v7 scmmanager/scm-manager: - linux/amd64 alpine based - linux/arm64 alpine based - linux/arm/v7 debian based scmmanager/scm-manager:latest - linux/amd64 alpine based - linux/arm64 alpine based - linux/arm/v7 debian based The development build produces only a single amd64 image at cloudogu/scm-manager with a snapshot version. Co-authored-by: Eduard Heimbuch --- Jenkinsfile | 7 +- docs/en/installation/docker.md | 32 ++++++- gradle/changelog/docker_multiarch.yml | 2 + .../docker/{Dockerfile => Dockerfile.alpine} | 33 +++++-- scm-packaging/docker/Dockerfile.debian | 73 ++++++++++++++++ scm-packaging/docker/build.gradle | 84 ++++++++++++------ scm-packaging/docker/docker-bake.hcl | 86 +++++++++++++++++++ 7 files changed, 280 insertions(+), 37 deletions(-) create mode 100644 gradle/changelog/docker_multiarch.yml rename scm-packaging/docker/{Dockerfile => Dockerfile.alpine} (77%) create mode 100644 scm-packaging/docker/Dockerfile.debian create mode 100644 scm-packaging/docker/docker-bake.hcl diff --git a/Jenkinsfile b/Jenkinsfile index d5b3cae8ea..b442bf7eda 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ pipeline { agent { docker { - image 'scmmanager/java-build:11.0.9.1_1-2' + image 'scmmanager/java-build:11.0.15_10-2' args '-v /var/run/docker.sock:/var/run/docker.sock --group-add 998' label 'docker' } @@ -285,14 +285,15 @@ void withChromaticEnvironment(Closure closure) { void withPublishEnivronment(Closure closure) { withCredentials([ usernamePassword(credentialsId: 'maven.scm-manager.org', usernameVariable: 'ORG_GRADLE_PROJECT_packagesScmManagerUsername', passwordVariable: 'ORG_GRADLE_PROJECT_packagesScmManagerPassword'), - usernamePassword(credentialsId: 'hub.docker.com-cesmarvin', usernameVariable: 'ORG_GRADLE_PROJECT_dockerUsername', passwordVariable: 'ORG_GRADLE_PROJECT_dockerPassword'), usernamePassword(credentialsId: 'cesmarvin-github', usernameVariable: 'ORG_GRADLE_PROJECT_gitHubUsername', passwordVariable: 'ORG_GRADLE_PROJECT_gitHubApiToken'), string(credentialsId: 'cesmarvin_npm_token', variable: 'ORG_GRADLE_PROJECT_npmToken'), file(credentialsId: 'oss-gpg-secring', variable: 'GPG_KEY_RING'), usernamePassword(credentialsId: 'oss-keyid-and-passphrase', usernameVariable: 'GPG_KEY_ID', passwordVariable: 'GPG_KEY_PASSWORD') ]) { withEnv(["ORG_GRADLE_PROJECT_npmEmail=cesmarvin@cloudogu.com"]) { - closure.call() + docker.withRegistry('', 'hub.docker.com-cesmarvin') { + closure.call() + } } } } diff --git a/docs/en/installation/docker.md b/docs/en/installation/docker.md index 41e0caea71..00ecc8decf 100644 --- a/docs/en/installation/docker.md +++ b/docs/en/installation/docker.md @@ -18,7 +18,6 @@ for example: docker run --name scm -p 8080:8080 -v scm-home:/var/lib/scm scmmanager/scm-manager:2.0.0 ``` - ## Persistence It is recommended to create a persistent volume for the scm-manager home directory. @@ -60,7 +59,6 @@ docker run -e JAVA_OPTS="-Dsome.property=value" scmmanager/scm-manager: ``` - ## Docker Compose If you want to use the image with docker-compose have a look at the example below. @@ -79,3 +77,33 @@ services: volumes: scmhome: {} ``` + +## Variants + +We are offer two variants of the SCM-Manager docker image one with OpenJDK on Alpine and the other with Eclipse Temurin on Debian. + +### scmmanager/scm-manager:-alpine + +This image uses the Alpine operating system and the OpenJDK distribution from the official apk repository. +The image is available for the following os/architectures: + +* linux/amd64 +* linux/arm64 + +### scmmanager/scm-manager:-debian + +This image uses the Debian operating system and the Eclipse Temurin JDK. +The image is available for the following os/architectures: + +* linux/amd64 +* linux/arm64 +* linux/arm/v7 + +### scmmanager/scm-manager: + +The default image is mainly an alias for the alpine variant, but there is no alpine variant for arm/v7. +For arm/v7 the debian variant is used. + +* linux/amd64 uses the alpine variant +* linux/arm64 uses the alpine variant +* linux/arm/v7 uses the debian variant diff --git a/gradle/changelog/docker_multiarch.yml b/gradle/changelog/docker_multiarch.yml new file mode 100644 index 0000000000..53bf50c169 --- /dev/null +++ b/gradle/changelog/docker_multiarch.yml @@ -0,0 +1,2 @@ +- type: added + description: Docker images for linux/arm/v7 and linux/arm64 ([#2021](https://github.com/scm-manager/scm-manager/pull/2021)) diff --git a/scm-packaging/docker/Dockerfile b/scm-packaging/docker/Dockerfile.alpine similarity index 77% rename from scm-packaging/docker/Dockerfile rename to scm-packaging/docker/Dockerfile.alpine index aa0c9124ab..585ba433c5 100644 --- a/scm-packaging/docker/Dockerfile +++ b/scm-packaging/docker/Dockerfile.alpine @@ -22,11 +22,33 @@ # SOFTWARE. # -FROM adoptopenjdk/openjdk11:jdk-11.0.14_9-alpine-slim -ENV SCM_HOME=/var/lib/scm -ENV CACHE_DIR=/var/cache/scm/work +# Create minimal java version +FROM alpine:3.15.4 as jre-build -COPY . / +RUN set -x \ + && apk add --no-cache openjdk11-jdk openjdk11-jmods \ + && jlink \ + --add-modules ALL-MODULE-PATH \ + --strip-debug \ + --no-man-pages \ + --no-header-files \ + --compress=2 \ + --output /javaruntime + + +# --- + +# SCM-Manager runtime +FROM alpine:3.15.4 as runtime + +ENV SCM_HOME /var/lib/scm +ENV CACHE_DIR /var/cache/scm/work +ENV JAVA_HOME /opt/java/openjdk +ENV PATH "${JAVA_HOME}/bin:${PATH}" + +COPY --from=jre-build /javaruntime "${JAVA_HOME}" +COPY build/docker/etc /etc +COPY build/docker/opt /opt RUN set -x \ # ttf-dejavu graphviz are required for the plantuml plugin @@ -38,10 +60,11 @@ RUN set -x \ && chown 1000:0 ${SCM_HOME} ${CACHE_DIR} \ && chmod -R g=u ${SCM_HOME} ${CACHE_DIR} +USER 1000 + WORKDIR "/opt/scm-server" VOLUME ["${SCM_HOME}", "${CACHE_DIR}"] EXPOSE 8080 -USER 1000 # we us a high relative high start period, # because the start time depends on the number of installed plugins diff --git a/scm-packaging/docker/Dockerfile.debian b/scm-packaging/docker/Dockerfile.debian new file mode 100644 index 0000000000..d0649629dc --- /dev/null +++ b/scm-packaging/docker/Dockerfile.debian @@ -0,0 +1,73 @@ +# +# MIT License +# +# Copyright (c) 2020-present Cloudogu GmbH and Contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +# Create minimal java version +FROM eclipse-temurin:11.0.14.1_1-jdk-focal as jre-build + +RUN jlink \ + --add-modules ALL-MODULE-PATH \ + --strip-debug \ + --no-man-pages \ + --no-header-files \ + --compress=2 \ + --output /javaruntime + +# --- + +# SCM-Manager runtime +FROM debian:11.3-slim as runtime + +ENV SCM_HOME /var/lib/scm +ENV CACHE_DIR /var/cache/scm/work +ENV JAVA_HOME /opt/java/openjdk +ENV PATH "${JAVA_HOME}/bin:${PATH}" + +COPY --from=jre-build /javaruntime "${JAVA_HOME}" +COPY build/docker/etc /etc +COPY build/docker/opt /opt + +RUN set -x \ + && apt-get update \ + # libfreetype6 libfontconfig1 graphviz + && apt-get install -y --no-install-recommends libfreetype6 libfontconfig1 graphviz mercurial bash ca-certificates \ + # use gid 0 for openshift compatibility + && useradd -d "${SCM_HOME}" -u 1000 -g 0 -m -s /bin/bash scm \ + && mkdir -p ${SCM_HOME} ${CACHE_DIR} \ + && chmod +x /opt/scm-server/bin/scm-server \ + # set permissions to group 0 for openshift compatibility + && chown 1000:0 ${SCM_HOME} ${CACHE_DIR} \ + && chmod -R g=u ${SCM_HOME} ${CACHE_DIR} + +USER 1000 + +WORKDIR "/opt/scm-server" +VOLUME ["${SCM_HOME}", "${CACHE_DIR}"] +EXPOSE 8080 + +# we us a high relative high start period, +# because the start time depends on the number of installed plugins +HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/scm/api/v2 || exit 1 + +ENTRYPOINT [ "/opt/scm-server/bin/scm-server" ] diff --git a/scm-packaging/docker/build.gradle b/scm-packaging/docker/build.gradle index 6817c82622..a4c8cdbfca 100644 --- a/scm-packaging/docker/build.gradle +++ b/scm-packaging/docker/build.gradle @@ -23,13 +23,12 @@ */ plugins { - id 'com.bmuschko.docker-remote-api' version '6.6.1' id 'org.scm-manager.packaging' id 'org.scm-manager.license' } import org.gradle.util.VersionNumber -import com.bmuschko.gradle.docker.tasks.image.* +import groovy.json.JsonSlurper configurations { server @@ -70,45 +69,76 @@ task context(type: Copy) { } } -task dockerImage(type: DockerBuildImage) { - inputDir = file('build/docker') - images = images() +task setupBuilder() { + doLast { + def inspect = exec { + commandLine = ["docker", "buildx", "inspect", "scm-builder"] + ignoreExitValue = true + } + if (inspect.exitValue != 0) { + exec { + commandLine = ["docker", "run", "--privileged", "--rm", "tonistiigi/binfmt", "--install", "arm,arm64"] + } + exec { + commandLine = ["docker", "buildx", "create", "--name", "scm-builder", "--driver", "docker-container", "--platform", "linux/arm/v7,linux/arm64/v8,linux/amd64"] + } + exec { + commandLine = ["docker", "buildx", "inspect", "scm-builder"] + } + } + } +} + +task build(type: Exec) { + commandLine = ["docker", "buildx", "bake", "--builder", "scm-builder", isSnapshot ? "dev": "prod"] + environment "VERSION", dockerTag + environment "COMMIT_SHA", revision + environment "IMAGE", dockerRepository + doLast { File file = new File(project.buildDir, 'docker.tag') file.text = dockerTag } - dependsOn 'context' + dependsOn 'context', 'setupBuilder' } -def images() { - if (isSnapshot) { - return [ - "${dockerRepository}:${dockerTag}" - ] - } else { - // What about patch releases? - // It is a good idea to push always latest - return [ - "${dockerRepository}:${dockerTag}", - "${dockerRepository}:latest" - ] - } +task pushImages(type: Exec) { + commandLine = ["docker", "buildx", "bake", "--builder", "scm-builder", isSnapshot ? "dev": "prod", "--push"] + environment "VERSION", dockerTag + environment "COMMIT_SHA", revision + environment "IMAGE", dockerRepository + + dependsOn 'build' } -task publish(type: DockerPushImage) { - images = images() - if (project.hasProperty("dockerUsername") && project.hasProperty("dockerPassword")) { - registryCredentials { - username = project.property("dockerUsername") - password = project.property("dockerPassword") +task publish() { + doLast { + if (!isSnapshot) { + // get digest of debian arm v7 image + def stdout = new ByteArrayOutputStream() + exec { + commandLine = ["docker", "buildx", "imagetools", "inspect", "--raw", "${dockerRepository}:${dockerTag}-debian"] + standardOutput = stdout + } + def inspect = new JsonSlurper().parseText(stdout.toString()) + def manifest = inspect.manifests.find { m -> m.platform.architecture == "arm" } + + // append arm image to manifest with version and without os suffix + exec { + commandLine = ["docker", "buildx", "imagetools", "create", "--append", "-t", "${dockerRepository}:${dockerTag}", "${dockerRepository}:${dockerTag}-debian@${manifest.digest}"] + } + // append arm image to latest manifest + exec { + commandLine = ["docker", "buildx", "imagetools", "create", "--append", "-t", "${dockerRepository}:latest", "${dockerRepository}:${dockerTag}-debian@${manifest.digest}"] + } } } - dependsOn dockerImage + dependsOn 'pushImages' } task distribution(type: PackageYaml) { type = 'docker' - dependsOn dockerImage + dependsOn build } artifacts { diff --git a/scm-packaging/docker/docker-bake.hcl b/scm-packaging/docker/docker-bake.hcl new file mode 100644 index 0000000000..3d4de393f8 --- /dev/null +++ b/scm-packaging/docker/docker-bake.hcl @@ -0,0 +1,86 @@ +# +# MIT License +# +# Copyright (c) 2020-present Cloudogu GmbH and Contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +group "prod" { + targets = [ + "alpine", + "debian" + ] +} + +variable "VERSION" { + default = "unknown" +} + +variable "COMMIT_SHA" { + default = "unknown" +} + +variable "IMAGE" { + default = "docker.io/cloudogu/scm-manager" +} + +target "base" { + context = "." + args = { + VERSION = VERSION + COMMIT_SHA = COMMIT_SHA + } + labels = { + "org.opencontainers.image.vendor" = "Cloudogu GmbH" + "org.opencontainers.image.title" = "Official SCM-Manager image" + "org.opencontainers.image.description" = "The easiest way to share and manage your Git, Mercurial and Subversion repositories" + "org.opencontainers.image.url" = "https://scm-manager.org/" + "org.opencontainers.image.source" = "https://github.com/scm-manager/docker" + "org.opencontainers.image.licenses" = "MIT" + "org.opencontainers.image.version" = VERSION + "org.opencontainers.image.revision" = COMMIT_SHA + } +} + +target "dev" { + inherits = ["base"] + dockerfile = "Dockerfile.alpine" + tags = ["${IMAGE}:${VERSION}"] +} + +target "alpine" { + inherits = ["base"] + dockerfile = "Dockerfile.alpine" + tags = [ + "${IMAGE}:latest", + "${IMAGE}:${VERSION}", + "${IMAGE}:${VERSION}-alpine" + ] + platforms = ["linux/amd64", "linux/arm64/v8"] +} + +target "debian" { + inherits = ["base"] + dockerfile = "Dockerfile.debian" + tags = [ + "${IMAGE}:${VERSION}-debian" + ] + platforms = ["linux/amd64", "linux/arm64/v8", "linux/arm/v7"] +}