From a14ba3ef651ee34878504817d9c3ae28f4a93d19 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:23:49 +0100 Subject: [PATCH 01/18] ci: build on native arm64 runners, pin actions to SHAs Use ubuntu-24.04-arm runners for arm64 builds (and tests) instead of QEMU emulation, mirroring appwrite/docker-base. Each arch is built and pushed separately, then a manifest job assembles the multi-arch tag. Pin all third-party actions to commit SHAs with version comments. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/build-and-push.yml | 58 +++++++++++++++++++++------- .github/workflows/test.yml | 30 ++++++++------ 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index fa33657..69d6192 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -4,37 +4,65 @@ on: release: types: [published] +permissions: + contents: read + env: REGISTRY: docker.io IMAGE_NAME: appwrite/utopia-base TAG: ${{ github.event.release.tag_name }} +# https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/ jobs: - build: - runs-on: ubuntu-latest + build_and_push: + runs-on: ${{ matrix.os }} strategy: matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5'] # add PHP versions as required + php-versions: ['8.2', '8.3', '8.4', '8.5'] + arch: [amd64, arm64] + include: + - arch: amd64 + os: ubuntu-24.04 + - arch: arm64 + os: ubuntu-24.04-arm steps: - name: Checkout the repo - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + - name: Build an image from Dockerfile + run: | + docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-${{ matrix.arch }} php-${{ matrix.php-versions }}/. - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + - name: Push an image + run: | + docker image push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-${{ matrix.arch }} - - name: Build and push - uses: docker/build-push-action@v4 + manifest: + needs: build_and_push + runs-on: ubuntu-24.04 + strategy: + matrix: + php-versions: ['8.2', '8.3', '8.4', '8.5'] + steps: + - name: Login to DockerHub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: - context: php-${{ matrix.php-versions }}/. - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }} + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Create manifest + run: | + docker manifest create \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-arm64 + + - name: Push manifest + run: | + docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ce4384..32d65b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,27 +5,35 @@ on: branches: - main +permissions: + contents: read + env: REGISTRY: docker.io IMAGE_NAME: appwrite/base jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5'] # add PHP versions as required + php-versions: ['8.2', '8.3', '8.4', '8.5'] + arch: [amd64, arm64] + include: + - arch: amd64 + os: ubuntu-24.04 + - arch: arm64 + os: ubuntu-24.04-arm steps: - name: Checkout the repo - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Setup container structure test + - name: Build an image from Dockerfile run: | - curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 - chmod +x container-structure-test-linux-amd64 - sudo mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test + docker image build --tag utopia-base-test php-${{ matrix.php-versions }}/. - - name: Build & run container structure test - run: | - docker build -t utopia-base-test php-${{ matrix.php-versions }}/. - container-structure-test test --image utopia-base-test --config tests.yaml php-${{ matrix.php-versions }}/tests.yaml + - name: Run container structure tests + uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 + with: + image: utopia-base-test + config: php-${{ matrix.php-versions }}/tests.yaml From 49a9235963d147f67d1d58694e2925f46cdc7bc3 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:26:33 +0100 Subject: [PATCH 02/18] ci: add Trivy CVE scan and Dive image-efficiency check Trivy scans built images for CRITICAL/HIGH CVEs and uploads SARIF to the GitHub Security tab. Runs on push, PR, and a weekly cron so newly-disclosed CVEs in already-shipped base images get flagged. Dive enforces layer-efficiency thresholds via .dive-ci.yml to catch Dockerfile regressions (e.g. uncleared apt caches). Both workflows fan out per PHP version. Co-Authored-By: Claude Opus 4.7 --- .dive-ci.yml | 13 ++++++++++ .github/workflows/dive.yml | 34 ++++++++++++++++++++++++++ .github/workflows/trivy.yml | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 .dive-ci.yml create mode 100644 .github/workflows/dive.yml create mode 100644 .github/workflows/trivy.yml diff --git a/.dive-ci.yml b/.dive-ci.yml new file mode 100644 index 0000000..f81d725 --- /dev/null +++ b/.dive-ci.yml @@ -0,0 +1,13 @@ +rules: + # If the efficiency is measured below X%, mark as failed. + # Expressed as a ratio between 0-1. + lowestEfficiency: 0.90 + + # If the amount of wasted space is at least X or larger than X, mark as failed. + # Expressed in B, KB, MB, and GB. + highestWastedBytes: 128MB + + # If the amount of wasted space makes up for X% or more of the image, mark as failed. + # Note: the base image layer is NOT included in the total image size. + # Expressed as a ratio between 0-1; fails if the threshold is met or crossed. + highestUserWastedPercent: 0.10 diff --git a/.github/workflows/dive.yml b/.github/workflows/dive.yml new file mode 100644 index 0000000..398efd9 --- /dev/null +++ b/.github/workflows/dive.yml @@ -0,0 +1,34 @@ +name: Dive Test + +on: + push: + pull_request: + branches: ["main"] + +permissions: + contents: read + +env: + REGISTRY: docker.io + IMAGE_NAME: appwrite/utopia-base + +jobs: + dive: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.2', '8.3', '8.4', '8.5'] + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build an image from Dockerfile + run: | + docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. + + - name: Dive + uses: yuichielectric/dive-action@c2bf577bd2ed379a30c45597cf304f9f269dbdfe # 0.0.4 + with: + config-file: ${{ github.workspace }}/.dive-ci.yml + image: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 0000000..178e4bf --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,48 @@ +# https://github.com/aquasecurity/trivy-action +name: Trivy Scan + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + schedule: + - cron: '43 11 * * 6' + +permissions: + contents: read + security-events: write + actions: read + +env: + REGISTRY: docker.io + IMAGE_NAME: appwrite/utopia-base + +jobs: + trivy: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.2', '8.3', '8.4', '8.5'] + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build an image from Dockerfile + run: | + docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. + + - name: Run Trivy vulnerability scanner (sarif report) + uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 + with: + format: 'sarif' + image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' + output: 'trivy-results-php-${{ matrix.php-versions }}.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy scan results + uses: github/codeql-action/upload-sarif@96a56f9e1619f4ee5970aabb968dc392d1e9b425 # codeql-bundle-v2.25.4 + with: + sarif_file: 'trivy-results-php-${{ matrix.php-versions }}.sarif' + category: 'php-${{ matrix.php-versions }}' From e5ef28ad7a021ef0fc14f5919488d0dfdc8e4b33 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:30:27 +0100 Subject: [PATCH 03/18] fix: drop PHP 8.2 support PHP 8.2 reaches end-of-life on 2026-12-31; drop it now to focus on 8.3/8.4/8.5. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/build-and-push.yml | 4 +- .github/workflows/dive.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/trivy.yml | 2 +- php-8.2/Dockerfile | 209 --------------------------- php-8.2/tests.yaml | 34 ----- 6 files changed, 5 insertions(+), 248 deletions(-) delete mode 100644 php-8.2/Dockerfile delete mode 100644 php-8.2/tests.yaml diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index 69d6192..89d2c1e 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -18,7 +18,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5'] + php-versions: ['8.3', '8.4', '8.5'] arch: [amd64, arm64] include: - arch: amd64 @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5'] + php-versions: ['8.3', '8.4', '8.5'] steps: - name: Login to DockerHub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 diff --git a/.github/workflows/dive.yml b/.github/workflows/dive.yml index 398efd9..be5c30c 100644 --- a/.github/workflows/dive.yml +++ b/.github/workflows/dive.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5'] + php-versions: ['8.3', '8.4', '8.5'] steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32d65b0..aaeb2ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5'] + php-versions: ['8.3', '8.4', '8.5'] arch: [amd64, arm64] include: - arch: amd64 diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 178e4bf..6398fc3 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.2', '8.3', '8.4', '8.5'] + php-versions: ['8.3', '8.4', '8.5'] steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/php-8.2/Dockerfile b/php-8.2/Dockerfile deleted file mode 100644 index ea02f69..0000000 --- a/php-8.2/Dockerfile +++ /dev/null @@ -1,209 +0,0 @@ -FROM php:8.2-cli-alpine AS compile - -ENV PHP_REDIS_VERSION="6.1.0" \ - PHP_SWOOLE_VERSION="v6.2.0" \ - PHP_IMAGICK_VERSION="3.7.0" \ - PHP_YAML_VERSION="2.2.4" \ - PHP_MAXMINDDB_VERSION="v1.12.0" \ - PHP_SCRYPT_VERSION="2.0.1" \ - PHP_ZSTD_VERSION="0.14.0" \ - PHP_BROTLI_VERSION="0.15.0" \ - PHP_SNAPPY_VERSION="0.2.2" \ - PHP_LZ4_VERSION="0.4.4" \ - PHP_XDEBUG_VERSION="3.4.1" \ - PHP_MONGO_VERSION="2.2.1" - -RUN \ - apk add --no-cache --virtual .deps \ - linux-headers \ - icu-dev \ - make \ - automake \ - autoconf \ - gcc \ - g++ \ - git \ - zlib-dev \ - openssl-dev \ - yaml-dev \ - imagemagick \ - imagemagick-dev \ - libjpeg-turbo-dev \ - jpeg-dev \ - libjxl-dev \ - libmaxminddb-dev \ - zstd-dev \ - brotli-dev \ - lz4-dev \ - curl-dev \ - cmake - -RUN docker-php-ext-install sockets - -FROM compile AS redis -RUN \ - # Redis Extension - git clone --depth 1 --branch $PHP_REDIS_VERSION https://github.com/phpredis/phpredis.git && \ - cd phpredis && \ - phpize && \ - ./configure && \ - make && make install - -## Swoole Extension -FROM compile AS swoole -RUN \ - git clone --depth 1 --branch $PHP_SWOOLE_VERSION https://github.com/swoole/swoole-src.git && \ - cd swoole-src && \ - phpize && \ - ./configure --enable-sockets --enable-http2 --enable-openssl --enable-swoole-curl && \ - make && make install && \ - cd .. - -## Imagick Extension -FROM compile AS imagick -RUN \ - git clone --depth 1 --branch $PHP_IMAGICK_VERSION https://github.com/imagick/imagick && \ - cd imagick && \ - phpize && \ - ./configure && \ - make && make install - -## YAML Extension -FROM compile AS yaml -RUN \ - git clone --depth 1 --branch $PHP_YAML_VERSION https://github.com/php/pecl-file_formats-yaml && \ - cd pecl-file_formats-yaml && \ - phpize && \ - ./configure && \ - make && make install - -## Maxminddb extension -FROM compile AS maxmind -RUN \ - git clone --depth 1 --branch $PHP_MAXMINDDB_VERSION https://github.com/maxmind/MaxMind-DB-Reader-php.git && \ - cd MaxMind-DB-Reader-php && \ - cd ext && \ - phpize && \ - ./configure && \ - make && make install - -# Zstd Compression -FROM compile AS zstd -RUN git clone --recursive -n https://github.com/kjdev/php-ext-zstd.git \ - && cd php-ext-zstd \ - && git checkout $PHP_ZSTD_VERSION \ - && phpize \ - && ./configure --with-libzstd \ - && make && make install - -## Brotli Extension -FROM compile AS brotli -RUN git clone https://github.com/kjdev/php-ext-brotli.git \ - && cd php-ext-brotli \ - && git reset --hard $PHP_BROTLI_VERSION \ - && phpize \ - && ./configure --with-libbrotli \ - && make && make install - -## LZ4 Extension -FROM compile AS lz4 -RUN git clone --recursive https://github.com/kjdev/php-ext-lz4.git \ - && cd php-ext-lz4 \ - && git reset --hard $PHP_LZ4_VERSION \ - && phpize \ - && ./configure --with-lz4-includedir=/usr \ - && make && make install - -## Snappy Extension -FROM compile AS snappy -RUN git clone --recursive https://github.com/kjdev/php-ext-snappy.git \ - && cd php-ext-snappy \ - && git reset --hard $PHP_SNAPPY_VERSION \ - && phpize \ - && ./configure \ - && make && make install - -## Scrypt Extension -FROM compile AS scrypt -RUN git clone --depth 1 https://github.com/DomBlack/php-scrypt.git \ - && cd php-scrypt \ - && git reset --hard $PHP_SCRYPT_VERSION \ - && phpize \ - && ./configure --enable-scrypt \ - && make && make install - -## XDebug Extension -FROM compile AS xdebug -RUN \ - git clone --depth 1 --branch $PHP_XDEBUG_VERSION https://github.com/xdebug/xdebug && \ - cd xdebug && \ - phpize && \ - ./configure && \ - make && make install - -## MongoDB Extension -FROM compile AS mongodb -RUN \ - git clone --depth 1 --recursive --branch $PHP_MONGO_VERSION https://github.com/mongodb/mongo-php-driver.git && \ - cd mongo-php-driver && \ - phpize && \ - ./configure && \ - make && make install - -FROM php:8.2-cli-alpine AS final - -LABEL maintainer="team@appwrite.io" - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN \ - apk update \ - && apk add --no-cache \ - linux-headers \ - rsync \ - brotli-dev \ - lz4-dev \ - zstd-dev \ - yaml-dev \ - imagemagick \ - libjpeg-turbo \ - libjxl \ - libavif \ - libheif \ - imagemagick-heic \ - libgomp \ - libwebp \ - git \ - && rm -rf /var/cache/apk/* - -RUN docker-php-ext-install sockets - -WORKDIR /usr/src/code - -COPY --from=swoole /usr/local/lib/php/extensions/no-debug-non-zts-20220829/swoole.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=redis /usr/local/lib/php/extensions/no-debug-non-zts-20220829/redis.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=imagick /usr/local/lib/php/extensions/no-debug-non-zts-20220829/imagick.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=yaml /usr/local/lib/php/extensions/no-debug-non-zts-20220829/yaml.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=scrypt /usr/local/lib/php/extensions/no-debug-non-zts-20220829/scrypt.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=zstd /usr/local/lib/php/extensions/no-debug-non-zts-20220829/zstd.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=brotli /usr/local/lib/php/extensions/no-debug-non-zts-20220829/brotli.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=lz4 /usr/local/lib/php/extensions/no-debug-non-zts-20220829/lz4.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=snappy /usr/local/lib/php/extensions/no-debug-non-zts-20220829/snappy.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=xdebug /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ -COPY --from=mongodb /usr/local/lib/php/extensions/no-debug-non-zts-20220829/mongodb.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ - -# Enable Extensions -RUN echo extension=swoole.so >> /usr/local/etc/php/conf.d/swoole.ini -RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini -RUN echo extension=imagick.so >> /usr/local/etc/php/conf.d/imagick.ini -RUN echo extension=yaml.so >> /usr/local/etc/php/conf.d/yaml.ini -RUN echo extension=scrypt.so >> /usr/local/etc/php/conf.d/scrypt.ini -RUN echo extension=zstd.so >> /usr/local/etc/php/conf.d/zstd.ini -RUN echo extension=brotli.so >> /usr/local/etc/php/conf.d/brotli.ini -RUN echo extension=lz4.so >> /usr/local/etc/php/conf.d/lz4.ini -RUN echo extension=snappy.so >> /usr/local/etc/php/conf.d/snappy.ini -RUN echo extension=mongodb.so >> /usr/local/etc/php/conf.d/mongodb.ini - -EXPOSE 80 - -CMD [ "tail", "-f", "/dev/null" ] diff --git a/php-8.2/tests.yaml b/php-8.2/tests.yaml deleted file mode 100644 index 9b1f136..0000000 --- a/php-8.2/tests.yaml +++ /dev/null @@ -1,34 +0,0 @@ -schemaVersion: '2.0.0' - -fileExistenceTests: - ## Extension files - - name: 'Check swoole extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/swoole.so - shouldExist: true - - name: 'Check redis extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/redis.so - shouldExist: true - - name: 'Check imagick extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/imagick.so - shouldExist: true - - name: 'Check yaml extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/yaml.so - shouldExist: true - - name: 'Check scrypt extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/scrypt.so - shouldExist: true - - name: 'Check zstd extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/zstd.so - shouldExist: true - - name: 'Check brotli extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/brotli.so - shouldExist: true - - name: 'Check lz4 extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/lz4.so - shouldExist: true - - name: 'Check snappy extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/snappy.so - shouldExist: true - - name: 'Check mongodb extension' - path: /usr/local/lib/php/extensions/no-debug-non-zts-20220829/mongodb.so - shouldExist: true \ No newline at end of file From 336901e7677019377638059d046d4680831335c8 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:32:49 +0100 Subject: [PATCH 04/18] ci: combine structure/dive/trivy into a single ci.yml workflow Three separate workflow files each created their own check rows and the push+pull_request triggers fired duplicate runs on every PR commit. Merge into one workflow with three jobs and limit triggers to push-on-main plus PRs (cron retained for Trivy CVE refresh). Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 96 +++++++++++++++++++++++++++++++++++++ .github/workflows/dive.yml | 34 ------------- .github/workflows/test.yml | 39 --------------- .github/workflows/trivy.yml | 48 ------------------- 4 files changed, 96 insertions(+), 121 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/dive.yml delete mode 100644 .github/workflows/test.yml delete mode 100644 .github/workflows/trivy.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8ee4846 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,96 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '43 11 * * 6' + +permissions: + contents: read + security-events: write + actions: read + +env: + REGISTRY: docker.io + IMAGE_NAME: appwrite/utopia-base + +jobs: + structure-test: + name: Structure (${{ matrix.php-versions }}, ${{ matrix.arch }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + arch: [amd64, arm64] + include: + - arch: amd64 + os: ubuntu-24.04 + - arch: arm64 + os: ubuntu-24.04-arm + steps: + - name: Checkout the repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build image + run: | + docker image build --tag utopia-base-test php-${{ matrix.php-versions }}/. + + - name: Run container structure tests + uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 + with: + image: utopia-base-test + config: php-${{ matrix.php-versions }}/tests.yaml + + dive: + name: Dive (${{ matrix.php-versions }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build image + run: | + docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. + + - name: Dive + uses: yuichielectric/dive-action@c2bf577bd2ed379a30c45597cf304f9f269dbdfe # 0.0.4 + with: + config-file: ${{ github.workspace }}/.dive-ci.yml + image: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' + + trivy: + name: Trivy (${{ matrix.php-versions }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build image + run: | + docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. + + - name: Run Trivy vulnerability scanner (sarif report) + uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 + with: + format: 'sarif' + image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' + output: 'trivy-results-php-${{ matrix.php-versions }}.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy scan results + uses: github/codeql-action/upload-sarif@96a56f9e1619f4ee5970aabb968dc392d1e9b425 # codeql-bundle-v2.25.4 + with: + sarif_file: 'trivy-results-php-${{ matrix.php-versions }}.sarif' + category: 'php-${{ matrix.php-versions }}' diff --git a/.github/workflows/dive.yml b/.github/workflows/dive.yml deleted file mode 100644 index be5c30c..0000000 --- a/.github/workflows/dive.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Dive Test - -on: - push: - pull_request: - branches: ["main"] - -permissions: - contents: read - -env: - REGISTRY: docker.io - IMAGE_NAME: appwrite/utopia-base - -jobs: - dive: - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4', '8.5'] - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Build an image from Dockerfile - run: | - docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. - - - name: Dive - uses: yuichielectric/dive-action@c2bf577bd2ed379a30c45597cf304f9f269dbdfe # 0.0.4 - with: - config-file: ${{ github.workspace }}/.dive-ci.yml - image: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index aaeb2ba..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Test container structure - -on: - pull_request: - branches: - - main - -permissions: - contents: read - -env: - REGISTRY: docker.io - IMAGE_NAME: appwrite/base - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - php-versions: ['8.3', '8.4', '8.5'] - arch: [amd64, arm64] - include: - - arch: amd64 - os: ubuntu-24.04 - - arch: arm64 - os: ubuntu-24.04-arm - steps: - - name: Checkout the repo - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Build an image from Dockerfile - run: | - docker image build --tag utopia-base-test php-${{ matrix.php-versions }}/. - - - name: Run container structure tests - uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 - with: - image: utopia-base-test - config: php-${{ matrix.php-versions }}/tests.yaml diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml deleted file mode 100644 index 6398fc3..0000000 --- a/.github/workflows/trivy.yml +++ /dev/null @@ -1,48 +0,0 @@ -# https://github.com/aquasecurity/trivy-action -name: Trivy Scan - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - schedule: - - cron: '43 11 * * 6' - -permissions: - contents: read - security-events: write - actions: read - -env: - REGISTRY: docker.io - IMAGE_NAME: appwrite/utopia-base - -jobs: - trivy: - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4', '8.5'] - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Build an image from Dockerfile - run: | - docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. - - - name: Run Trivy vulnerability scanner (sarif report) - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 - with: - format: 'sarif' - image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' - output: 'trivy-results-php-${{ matrix.php-versions }}.sarif' - severity: 'CRITICAL,HIGH' - - - name: Upload Trivy scan results - uses: github/codeql-action/upload-sarif@96a56f9e1619f4ee5970aabb968dc392d1e9b425 # codeql-bundle-v2.25.4 - with: - sarif_file: 'trivy-results-php-${{ matrix.php-versions }}.sarif' - category: 'php-${{ matrix.php-versions }}' From 4defb2a6231267179f35921a802c436c23fd0cbb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:34:10 +0100 Subject: [PATCH 05/18] ci: rename build-and-push.yml to release.yml Trigger is `release: published`; matching the filename to the trigger makes intent obvious at a glance. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/{build-and-push.yml => release.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{build-and-push.yml => release.yml} (98%) diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/release.yml similarity index 98% rename from .github/workflows/build-and-push.yml rename to .github/workflows/release.yml index 89d2c1e..225a6a5 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Build and Push to DockerHub +name: Release on: release: From d6f384453c9d1dc9cd5261d8fcb8baf0acb0af82 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:38:42 +0100 Subject: [PATCH 06/18] ci: build once, share via artifacts; add concurrency cancel Previously each of structure-test/dive/trivy rebuilt the same Dockerfile, amounting to 12 builds per CI run for 3 Dockerfiles. Split into a single build job per (php, arch) that saves the image as an artifact; downstream jobs load it instead of rebuilding (3 amd64 builds reused twice each, plus the 3 arm64 builds for structure-test). Add concurrency group keyed on ref so superseded commits' runs cancel instead of stacking up. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 70 ++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ee4846..0989b69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,10 @@ on: schedule: - cron: '43 11 * * 6' +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read security-events: write @@ -18,8 +22,8 @@ env: IMAGE_NAME: appwrite/utopia-base jobs: - structure-test: - name: Structure (${{ matrix.php-versions }}, ${{ matrix.arch }}) + build: + name: Build (${{ matrix.php-versions }}, ${{ matrix.arch }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -39,6 +43,42 @@ jobs: run: | docker image build --tag utopia-base-test php-${{ matrix.php-versions }}/. + - name: Save image + run: docker image save utopia-base-test -o image.tar + + - name: Upload image artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: image-php-${{ matrix.php-versions }}-${{ matrix.arch }} + path: image.tar + retention-days: 1 + + structure-test: + name: Structure (${{ matrix.php-versions }}, ${{ matrix.arch }}) + needs: build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + arch: [amd64, arm64] + include: + - arch: amd64 + os: ubuntu-24.04 + - arch: arm64 + os: ubuntu-24.04-arm + steps: + - name: Checkout the repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download image artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: image-php-${{ matrix.php-versions }}-${{ matrix.arch }} + + - name: Load image + run: docker image load -i image.tar + - name: Run container structure tests uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 with: @@ -47,6 +87,7 @@ jobs: dive: name: Dive (${{ matrix.php-versions }}) + needs: build runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -56,18 +97,23 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Build image - run: | - docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. + - name: Download image artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: image-php-${{ matrix.php-versions }}-amd64 + + - name: Load image + run: docker image load -i image.tar - name: Dive uses: yuichielectric/dive-action@c2bf577bd2ed379a30c45597cf304f9f269dbdfe # 0.0.4 with: config-file: ${{ github.workspace }}/.dive-ci.yml - image: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' + image: utopia-base-test trivy: name: Trivy (${{ matrix.php-versions }}) + needs: build runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -77,15 +123,19 @@ jobs: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Build image - run: | - docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} php-${{ matrix.php-versions }}/. + - name: Download image artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: image-php-${{ matrix.php-versions }}-amd64 + + - name: Load image + run: docker image load -i image.tar - name: Run Trivy vulnerability scanner (sarif report) uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 with: format: 'sarif' - image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}' + image-ref: 'utopia-base-test' output: 'trivy-results-php-${{ matrix.php-versions }}.sarif' severity: 'CRITICAL,HIGH' From 68d5dcba1388478a2242259b9ae065824686bb49 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:44:14 +0100 Subject: [PATCH 07/18] ci: use docker/metadata-action; release promotes instead of rebuilds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI now pushes per-commit images on main (php-X.Y-[-arch]) after tests pass and assembles the multi-arch manifest with buildx imagetools create. Tagging is driven by docker/metadata-action. Release workflow drops its build/push matrix entirely — it just calls buildx imagetools create to retag the already-tested per-commit manifest to the release tag. Server-side, no rebuild, takes seconds. Add cleanup.yml: daily scheduled job that deletes CI tags matching php-X.Y-[-arch] older than TTL_DAYS via the Docker Hub API. Released semver tags don't match the regex and are never touched. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 69 +++++++++++++++++++++++++++++++++++ .github/workflows/cleanup.yml | 58 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 61 +++++++++++-------------------- 3 files changed, 148 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/cleanup.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0989b69..93d06a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,3 +144,72 @@ jobs: with: sarif_file: 'trivy-results-php-${{ matrix.php-versions }}.sarif' category: 'php-${{ matrix.php-versions }}' + + push: + name: Push (${{ matrix.php-versions }}, ${{ matrix.arch }}) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: [structure-test, dive, trivy] + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + arch: [amd64, arm64] + steps: + - name: Login to DockerHub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Download image artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: image-php-${{ matrix.php-versions }}-${{ matrix.arch }} + + - name: Load image + run: docker image load -i image.tar + + - name: Docker meta + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + latest=false + suffix=-${{ matrix.arch }} + tags: | + type=raw,value=php-${{ matrix.php-versions }}-${{ github.sha }} + + - name: Tag and push + run: | + for tag in ${{ steps.meta.outputs.tags }}; do + docker tag utopia-base-test "$tag" + docker push "$tag" + done + + manifest: + name: Manifest (${{ matrix.php-versions }}) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: push + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + steps: + - name: Login to DockerHub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Create multi-arch manifest + run: | + docker buildx imagetools create \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}-arm64 diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 0000000..102a79e --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,58 @@ +name: Cleanup stale CI tags + +on: + schedule: + - cron: '0 5 * * *' + workflow_dispatch: + +permissions: + contents: read + +env: + REGISTRY: docker.io + REPO: appwrite/utopia-base + # Delete CI tags (those matching php-X.Y-[-arch]) last pushed more + # than this many days ago. Released semver tags don't match the regex + # and are never touched. + TTL_DAYS: 14 + +jobs: + prune: + runs-on: ubuntu-24.04 + steps: + - name: Prune stale per-commit tags + env: + DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME }} + DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + run: | + set -euo pipefail + + token=$(curl -s -H "Content-Type: application/json" \ + -X POST \ + -d "{\"username\": \"$DOCKERHUB_USERNAME\", \"password\": \"$DOCKERHUB_PASSWORD\"}" \ + https://hub.docker.com/v2/users/login/ | jq -r .token) + + cutoff=$(date -u -d "${TTL_DAYS} days ago" +%s) + # CI tags look like php-8.3-<40-char sha>[-amd64|-arm64] + pattern='^php-[0-9]+\.[0-9]+-[0-9a-f]{40}(-(amd64|arm64))?$' + + url="https://hub.docker.com/v2/repositories/${REPO}/tags?page_size=100" + deleted=0 + while [ -n "$url" ] && [ "$url" != "null" ]; do + page=$(curl -s -H "Authorization: JWT $token" "$url") + url=$(echo "$page" | jq -r '.next') + + echo "$page" | jq -r '.results[] | "\(.name)\t\(.tag_last_pushed)"' | \ + while IFS=$'\t' read -r name pushed; do + [[ "$name" =~ $pattern ]] || continue + pushed_ts=$(date -u -d "$pushed" +%s) + if [ "$pushed_ts" -lt "$cutoff" ]; then + echo "Deleting $name (pushed $pushed)" + curl -s -X DELETE \ + -H "Authorization: JWT $token" \ + "https://hub.docker.com/v2/repositories/${REPO}/tags/${name}/" + deleted=$((deleted + 1)) + fi + done + done + echo "Done." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 225a6a5..a66864f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,59 +10,40 @@ permissions: env: REGISTRY: docker.io IMAGE_NAME: appwrite/utopia-base - TAG: ${{ github.event.release.tag_name }} -# https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/ +# Promote the already-built, already-tested CI image (built on the +# release's target commit) to the release tag — no rebuild. jobs: - build_and_push: - runs-on: ${{ matrix.os }} + promote: + name: Promote (${{ matrix.php-versions }}) + runs-on: ubuntu-24.04 strategy: + fail-fast: false matrix: php-versions: ['8.3', '8.4', '8.5'] - arch: [amd64, arm64] - include: - - arch: amd64 - os: ubuntu-24.04 - - arch: arm64 - os: ubuntu-24.04-arm steps: - - name: Checkout the repo - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Login to DockerHub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - - name: Build an image from Dockerfile - run: | - docker image build --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-${{ matrix.arch }} php-${{ matrix.php-versions }}/. - - - name: Push an image - run: | - docker image push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-${{ matrix.arch }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - manifest: - needs: build_and_push - runs-on: ubuntu-24.04 - strategy: - matrix: - php-versions: ['8.3', '8.4', '8.5'] - steps: - - name: Login to DockerHub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + - name: Docker meta + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} - - - name: Create manifest - run: | - docker manifest create \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }} \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-amd64 \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }}-arm64 + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + latest=false + tags: | + type=raw,value=php-${{ matrix.php-versions }}-${{ github.event.release.tag_name }} - - name: Push manifest + - name: Promote multi-arch image run: | - docker manifest push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ env.TAG }} + SRC=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} + for tag in ${{ steps.meta.outputs.tags }}; do + docker buildx imagetools create -t "$tag" "$SRC" + done From 429632d05c06c2a0006d41f7a6842badd44f4647 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:45:53 +0100 Subject: [PATCH 08/18] ci: release waits for CI run on the same commit before promoting If a release is cut before CI finishes pushing the per-commit image, buildx imagetools create would fail with manifest-not-found. Add a wait-for-ci job that polls the CI run for github.sha until it succeeds (or times out at 30min), then runs the promote matrix. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/release.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a66864f..dc362d5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: permissions: contents: read + actions: read env: REGISTRY: docker.io @@ -14,8 +15,37 @@ env: # Promote the already-built, already-tested CI image (built on the # release's target commit) to the release tag — no rebuild. jobs: + wait-for-ci: + name: Wait for CI + runs-on: ubuntu-24.04 + steps: + - name: Wait for CI workflow on ${{ github.sha }} + env: + GH_TOKEN: ${{ github.token }} + SHA: ${{ github.sha }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + deadline=$(( $(date +%s) + 1800 )) # 30 min + while :; do + run=$(gh run list --repo "$REPO" --workflow ci.yml --commit "$SHA" \ + --json databaseId,status,conclusion --limit 1) + status=$(echo "$run" | jq -r '.[0].status // "missing"') + conclusion=$(echo "$run" | jq -r '.[0].conclusion // ""') + echo "CI status: $status, conclusion: $conclusion" + if [ "$status" = "completed" ]; then + if [ "$conclusion" = "success" ]; then exit 0; fi + echo "CI did not succeed (conclusion=$conclusion)"; exit 1 + fi + if [ "$(date +%s)" -gt "$deadline" ]; then + echo "Timed out waiting for CI"; exit 1 + fi + sleep 30 + done + promote: name: Promote (${{ matrix.php-versions }}) + needs: wait-for-ci runs-on: ubuntu-24.04 strategy: fail-fast: false From f2efa479b10dc6252a2a947fa229548ce14bebb5 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:47:37 +0100 Subject: [PATCH 09/18] ci: serialize release behind CI via shared concurrency group Replace the polling wait-for-ci job with a shared per-commit concurrency group ('build-'). CI and release on the same commit can't run concurrently; release queues until CI's group slot is free. PR runs use a separate group keyed on ref so superseded PR commits keep cancelling earlier runs as before. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 7 +++++-- .github/workflows/release.yml | 36 ++++++----------------------------- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93d06a7..ef2f5b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,8 +9,11 @@ on: - cron: '43 11 * * 6' concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + # PR runs cancel earlier runs on the same ref. Push/schedule runs share + # the per-commit group used by release.yml so a release queues behind + # CI on the same commit instead of racing. + group: ${{ github.event_name == 'pull_request' && format('ci-pr-{0}', github.ref) || format('build-{0}', github.sha) }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc362d5..4a12be9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,14 @@ on: release: types: [published] +# Share the per-commit build group with ci.yml. If CI is still running +# on this commit, release queues behind it. +concurrency: + group: build-${{ github.sha }} + cancel-in-progress: false + permissions: contents: read - actions: read env: REGISTRY: docker.io @@ -15,37 +20,8 @@ env: # Promote the already-built, already-tested CI image (built on the # release's target commit) to the release tag — no rebuild. jobs: - wait-for-ci: - name: Wait for CI - runs-on: ubuntu-24.04 - steps: - - name: Wait for CI workflow on ${{ github.sha }} - env: - GH_TOKEN: ${{ github.token }} - SHA: ${{ github.sha }} - REPO: ${{ github.repository }} - run: | - set -euo pipefail - deadline=$(( $(date +%s) + 1800 )) # 30 min - while :; do - run=$(gh run list --repo "$REPO" --workflow ci.yml --commit "$SHA" \ - --json databaseId,status,conclusion --limit 1) - status=$(echo "$run" | jq -r '.[0].status // "missing"') - conclusion=$(echo "$run" | jq -r '.[0].conclusion // ""') - echo "CI status: $status, conclusion: $conclusion" - if [ "$status" = "completed" ]; then - if [ "$conclusion" = "success" ]; then exit 0; fi - echo "CI did not succeed (conclusion=$conclusion)"; exit 1 - fi - if [ "$(date +%s)" -gt "$deadline" ]; then - echo "Timed out waiting for CI"; exit 1 - fi - sleep 30 - done - promote: name: Promote (${{ matrix.php-versions }}) - needs: wait-for-ci runs-on: ubuntu-24.04 strategy: fail-fast: false From d3266dd8306fcb65df0db71167d1c3c6b12f910e Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 16:51:44 +0100 Subject: [PATCH 10/18] ci: tidy job/step names; install container-structure-test directly Audit pass on workflow names: unify 'Checkout the repo' / 'Checkout code' to 'Checkout', shorten verbose Trivy/manifest/promote step names, and rename 'Cleanup stale CI tags' workflow to 'Cleanup' for consistency with 'CI' / 'Release'. Standardize on 'Docker Hub' (two words). Replace plexsystems/container-structure-test-action with a direct binary install: the action is a Docker action that hardcodes the amd64 binary, so it fails with 'exec format error' on the ubuntu-24.04-arm runners. Install the matching linux- binary instead. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 31 +++++++++++++++++-------------- .github/workflows/cleanup.yml | 5 +++-- .github/workflows/release.yml | 4 ++-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef2f5b0..b291559 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: - arch: arm64 os: ubuntu-24.04-arm steps: - - name: Checkout the repo + - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Build image @@ -71,7 +71,7 @@ jobs: - arch: arm64 os: ubuntu-24.04-arm steps: - - name: Checkout the repo + - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download image artifact @@ -82,11 +82,14 @@ jobs: - name: Load image run: docker image load -i image.tar + - name: Install container-structure-test + run: | + curl -sLo cst https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-${{ matrix.arch }} + chmod +x cst + sudo mv cst /usr/local/bin/container-structure-test + - name: Run container structure tests - uses: plexsystems/container-structure-test-action@c0a028aa96e8e82ae35be556040340cbb3e280ca # v0.3.0 - with: - image: utopia-base-test - config: php-${{ matrix.php-versions }}/tests.yaml + run: container-structure-test test --image utopia-base-test --config php-${{ matrix.php-versions }}/tests.yaml dive: name: Dive (${{ matrix.php-versions }}) @@ -97,7 +100,7 @@ jobs: matrix: php-versions: ['8.3', '8.4', '8.5'] steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download image artifact @@ -123,7 +126,7 @@ jobs: matrix: php-versions: ['8.3', '8.4', '8.5'] steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Download image artifact @@ -134,7 +137,7 @@ jobs: - name: Load image run: docker image load -i image.tar - - name: Run Trivy vulnerability scanner (sarif report) + - name: Run Trivy uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 with: format: 'sarif' @@ -142,7 +145,7 @@ jobs: output: 'trivy-results-php-${{ matrix.php-versions }}.sarif' severity: 'CRITICAL,HIGH' - - name: Upload Trivy scan results + - name: Upload SARIF uses: github/codeql-action/upload-sarif@96a56f9e1619f4ee5970aabb968dc392d1e9b425 # codeql-bundle-v2.25.4 with: sarif_file: 'trivy-results-php-${{ matrix.php-versions }}.sarif' @@ -159,7 +162,7 @@ jobs: php-versions: ['8.3', '8.4', '8.5'] arch: [amd64, arm64] steps: - - name: Login to DockerHub + - name: Login to Docker Hub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ vars.DOCKERHUB_USERNAME }} @@ -184,7 +187,7 @@ jobs: tags: | type=raw,value=php-${{ matrix.php-versions }}-${{ github.sha }} - - name: Tag and push + - name: Push image run: | for tag in ${{ steps.meta.outputs.tags }}; do docker tag utopia-base-test "$tag" @@ -201,7 +204,7 @@ jobs: matrix: php-versions: ['8.3', '8.4', '8.5'] steps: - - name: Login to DockerHub + - name: Login to Docker Hub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ vars.DOCKERHUB_USERNAME }} @@ -210,7 +213,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Create multi-arch manifest + - name: Create manifest run: | docker buildx imagetools create \ -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} \ diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 102a79e..da1aa72 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -1,4 +1,4 @@ -name: Cleanup stale CI tags +name: Cleanup on: schedule: @@ -18,9 +18,10 @@ env: jobs: prune: + name: Prune runs-on: ubuntu-24.04 steps: - - name: Prune stale per-commit tags + - name: Prune stale tags env: DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a12be9..106f436 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: matrix: php-versions: ['8.3', '8.4', '8.5'] steps: - - name: Login to DockerHub + - name: Login to Docker Hub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ vars.DOCKERHUB_USERNAME }} @@ -47,7 +47,7 @@ jobs: tags: | type=raw,value=php-${{ matrix.php-versions }}-${{ github.event.release.tag_name }} - - name: Promote multi-arch image + - name: Promote run: | SRC=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} for tag in ${{ steps.meta.outputs.tags }}; do From 6609fc4bbf914973ea9c444a57f925076c197970 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:04:18 +0100 Subject: [PATCH 11/18] ci: split push/manifest into publish-sha.yml triggered by workflow_run Job-level 'if' on a matrix job displays the unrendered '\${{ matrix.* }}' string in skipped check rows. Move push/manifest into a separate publish-sha workflow that triggers via workflow_run on CI success on main, so PRs no longer see skipped placeholder rows. The new workflow downloads CI's image artifact by run-id and pushes per-arch tags + assembles the multi-arch manifest. Concurrency group is still 'build-', shared with ci.yml and release.yml. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 68 ----------------------- .github/workflows/publish-sha.yml | 91 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/publish-sha.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b291559..448372c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,71 +151,3 @@ jobs: sarif_file: 'trivy-results-php-${{ matrix.php-versions }}.sarif' category: 'php-${{ matrix.php-versions }}' - push: - name: Push (${{ matrix.php-versions }}, ${{ matrix.arch }}) - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: [structure-test, dive, trivy] - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4', '8.5'] - arch: [amd64, arm64] - steps: - - name: Login to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 - with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} - - - name: Download image artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: image-php-${{ matrix.php-versions }}-${{ matrix.arch }} - - - name: Load image - run: docker image load -i image.tar - - - name: Docker meta - id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - flavor: | - latest=false - suffix=-${{ matrix.arch }} - tags: | - type=raw,value=php-${{ matrix.php-versions }}-${{ github.sha }} - - - name: Push image - run: | - for tag in ${{ steps.meta.outputs.tags }}; do - docker tag utopia-base-test "$tag" - docker push "$tag" - done - - manifest: - name: Manifest (${{ matrix.php-versions }}) - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: push - runs-on: ubuntu-24.04 - strategy: - fail-fast: false - matrix: - php-versions: ['8.3', '8.4', '8.5'] - steps: - - name: Login to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 - with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - - name: Create manifest - run: | - docker buildx imagetools create \ - -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }} \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}-amd64 \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${{ github.sha }}-arm64 diff --git a/.github/workflows/publish-sha.yml b/.github/workflows/publish-sha.yml new file mode 100644 index 0000000..3ee985c --- /dev/null +++ b/.github/workflows/publish-sha.yml @@ -0,0 +1,91 @@ +name: Publish SHA + +on: + workflow_run: + workflows: [CI] + types: [completed] + branches: [main] + +concurrency: + group: build-${{ github.event.workflow_run.head_sha }} + cancel-in-progress: false + +permissions: + contents: read + actions: read + +env: + REGISTRY: docker.io + IMAGE_NAME: appwrite/utopia-base + +jobs: + push: + name: Push (${{ matrix.php-versions }}, ${{ matrix.arch }}) + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + arch: [amd64, arm64] + steps: + - name: Login to Docker Hub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Download image artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: image-php-${{ matrix.php-versions }}-${{ matrix.arch }} + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + + - name: Load image + run: docker image load -i image.tar + + - name: Docker meta + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + latest=false + suffix=-${{ matrix.arch }} + tags: | + type=raw,value=php-${{ matrix.php-versions }}-${{ github.event.workflow_run.head_sha }} + + - name: Push image + run: | + for tag in ${{ steps.meta.outputs.tags }}; do + docker tag utopia-base-test "$tag" + docker push "$tag" + done + + manifest: + name: Manifest (${{ matrix.php-versions }}) + needs: push + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php-versions: ['8.3', '8.4', '8.5'] + steps: + - name: Login to Docker Hub + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Create manifest + env: + SHA: ${{ github.event.workflow_run.head_sha }} + run: | + docker buildx imagetools create \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${SHA} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${SHA}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:php-${{ matrix.php-versions }}-${SHA}-arm64 From c84dc2664a40994a03147257c9a03ea6041cb1ee Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:06:46 +0100 Subject: [PATCH 12/18] ci: rename publish-sha.yml to publish.yml Pipeline reads as CI -> publish -> release; -sha suffix leaked the tag format into the filename. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/{publish-sha.yml => publish.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{publish-sha.yml => publish.yml} (99%) diff --git a/.github/workflows/publish-sha.yml b/.github/workflows/publish.yml similarity index 99% rename from .github/workflows/publish-sha.yml rename to .github/workflows/publish.yml index 3ee985c..67f9db3 100644 --- a/.github/workflows/publish-sha.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Publish SHA +name: Publish on: workflow_run: From 28121cc3df01b102952e775ad0e27dda03fdc95b Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:08:55 +0100 Subject: [PATCH 13/18] ci: drop dead deleted counter; use curl --fail on all HTTP calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'deleted' counter in cleanup.yml was incremented inside a piped subshell so the outer scope never saw it — pure dead code; remove it and use a process substitution loop instead so the body runs in the parent shell. While here, switch all curl invocations (cleanup HTTP calls + structure-test binary download) to '-fsS' / '-fsSL' so HTTP errors surface as a non-zero exit instead of being silently saved as the response body. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 2 +- .github/workflows/cleanup.yml | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 448372c..e72e0f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: - name: Install container-structure-test run: | - curl -sLo cst https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-${{ matrix.arch }} + curl -fsSL -o cst https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-${{ matrix.arch }} chmod +x cst sudo mv cst /usr/local/bin/container-structure-test diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index da1aa72..847f62d 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -28,7 +28,7 @@ jobs: run: | set -euo pipefail - token=$(curl -s -H "Content-Type: application/json" \ + token=$(curl -fsS -H "Content-Type: application/json" \ -X POST \ -d "{\"username\": \"$DOCKERHUB_USERNAME\", \"password\": \"$DOCKERHUB_PASSWORD\"}" \ https://hub.docker.com/v2/users/login/ | jq -r .token) @@ -38,22 +38,19 @@ jobs: pattern='^php-[0-9]+\.[0-9]+-[0-9a-f]{40}(-(amd64|arm64))?$' url="https://hub.docker.com/v2/repositories/${REPO}/tags?page_size=100" - deleted=0 while [ -n "$url" ] && [ "$url" != "null" ]; do - page=$(curl -s -H "Authorization: JWT $token" "$url") + page=$(curl -fsS -H "Authorization: JWT $token" "$url") url=$(echo "$page" | jq -r '.next') - echo "$page" | jq -r '.results[] | "\(.name)\t\(.tag_last_pushed)"' | \ while IFS=$'\t' read -r name pushed; do [[ "$name" =~ $pattern ]] || continue pushed_ts=$(date -u -d "$pushed" +%s) if [ "$pushed_ts" -lt "$cutoff" ]; then echo "Deleting $name (pushed $pushed)" - curl -s -X DELETE \ + curl -fsS -X DELETE \ -H "Authorization: JWT $token" \ "https://hub.docker.com/v2/repositories/${REPO}/tags/${name}/" - deleted=$((deleted + 1)) fi - done + done < <(echo "$page" | jq -r '.results[] | "\(.name)\t\(.tag_last_pushed)"') done echo "Done." From 7c4cb0f0fe63693e9649b51f19d7342da3a8f040 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:10:48 +0100 Subject: [PATCH 14/18] ci: pin container-structure-test to v1.22.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Floating on 'latest' makes builds non-reproducible — pin the version so upgrades are explicit. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e72e0f5..82f9eb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,8 +83,10 @@ jobs: run: docker image load -i image.tar - name: Install container-structure-test + env: + CST_VERSION: v1.22.1 run: | - curl -fsSL -o cst https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-${{ matrix.arch }} + curl -fsSL -o cst "https://storage.googleapis.com/container-structure-test/${CST_VERSION}/container-structure-test-linux-${{ matrix.arch }}" chmod +x cst sudo mv cst /usr/local/bin/container-structure-test From 778e71e8509b91984047f5966ad26e3485f4fc76 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:19:41 +0100 Subject: [PATCH 15/18] ci: download container-structure-test from GitHub releases; update README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The storage.googleapis.com bucket only hosts a 'latest/' path, not versioned ones — pinned URL 404'd. Switch to the GitHub release asset URL which exposes per-version downloads. Expand README with supported PHP versions, tag scheme, and a description of the four workflows. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 2 +- README.md | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82f9eb0..177e95c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: env: CST_VERSION: v1.22.1 run: | - curl -fsSL -o cst "https://storage.googleapis.com/container-structure-test/${CST_VERSION}/container-structure-test-linux-${{ matrix.arch }}" + curl -fsSL -o cst "https://github.com/GoogleContainerTools/container-structure-test/releases/download/${CST_VERSION}/container-structure-test-linux-${{ matrix.arch }}" chmod +x cst sudo mv cst /usr/local/bin/container-structure-test diff --git a/README.md b/README.md index 327b0e1..9c85a87 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ # docker-base -Base docker image for Utopia libraries + +Multi-arch base Docker images for Utopia PHP libraries, built on Alpine with Swoole and a curated set of PHP extensions. + +## Supported PHP versions + +- PHP 8.3 +- PHP 8.4 +- PHP 8.5 + +Each version is published as a multi-arch image (`linux/amd64`, `linux/arm64`). + +## Tags + +Images are published to [`appwrite/utopia-base`](https://hub.docker.com/r/appwrite/utopia-base) on Docker Hub. + +| Tag | Source | Stability | +| --- | --- | --- | +| `php--` | `release.yml` on a published GitHub release | Stable — pin to this in production | +| `php--` | `publish.yml` on every push to `main` after CI passes | Tracks `main` — useful for testing, garbage-collected after 14 days | + +`` is one of `8.3`, `8.4`, `8.5`. `` is the GitHub release tag (e.g. `0.5.0`). `` is the full 40-char commit SHA. + +## CI/CD + +Four workflows: + +- **`ci.yml`** — runs on PRs, pushes to `main`, and weekly. Builds each `(php × arch)` once, shares the image via artifact, then runs structure tests, [dive](https://github.com/wagoodman/dive) efficiency checks, and [Trivy](https://github.com/aquasecurity/trivy) CVE scans against the artifact. SARIF results upload to the Security tab. +- **`publish.yml`** — `workflow_run`-triggered after CI succeeds on `main`. Pushes per-arch tags and assembles the multi-arch manifest under `php--`. No rebuild — uses CI's artifacts. +- **`release.yml`** — triggered by `release: published`. Promotes the existing `php--` manifest to `php--` via `docker buildx imagetools create`. No rebuild. +- **`cleanup.yml`** — daily cron. Deletes `php--[-arch]` tags older than 14 days from Docker Hub. Release tags don't match the regex and are never touched. + +All publishing workflows share a `build-` concurrency group so a release queues behind in-flight CI on the same commit. + +## Local development + +```sh +docker build -t utopia-base-test php-8.4/. +container-structure-test test --image utopia-base-test --config php-8.4/tests.yaml +``` From 05b337be75e092c47ef60f09c3cd6665cedc7338 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:20:09 +0100 Subject: [PATCH 16/18] ci: verify container-structure-test binary against upstream checksums Download checksums.txt from the same release and run sha256sum -c against the per-arch asset. Catches a corrupted download or a tampered mirror. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 177e95c..05251f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,9 +86,13 @@ jobs: env: CST_VERSION: v1.22.1 run: | - curl -fsSL -o cst "https://github.com/GoogleContainerTools/container-structure-test/releases/download/${CST_VERSION}/container-structure-test-linux-${{ matrix.arch }}" - chmod +x cst - sudo mv cst /usr/local/bin/container-structure-test + base="https://github.com/GoogleContainerTools/container-structure-test/releases/download/${CST_VERSION}" + asset="container-structure-test-linux-${{ matrix.arch }}" + curl -fsSL -o "$asset" "$base/$asset" + curl -fsSL -o checksums.txt "$base/checksums.txt" + grep " $asset\$" checksums.txt | sha256sum -c - + chmod +x "$asset" + sudo mv "$asset" /usr/local/bin/container-structure-test - name: Run container structure tests run: container-structure-test test --image utopia-base-test --config php-${{ matrix.php-versions }}/tests.yaml From 29d729a383db9d395db9ea76d0a1c9d764087a40 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:28:50 +0100 Subject: [PATCH 17/18] ci: replace stale dive action with direct install; surface report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The yuichielectric/dive-action wraps dive v0.9, which doesn't yet support --ci-config — invocation failed with 'unknown flag: --ci-config'. Install dive v0.13.1 directly with checksum verification. Capture the dive report to the GitHub Actions step summary so the layer breakdown and efficiency metrics are visible on every run instead of buried in raw step logs. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05251f8..6bc75f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,11 +117,36 @@ jobs: - name: Load image run: docker image load -i image.tar - - name: Dive - uses: yuichielectric/dive-action@c2bf577bd2ed379a30c45597cf304f9f269dbdfe # 0.0.4 - with: - config-file: ${{ github.workspace }}/.dive-ci.yml - image: utopia-base-test + - name: Install dive + env: + DIVE_VERSION: v0.13.1 + run: | + ver="${DIVE_VERSION#v}" + base="https://github.com/wagoodman/dive/releases/download/${DIVE_VERSION}" + asset="dive_${ver}_linux_amd64.tar.gz" + curl -fsSL -o "$asset" "$base/$asset" + curl -fsSL -o checksums.txt "$base/dive_${ver}_checksums.txt" + grep " $asset\$" checksums.txt | sha256sum -c - + tar -xzf "$asset" dive + chmod +x dive + sudo mv dive /usr/local/bin/dive + + - name: Run dive + id: dive + run: | + set -o pipefail + CI=true dive --ci-config .dive-ci utopia-base-test | tee dive.log + + - name: Publish dive report to step summary + if: always() && steps.dive.outcome != 'skipped' + run: | + { + echo "## Dive — php-${{ matrix.php-versions }}" + echo + echo '```' + cat dive.log + echo '```' + } >> "$GITHUB_STEP_SUMMARY" trivy: name: Trivy (${{ matrix.php-versions }}) From 8c898cb246de292e183d041e15061579cdc28ca5 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Tue, 12 May 2026 17:32:52 +0100 Subject: [PATCH 18/18] ci: point --ci-config at the real filename dive's --ci-config is passed verbatim to viper.SetConfigFile, so it needs the literal path including extension. The committed file is .dive-ci.yml. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bc75f5..f127da4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,7 +135,7 @@ jobs: id: dive run: | set -o pipefail - CI=true dive --ci-config .dive-ci utopia-base-test | tee dive.log + CI=true dive --ci-config .dive-ci.yml utopia-base-test | tee dive.log - name: Publish dive report to step summary if: always() && steps.dive.outcome != 'skipped'