Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a14ba3e
ci: build on native arm64 runners, pin actions to SHAs
loks0n May 12, 2026
49a9235
ci: add Trivy CVE scan and Dive image-efficiency check
loks0n May 12, 2026
e5ef28a
fix: drop PHP 8.2 support
loks0n May 12, 2026
336901e
ci: combine structure/dive/trivy into a single ci.yml workflow
loks0n May 12, 2026
4defb2a
ci: rename build-and-push.yml to release.yml
loks0n May 12, 2026
d6f3844
ci: build once, share via artifacts; add concurrency cancel
loks0n May 12, 2026
68d5dcb
ci: use docker/metadata-action; release promotes instead of rebuilds
loks0n May 12, 2026
429632d
ci: release waits for CI run on the same commit before promoting
loks0n May 12, 2026
f2efa47
ci: serialize release behind CI via shared concurrency group
loks0n May 12, 2026
d3266dd
ci: tidy job/step names; install container-structure-test directly
loks0n May 12, 2026
6609fc4
ci: split push/manifest into publish-sha.yml triggered by workflow_run
loks0n May 12, 2026
c84dc26
ci: rename publish-sha.yml to publish.yml
loks0n May 12, 2026
28121cc
ci: drop dead deleted counter; use curl --fail on all HTTP calls
loks0n May 12, 2026
7c4cb0f
ci: pin container-structure-test to v1.22.1
loks0n May 12, 2026
778e71e
ci: download container-structure-test from GitHub releases; update RE…
loks0n May 12, 2026
05b337b
ci: verify container-structure-test binary against upstream checksums
loks0n May 12, 2026
29d729a
ci: replace stale dive action with direct install; surface report
loks0n May 12, 2026
8c898cb
ci: point --ci-config at the real filename
loks0n May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dive-ci.yml
Original file line number Diff line number Diff line change
@@ -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
40 changes: 0 additions & 40 deletions .github/workflows/build-and-push.yml

This file was deleted.

184 changes: 184 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '43 11 * * 6'

concurrency:
# 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
security-events: write
actions: read

env:
REGISTRY: docker.io
IMAGE_NAME: appwrite/utopia-base

jobs:
build:
name: Build (${{ 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
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Build image
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
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: Install container-structure-test
env:
CST_VERSION: v1.22.1
run: |
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

dive:
name: Dive (${{ matrix.php-versions }})
needs: build
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
php-versions: ['8.3', '8.4', '8.5']
steps:
- name: Checkout
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 }}-amd64

- name: Load image
run: docker image load -i image.tar

- 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.yml 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 }})
needs: build
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
php-versions: ['8.3', '8.4', '8.5']
steps:
- name: Checkout
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 }}-amd64

- name: Load image
run: docker image load -i image.tar

- name: Run Trivy
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
with:
format: 'sarif'
image-ref: 'utopia-base-test'
output: 'trivy-results-php-${{ matrix.php-versions }}.sarif'
severity: 'CRITICAL,HIGH'

- 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'
category: 'php-${{ matrix.php-versions }}'

56 changes: 56 additions & 0 deletions .github/workflows/cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Cleanup

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-<sha>[-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:
name: Prune
runs-on: ubuntu-24.04
steps:
- name: Prune stale tags
env:
DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
run: |
set -euo pipefail

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)

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"
while [ -n "$url" ] && [ "$url" != "null" ]; do
page=$(curl -fsS -H "Authorization: JWT $token" "$url")
url=$(echo "$page" | jq -r '.next')

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 -fsS -X DELETE \
-H "Authorization: JWT $token" \
"https://hub.docker.com/v2/repositories/${REPO}/tags/${name}/"
fi
done < <(echo "$page" | jq -r '.results[] | "\(.name)\t\(.tag_last_pushed)"')
done
echo "Done."
91 changes: 91 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Publish

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
Loading
Loading