From ec02350587415ccc0855991be593bd267c6f1f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A5=BA=E5=AD=90w=20=28Yumechi=29?= <35571479+eternal-flame-AD@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:14:59 -0500 Subject: [PATCH] Docker multiarch image (#703) fixes #257 . fixes #350 --------- Signed-off-by: eternal-flame-AD --- .dockerignore | 9 +++++ .github/workflows/build.yml | 39 ++++++++++-------- Makefile | 78 ++++++++++++++++-------------------- docker/Dockerfile | 80 ++++++++++++++++++++++++++++++++----- docker/Dockerfile.arm64 | 5 --- docker/Dockerfile.armv7 | 5 --- docker/Dockerfile.riscv64 | 5 --- test/filepath_test.go | 8 ++-- 8 files changed, 141 insertions(+), 88 deletions(-) create mode 100644 .dockerignore delete mode 100644 docker/Dockerfile.arm64 delete mode 100644 docker/Dockerfile.armv7 delete mode 100644 docker/Dockerfile.riscv64 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b3dae9c3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +vendor/ +.idea/ +build/ +licenses/ +coverage.txt +data/ +images/ +.git/ +*/node_modules/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 503fb8e4..e34c2896 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,27 +31,32 @@ jobs: - if: startsWith(github.ref, 'refs/tags/v') run: | export LD_FLAGS="-w -s -X main.Version=$VERSION -X main.BuildDate=$(date "+%F-%T") -X main.Commit=$(git rev-parse --verify HEAD) -X main.Mode=prod" + echo "LD_FLAGS=$LD_FLAGS" >> $GITHUB_ENV + make build sudo chown -R $UID build make package-zip ls -lath build - make build-docker - docker image ls - echo "$DOCKER_PASS" | docker login --username "$DOCKER_USER" --password-stdin - echo "$DOCKER_GHCR_PASS" | docker login ghcr.io --username "$DOCKER_GHCR_USER" --password-stdin - docker push --all-tags gotify/server - docker push --all-tags gotify/server-arm7 - docker push --all-tags gotify/server-arm64 - docker push --all-tags gotify/server-riscv64 - docker push --all-tags ghcr.io/gotify/server - docker push --all-tags ghcr.io/gotify/server-arm7 - docker push --all-tags ghcr.io/gotify/server-arm64 - docker push --all-tags ghcr.io/gotify/server-riscv64 - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASS: ${{ secrets.DOCKER_PASS }} - DOCKER_GHCR_USER: ${{ secrets.DOCKER_GHCR_USER }} - DOCKER_GHCR_PASS: ${{ secrets.DOCKER_GHCR_PASS }} + - if: startsWith(github.ref, 'refs/tags/v') + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - if: startsWith(github.ref, 'refs/tags/v') + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - if: startsWith(github.ref, 'refs/tags/v') + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + - if: startsWith(github.ref, 'refs/tags/v') + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ secrets.DOCKER_GHCR_USER }} + password: ${{ secrets.DOCKER_GHCR_PASS }} + - if: startsWith(github.ref, 'refs/tags/v') + run: | + make DOCKER_BUILD_PUSH=true build-docker - if: startsWith(github.ref, 'refs/tags/v') uses: svenstaro/upload-release-action@v2 with: diff --git a/Makefile b/Makefile index 3c49a8dd..dd7b3740 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,9 @@ SHELL := /bin/bash GO_VERSION=`cat GO_VERSION` DOCKER_BUILD_IMAGE=gotify/build DOCKER_WORKDIR=/proj -DOCKER_RUN=docker run --rm -v "$$PWD/.:${DOCKER_WORKDIR}" -v "`go env GOPATH`/pkg/mod/.:/go/pkg/mod:ro" -w ${DOCKER_WORKDIR} +DOCKER_RUN=docker run --rm -e LD_FLAGS="$$LD_FLAGS" -v "$$PWD/.:${DOCKER_WORKDIR}" -v "`go env GOPATH`/pkg/mod/.:/go/pkg/mod:ro" -w ${DOCKER_WORKDIR} DOCKER_GO_BUILD=go build -mod=readonly -a -installsuffix cgo -ldflags "$$LD_FLAGS" +DOCKER_TEST_LEVEL ?= 0 # Optionally run a test during docker build NODE_OPTIONS=$(shell if node --help | grep -q -- "--openssl-legacy-provider"; then echo --openssl-legacy-provider; fi) test: test-coverage test-js @@ -63,38 +64,17 @@ package-zip: extract-licenses zip -ur $$BUILD.zip ${LICENSE_DIR}; \ done -build-docker-amd64: require-version - cp ${BUILD_DIR}/gotify-linux-amd64 ./docker/gotify-app - cd ${DOCKER_DIR} && \ - docker build \ +build-docker-multiarch: require-version + docker buildx build --sbom=true --provenance=true \ + $(if $(DOCKER_BUILD_PUSH),--push) \ -t gotify/server:latest \ -t gotify/server:${VERSION} \ -t gotify/server:$(shell echo $(VERSION) | cut -d '.' -f -2) \ -t gotify/server:$(shell echo $(VERSION) | cut -d '.' -f -1) \ - -t ghcr.io/gotify/server:latest \ + -t ghcr.io/gotify/server:latest \ -t ghcr.io/gotify/server:${VERSION} \ -t ghcr.io/gotify/server:$(shell echo $(VERSION) | cut -d '.' -f -2) \ - -t ghcr.io/gotify/server:$(shell echo $(VERSION) | cut -d '.' -f -1) . - rm ${DOCKER_DIR}gotify-app - -build-docker-arm-7: require-version - cp ${BUILD_DIR}/gotify-linux-arm-7 ./docker/gotify-app - cd ${DOCKER_DIR} && \ - docker build -f Dockerfile.armv7 \ - -t gotify/server-arm7:latest \ - -t gotify/server-arm7:${VERSION} \ - -t gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -2) \ - -t gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -1) \ - -t ghcr.io/gotify/server-arm7:latest \ - -t ghcr.io/gotify/server-arm7:${VERSION} \ - -t ghcr.io/gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -2) \ - -t ghcr.io/gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -1) . - rm ${DOCKER_DIR}gotify-app - -build-docker-arm64: require-version - cp ${BUILD_DIR}/gotify-linux-arm64 ./docker/gotify-app - cd ${DOCKER_DIR} && \ - docker build -f Dockerfile.arm64 \ + -t ghcr.io/gotify/server:$(shell echo $(VERSION) | cut -d '.' -f -1) \ -t gotify/server-arm64:latest \ -t gotify/server-arm64:${VERSION} \ -t gotify/server-arm64:$(shell echo $(VERSION) | cut -d '.' -f -2) \ @@ -102,13 +82,15 @@ build-docker-arm64: require-version -t ghcr.io/gotify/server-arm64:latest \ -t ghcr.io/gotify/server-arm64:${VERSION} \ -t ghcr.io/gotify/server-arm64:$(shell echo $(VERSION) | cut -d '.' -f -2) \ - -t ghcr.io/gotify/server-arm64:$(shell echo $(VERSION) | cut -d '.' -f -1) . - rm ${DOCKER_DIR}gotify-app - -build-docker-riscv64: require-version - cp ${BUILD_DIR}/gotify-linux-riscv64 ./docker/gotify-app - cd ${DOCKER_DIR} && \ - docker build -f Dockerfile.riscv64 \ + -t ghcr.io/gotify/server-arm64:$(shell echo $(VERSION) | cut -d '.' -f -1) \ + -t gotify/server-arm7:latest \ + -t gotify/server-arm7:${VERSION} \ + -t gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -2) \ + -t gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -1) \ + -t ghcr.io/gotify/server-arm7:latest \ + -t ghcr.io/gotify/server-arm7:${VERSION} \ + -t ghcr.io/gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -2) \ + -t ghcr.io/gotify/server-arm7:$(shell echo $(VERSION) | cut -d '.' -f -1) \ -t gotify/server-riscv64:latest \ -t gotify/server-riscv64:${VERSION} \ -t gotify/server-riscv64:$(shell echo $(VERSION) | cut -d '.' -f -2) \ @@ -116,34 +98,42 @@ build-docker-riscv64: require-version -t ghcr.io/gotify/server-riscv64:latest \ -t ghcr.io/gotify/server-riscv64:${VERSION} \ -t ghcr.io/gotify/server-riscv64:$(shell echo $(VERSION) | cut -d '.' -f -2) \ - -t ghcr.io/gotify/server-riscv64:$(shell echo $(VERSION) | cut -d '.' -f -1) . - rm ${DOCKER_DIR}gotify-app + -t ghcr.io/gotify/server-riscv64:$(shell echo $(VERSION) | cut -d '.' -f -1) \ + --build-arg RUN_TESTS=$(DOCKER_TEST_LEVEL) \ + --build-arg GO_VERSION=$(shell cat GO_VERSION) \ + --build-arg LD_FLAGS="$$LD_FLAGS" \ + --platform linux/amd64,linux/arm64,linux/386,linux/arm/v7,linux/riscv64 \ + -f docker/Dockerfile . + +build-docker: build-docker-multiarch -build-docker: build-docker-amd64 build-docker-arm-7 build-docker-arm64 build-docker-riscv64 +_build_within_docker: OUTPUT = gotify-app +_build_within_docker: + ${DOCKER_GO_BUILD} -o ${OUTPUT} build-js: (cd ui && NODE_OPTIONS="${NODE_OPTIONS}" yarn build) build-linux-amd64: - ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-amd64 ${DOCKER_GO_BUILD} -o ${BUILD_DIR}/gotify-linux-amd64 ${DOCKER_WORKDIR} + ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-amd64 make _build_within_docker OUTPUT=${BUILD_DIR}/gotify-linux-amd64 build-linux-386: - ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-386 ${DOCKER_GO_BUILD} -o ${BUILD_DIR}/gotify-linux-386 ${DOCKER_WORKDIR} + ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-386 make _build_within_docker OUTPUT=${BUILD_DIR}/gotify-linux-386 build-linux-arm-7: - ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm-7 ${DOCKER_GO_BUILD} -o ${BUILD_DIR}/gotify-linux-arm-7 ${DOCKER_WORKDIR} + ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm-7 make _build_within_docker OUTPUT=${BUILD_DIR}/gotify-linux-arm-7 build-linux-arm64: - ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm64 ${DOCKER_GO_BUILD} -o ${BUILD_DIR}/gotify-linux-arm64 ${DOCKER_WORKDIR} + ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-arm64 make _build_within_docker OUTPUT=${BUILD_DIR}/gotify-linux-arm64 build-linux-riscv64: - ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-riscv64 ${DOCKER_GO_BUILD} -o ${BUILD_DIR}/gotify-linux-riscv64 ${DOCKER_WORKDIR} + ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-linux-riscv64 make _build_within_docker OUTPUT=${BUILD_DIR}/gotify-linux-riscv64 build-windows-amd64: - ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-windows-amd64 ${DOCKER_GO_BUILD} -o ${BUILD_DIR}/gotify-windows-amd64.exe ${DOCKER_WORKDIR} + ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-windows-amd64 make _build_within_docker OUTPUT=${BUILD_DIR}/gotify-windows-amd64.exe build-windows-386: - ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-windows-386 ${DOCKER_GO_BUILD} -o ${BUILD_DIR}/gotify-windows-386.exe ${DOCKER_WORKDIR} + ${DOCKER_RUN} ${DOCKER_BUILD_IMAGE}:$(GO_VERSION)-windows-386 make _build_within_docker OUTPUT=${BUILD_DIR}/gotify-windows-386.exe build: build-linux-arm-7 build-linux-amd64 build-linux-386 build-linux-arm64 build-linux-riscv64 build-windows-amd64 build-windows-386 diff --git a/docker/Dockerfile b/docker/Dockerfile index 70c98926..bc9a1967 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,74 @@ -FROM amd64/debian:stable-slim -ENV GOTIFY_SERVER_PORT="80" +ARG BUILDKIT_SBOM_SCAN_CONTEXT=true +# Suppress warning about invalid variable expansion +ARG GO_VERSION=PLEASE_PROVIDE_GO_VERSION +ARG DEBIAN=sid-slim + +# Hack to normalize platform to match the chosed build image +# Get the gotify/build image tag +ARG __TARGETPLATFORM_DASHES=${TARGETPLATFORM/\//-} +ARG __TARGETPLATFORM_GO_NOTATION=${__TARGETPLATFORM_DASHES/arm\/v7/arm-7} + +# --- JS Builder --- + +FROM --platform=${BUILDPLATFORM} node:23 AS js-builder + +ARG BUILD_JS=0 + +COPY ./Makefile /src/gotify/Makefile +COPY ./ui /src/gotify/ui + +RUN if [ "$BUILD_JS" = "1" ]; then \ + (cd /src/gotify/ui && yarn install) && \ + (cd /src/gotify && make build-js) \ + else \ + mkdir -p /src/gotify/ui/build; \ + fi + +# --- Go Builder --- + +FROM --platform=${BUILDPLATFORM} gotify/build:${GO_VERSION}-${__TARGETPLATFORM_GO_NOTATION} AS builder + +ARG BUILDPLATFORM +ARG TARGETPLATFORM +ARG BUILD_JS=0 +ARG RUN_TESTS=0 # 0=never, 1=native only +ARG LD_FLAGS="" +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -yq --no-install-recommends \ + ca-certificates \ + git + +COPY . /src/gotify +COPY --from=js-builder /src/gotify/ui/build /ui-build + +RUN if [ "$BUILD_JS" = "1" ]; then \ + cp -r --update /ui-build /src/gotify/ui/build; \ + fi + +RUN cd /src/gotify && \ + if [ "$RUN_TESTS" = "1" ] && [ "$BUILDPLATFORM" = "$TARGETPLATFORM" ]; then \ + go test -v ./...; \ + fi && \ + LD_FLAGS=${LD_FLAGS} make OUTPUT=/target/app/gotify-app _build_within_docker + +FROM debian:${DEBIAN} + +# Build-time configurables +ARG GOTIFY_SERVER_EXPOSE=80 +ENV GOTIFY_SERVER_PORT=$GOTIFY_SERVER_EXPOSE + WORKDIR /app -RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -yq \ - tzdata \ - curl \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* -ADD gotify-app /app/ + +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -yq --no-install-recommends \ + tzdata \ + curl \ + ca-certificates && \ + rm -rf /var/lib/apt/lists/* + HEALTHCHECK --interval=30s --timeout=5s --start-period=5s CMD curl --fail http://localhost:$GOTIFY_SERVER_PORT/health || exit 1 -EXPOSE 80 +EXPOSE $GOTIFY_SERVER_EXPOSE + +COPY --from=builder /target / + ENTRYPOINT ["./gotify-app"] diff --git a/docker/Dockerfile.arm64 b/docker/Dockerfile.arm64 deleted file mode 100644 index 240448a9..00000000 --- a/docker/Dockerfile.arm64 +++ /dev/null @@ -1,5 +0,0 @@ -FROM arm64v8/debian -WORKDIR /app -ADD gotify-app /app/ -EXPOSE 80 -ENTRYPOINT ["./gotify-app"] \ No newline at end of file diff --git a/docker/Dockerfile.armv7 b/docker/Dockerfile.armv7 deleted file mode 100644 index 8d91d0ae..00000000 --- a/docker/Dockerfile.armv7 +++ /dev/null @@ -1,5 +0,0 @@ -FROM arm32v7/debian:stable-slim -WORKDIR /app -ADD gotify-app /app/ -EXPOSE 80 -ENTRYPOINT ["./gotify-app"] diff --git a/docker/Dockerfile.riscv64 b/docker/Dockerfile.riscv64 deleted file mode 100644 index 5044a10f..00000000 --- a/docker/Dockerfile.riscv64 +++ /dev/null @@ -1,5 +0,0 @@ -FROM riscv64/debian:sid-slim -WORKDIR /app -ADD gotify-app /app/ -EXPOSE 80 -ENTRYPOINT ["./gotify-app"] diff --git a/test/filepath_test.go b/test/filepath_test.go index d6efbb98..fbf19937 100644 --- a/test/filepath_test.go +++ b/test/filepath_test.go @@ -32,9 +32,11 @@ func TestWithWd(t *testing.T) { }) assert.Nil(t, os.Mkdir(tmpDir.Path(), 0o644)) - assert.Panics(t, func() { - WithWd(tmpDir.Path(), func(string) {}) - }) + if os.Getuid() != 0 { // root is not subject to this check + assert.Panics(t, func() { + WithWd(tmpDir.Path(), func(string) {}) + }) + } assert.Nil(t, os.Remove(tmpDir.Path())) assert.Nil(t, os.Mkdir(tmpDir.Path(), 0o755))