-
-
Notifications
You must be signed in to change notification settings - Fork 1
437 lines (375 loc) · 17.5 KB
/
docker-image.v2.yml
File metadata and controls
437 lines (375 loc) · 17.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
name: PHP Docker Build Pipeline
on:
workflow_dispatch:
# schedule:
# - cron: '31 16 * * 3'
# push:
# branches:
# - main
# paths:
# - 'Dockerfile'
# - '.github/workflows/docker-image.yml'
# - 's6-overlay/**'
# - '!README.md'
# - '!*.md'
# - '!docs/**'
# pull_request:
# paths:
# - 'Dockerfile'
# - '.github/workflows/docker-image.yml'
# - 's6-overlay/**'
# - '!README.md'
# - '!*.md'
# - '!docs/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
matrix-prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Generate build matrix
id: set-matrix
run: |
# Using the heredoc delimiter syntax for multiline GitHub Actions outputs
echo "matrix<<EOF" >> $GITHUB_OUTPUT
echo '{
"php-version": ["8.1", "8.2", "8.3", "8.4"],
"php-type": ["cli", "fpm", "apache"],
"php-base-os": ["trixie", "alpine"],
"exclude": [
{"php-type": "apache", "php-base-os": "alpine"}
]
}' >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
build-test:
needs: matrix-prep
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix-prep.outputs.matrix)}}
outputs:
image-tags: ${{ steps.save-tags.outputs.tags }}
steps:
- name: Record start time
id: start_time
run: |
echo "time=$(date +%s)" >> $GITHUB_OUTPUT
- name: Checkout source
uses: actions/checkout@v4
- name: Setup Docker environment
uses: docker/setup-qemu-action@v3
with:
platforms: amd64,arm64,arm
- name: Setup Docker Buildx with Resource Limits
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: |
--debug
--allow-insecure-entitlement security.insecure
--allow-insecure-entitlement network.host
buildkitd-config-inline: |
[worker.oci]
max-parallelism = 4
[worker.containerd]
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7"]
# [worker.oci.gcpolicy]
# keepBytes = 10000000000
# keepDuration = 604800
- name: Login to DockerHub registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log into ghcr.io registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Quay.io registry
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
- name: Set image details
id: image-details
run: |
# Generate consistent image tags/names across the workflow
echo "IMAGE_NAME=${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}" >> $GITHUB_ENV
echo "DOCKERHUB_TAG=docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}" >> $GITHUB_ENV
echo "GHCR_TAG=ghcr.io/kingpin/${{ github.event.repository.name }}:${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}" >> $GITHUB_ENV
echo "QUAY_TAG=quay.io/kingpinx1/${{ github.event.repository.name }}:${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}" >> $GITHUB_ENV
- name: Set smart tags
id: smart-tags
run: |
echo "::group::Setting smart version tags"
# Add major.minor tags
MAJOR_VERSION=$(echo "${{ matrix.php-version }}" | cut -d. -f1)
MINOR_VERSION=$(echo "${{ matrix.php-version }}" | cut -d. -f1,2)
# Set tags for each registry
if [[ "${{ matrix.php-version }}" == "8.4" && "${{ matrix.php-type }}" == "cli" && "${{ matrix.php-base-os }}" == "alpine" ]]; then
echo "DOCKERHUB_ADDITIONAL_TAGS=docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:latest,docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:${MAJOR_VERSION},docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:${MINOR_VERSION}" >> $GITHUB_ENV
echo "GHCR_ADDITIONAL_TAGS=ghcr.io/kingpin/${{ github.event.repository.name }}:latest,ghcr.io/kingpin/${{ github.event.repository.name }}:${MAJOR_VERSION},ghcr.io/kingpin/${{ github.event.repository.name }}:${MINOR_VERSION}" >> $GITHUB_ENV
echo "QUAY_ADDITIONAL_TAGS=quay.io/kingpinx1/${{ github.event.repository.name }}:latest,quay.io/kingpinx1/${{ github.event.repository.name }}:${MAJOR_VERSION},quay.io/kingpinx1/${{ github.event.repository.name }}:${MINOR_VERSION}" >> $GITHUB_ENV
else
# Regular version tags
ADDITIONAL_TAGS="${MAJOR_VERSION}-${{ matrix.php-type }}-${{ matrix.php-base-os }},${MINOR_VERSION}-${{ matrix.php-type }}-${{ matrix.php-base-os }}"
# Add bookworm compatibility tags for trixie images (backward compatibility)
# Also add bullseye tags for transition compatibility
if [[ "${{ matrix.php-base-os }}" == "trixie" ]]; then
ADDITIONAL_TAGS="${ADDITIONAL_TAGS},${{ matrix.php-version }}-${{ matrix.php-type }}-bookworm,${MAJOR_VERSION}-${{ matrix.php-type }}-bookworm,${MINOR_VERSION}-${{ matrix.php-type }}-bookworm"
ADDITIONAL_TAGS="${ADDITIONAL_TAGS},${{ matrix.php-version }}-${{ matrix.php-type }}-bullseye,${MAJOR_VERSION}-${{ matrix.php-type }}-bullseye,${MINOR_VERSION}-${{ matrix.php-type }}-bullseye"
fi
echo "DOCKERHUB_ADDITIONAL_TAGS=docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}:${ADDITIONAL_TAGS//,/,docker.io\/${{ secrets.DOCKERHUB_USERNAME }}\/${{ github.event.repository.name }}:}" >> $GITHUB_ENV
echo "GHCR_ADDITIONAL_TAGS=ghcr.io/kingpin/${{ github.event.repository.name }}:${ADDITIONAL_TAGS//,/,ghcr.io\/kingpin\/${{ github.event.repository.name }}:}" >> $GITHUB_ENV
echo "QUAY_ADDITIONAL_TAGS=quay.io/kingpinx1/${{ github.event.repository.name }}:${ADDITIONAL_TAGS//,/,quay.io\/kingpinx1\/${{ github.event.repository.name }}:}" >> $GITHUB_ENV
fi
echo "::endgroup::"
- name: Set lowercase owner name
run: |
echo "OWNER_LC=${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_ENV
- name: Build test image
uses: docker/build-push-action@v6
with:
context: .
load: true
platforms: linux/amd64
cache-from: |
type=gha,scope=test-${{ env.IMAGE_NAME }}
type=registry,ref=ttl.sh/${{ env.OWNER_LC }}-test-cache:${{ env.IMAGE_NAME }}
cache-to: |
type=gha,mode=max,scope=test-${{ env.IMAGE_NAME }}
build-args: |
VERSION=${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}
PHPVERSION=${{ matrix.php-version }}
BASEOS=${{ matrix.php-base-os }}
tags: |
test-${{ env.IMAGE_NAME }}
- name: Run validation tests
run: |
echo "::group::Testing image ${{ env.IMAGE_NAME }}"
# Version validation
echo "Verifying PHP version..."
docker run --rm test-${{ env.IMAGE_NAME }} php -v | grep -q "${{ matrix.php-version }}" || { echo "::error::PHP version mismatch"; exit 1; }
# Extension validation
echo "Checking extensions..."
docker run --rm test-${{ env.IMAGE_NAME }} php -m > extensions.txt
# Check for critical extensions
for ext in imagick gd json mysqli zip; do
grep -q "$ext" extensions.txt || { echo "::error::Missing extension: $ext"; exit 1; }
done
# Additional health checks
echo "Running health checks..."
docker run --rm test-${{ env.IMAGE_NAME }} php -r "echo 'PHP is working correctly\n';"
echo "::endgroup::"
- name: Run comprehensive tests
run: |
echo "::group::Advanced Testing Suite"
# Test PHP modules functionality
docker run --rm test-${{ env.IMAGE_NAME }} php -r "
// Test GD functionality
\$img = imagecreatetruecolor(100, 100);
echo imagecolorallocate(\$img, 255, 0, 0) !== false ? 'GD working ✅\n' : 'GD failed ❌\n';
imagedestroy(\$img);
// Test MySQL connectivity
if (function_exists('mysqli_connect')) {
echo 'MySQLi extension loaded ✅\n';
}
// Test Zip functionality
if (class_exists('ZipArchive')) {
echo 'Zip extension loaded ✅\n';
}
"
echo "::endgroup::"
- name: Check for changes
id: check-changes
run: |
echo "::group::Checking for image changes"
# Try to pull the previous image
if docker pull ${{ env.DOCKERHUB_TAG }} &>/dev/null; then
# Compare image digest with current Dockerfile hash
PREVIOUS_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ${{ env.DOCKERHUB_TAG }} | cut -d'@' -f2)
CURRENT_HASH=$(sha256sum Dockerfile | cut -d' ' -f1)
echo "Previous digest: $PREVIOUS_DIGEST"
echo "Current hash: $CURRENT_HASH"
# Skip if unchanged and not manually triggered
if [[ "$PREVIOUS_DIGEST" == "$CURRENT_HASH" && "${{ github.event_name }}" != "workflow_dispatch" ]]; then
echo "No changes detected, skipping build"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Changes detected, will build"
echo "skip=false" >> $GITHUB_OUTPUT
fi
else
echo "No previous image found, will build"
echo "skip=false" >> $GITHUB_OUTPUT
fi
echo "::endgroup::"
- name: Build and push Docker image
if: steps.check-changes.outputs.skip != 'true'
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
cache-from: |
type=gha,scope=${{ env.IMAGE_NAME }}
type=registry,ref=ttl.sh/${{ env.OWNER_LC }}-cache:${{ env.IMAGE_NAME }}
cache-to: |
type=gha,mode=max,scope=${{ env.IMAGE_NAME }}
type=registry,ref=ttl.sh/${{ env.OWNER_LC }}-cache:${{ env.IMAGE_NAME }},mode=max
build-args: |
VERSION=${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}
PHPVERSION=${{ matrix.php-version }}
BASEOS=${{ matrix.php-base-os }}
tags: |
${{ env.DOCKERHUB_TAG }}
${{ env.GHCR_TAG }}
${{ env.QUAY_TAG }}
${{ env.DOCKERHUB_ADDITIONAL_TAGS }}
${{ env.GHCR_ADDITIONAL_TAGS }}
${{ env.QUAY_ADDITIONAL_TAGS }}
# PR preview image build disabled because workflow now runs only via workflow_dispatch
# - name: Build PR preview image
# if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
# uses: docker/build-push-action@v6
# with:
# context: .
# push: true
# platforms: linux/amd64
# build-args: |
# VERSION=${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}
# PHPVERSION=${{ matrix.php-version }}
# BASEOS=${{ matrix.php-base-os }}
# tags: |
# ghcr.io/kingpin/${{ github.event.repository.name }}:pr-${{ github.event.pull_request.number }}-${{ env.IMAGE_NAME }}
- name: Create and push manifest
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && steps.check-changes.outputs.skip != 'true' }}
run: |
echo "::group::Creating multi-architecture manifests"
# Multi-architecture images are automatically created by docker/build-push-action
# This step is only needed for the additional tag manifests
if [[ -n "$DOCKERHUB_ADDITIONAL_TAGS" ]]; then
for TAG in $(echo $DOCKERHUB_ADDITIONAL_TAGS | tr ',' ' '); do
docker buildx imagetools create -t $TAG $DOCKERHUB_TAG
done
fi
if [[ -n "$GHCR_ADDITIONAL_TAGS" ]]; then
for TAG in $(echo $GHCR_ADDITIONAL_TAGS | tr ',' ' '); do
docker buildx imagetools create -t $TAG $GHCR_TAG
done
fi
if [[ -n "$QUAY_ADDITIONAL_TAGS" ]]; then
for TAG in $(echo $QUAY_ADDITIONAL_TAGS | tr ',' ' '); do
docker buildx imagetools create -t $TAG $QUAY_TAG
done
fi
echo "::endgroup::"
- name: Save image tags for scan job
id: save-tags
if: steps.check-changes.outputs.skip != 'true' && github.ref == 'refs/heads/main'
run: |
# Save image tags for scan job in JSON format
echo "tags={\"dockerhub\":\"${{ env.DOCKERHUB_TAG }}\",\"ghcr\":\"${{ env.GHCR_TAG }}\",\"quay\":\"${{ env.QUAY_TAG }}\",\"image_name\":\"${{ env.IMAGE_NAME }}\"}" >> $GITHUB_OUTPUT
- name: Timing Report
if: always()
run: |
echo "::group::Build Performance Metrics"
# Calculate and store build times
END_TIME=$(date +%s)
BUILD_TIME=$((END_TIME - ${{ steps.start_time.outputs.time }}))
echo "Total build time: $(($BUILD_TIME / 60)) minutes and $(($BUILD_TIME % 60)) seconds"
# Store metrics for tracking
echo "${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }},$BUILD_TIME" >> build-metrics.csv
echo "::endgroup::"
- name: Upload build metrics
if: always()
uses: actions/upload-artifact@v4
with:
name: build-metrics-${{ matrix.php-version }}-${{ matrix.php-type }}-${{ matrix.php-base-os }}-${{ github.run_id }}
path: build-metrics.csv
retention-days: 90
scan:
needs: build-test
if: ${{ needs.build-test.outputs.image-tags != '' && github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- registry: dockerhub
name: DockerHub
- registry: ghcr
name: GitHub Container Registry
steps:
- name: Parse image details
id: image-details
run: |
# Extract the image tag from the JSON output
IMAGE_TAGS='${{ needs.build-test.outputs.image-tags }}'
REGISTRY="${{ matrix.registry }}"
# Parse JSON to get the specific registry tag
IMAGE_TAG=$(echo $IMAGE_TAGS | jq -r ".$REGISTRY")
IMAGE_NAME=$(echo $IMAGE_TAGS | jq -r ".image_name")
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: image
image-ref: ${{ env.IMAGE_TAG }}
format: 'sarif'
severity: 'CRITICAL,HIGH'
hide-progress: false
output: 'trivy-results-${{ matrix.registry }}-${{ env.IMAGE_NAME }}.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results-${{ matrix.registry }}-${{ env.IMAGE_NAME }}.sarif'
category: ${{ matrix.name }}
publish:
needs: [build-test, scan]
if: ${{ needs.build-test.outputs.image-tags != '' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Update README
run: |
echo "::group::Updating image metadata"
# Update project documentation with latest build info
DATE=$(date '+%Y-%m-%d')
echo "Last updated: $DATE" >> README.md
echo "Successfully built and published images for PHP Docker project" >> README.md
echo "::endgroup::"
- name: Notify on completion
if: always()
run: |
echo "::group::Build status notification"
# Send notification about build status
if [ "${{ job.status }}" == "success" ]; then
echo "Build completed successfully"
else
echo "Build failed"
fi
echo "::endgroup::"
combine-metrics:
needs: build-test
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: build-metrics-*
merge-multiple: true
path: all-metrics
- name: Combine metrics files
run: |
cat all-metrics/*.csv > combined-metrics.csv
- uses: actions/upload-artifact@v4
with:
name: combined-build-metrics
path: combined-metrics.csv