Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 51 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ on:
- 'src/entrypoint.sh'
- 'src/.env'
- 'src/.env.example'
- 'support/makefile/**'
- '.hadolint.yaml'
- '.github/workflows/ci.yml'
pull_request:
branches:
Expand All @@ -22,6 +24,8 @@ on:
- 'src/entrypoint.sh'
- 'src/.env'
- 'src/.env.example'
- 'support/makefile/**'
- '.hadolint.yaml'
- '.github/workflows/ci.yml'

concurrency:
Expand All @@ -42,6 +46,20 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: gh api -X PUT repos/${{ github.repository }}/actions/workflows/ci.yml/enable

hadolint:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out the repository
uses: actions/checkout@v6

- name: Lint Dockerfile (hadolint)
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: src/Dockerfile
config: .hadolint.yaml

build-test-and-push:
runs-on: ubuntu-latest
strategy:
Expand All @@ -51,7 +69,6 @@ jobs:

permissions:
contents: read
packages: write
id-token: write
attestations: write

Expand All @@ -65,13 +82,6 @@ jobs:
username: ${{ secrets.DOCKER_USER_NAME }}
password: ${{ secrets.DOCKER_PAT }}

- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
Expand All @@ -89,6 +99,39 @@ jobs:
run: |
PHP_VERSION=${{ matrix.php_version }} make CACHE_BACKEND=gha test-all

# Push-Guard: build on PR (via test-all above), publish only on merge/schedule/dispatch.
- name: Build & push current version (multi-arch)
if: github.event_name != 'pull_request'
run: |
PHP_VERSION=${{ matrix.php_version }} make CACHE_BACKEND=gha build-remote-version

trivy-report:
# Non-blocking CVE visibility on the freshly published images (base-image CVEs
# are not self-fixable, so this reports instead of failing the build).
if: github.event_name != 'pull_request'
needs: build-test-and-push
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
php_version: ["8.2", "8.3", "8.4"]
fail-fast: false
steps:
- name: Scan image for HIGH/CRITICAL CVEs
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: headgent/phpcli:${{ matrix.php_version }}
format: table
severity: HIGH,CRITICAL
exit-code: '0'
output: trivy-${{ matrix.php_version }}.txt

- name: Publish report to job summary
run: |
{
echo "## Trivy CVE report — phpcli:${{ matrix.php_version }} (HIGH/CRITICAL)";
echo '```';
cat trivy-${{ matrix.php_version }}.txt;
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
15 changes: 15 additions & 0 deletions .hadolint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# hadolint configuration for the phpcli base image.
# Every ignore below is a deliberate base-image design decision, not an oversight.
ignored:
# DL3018 — "Pin versions in apk add". The apk packages are Alpine system libraries
# that track the pinned ALPINE_VERSION build-arg. Pinning each library version
# individually would be brittle (versions change with every Alpine point release)
# and high-maintenance for a base image whose job is to stay current with patches.
- DL3018
# DL4006 — "Set pipefail before RUN with a pipe". The only pipe is `yes "" | pecl install`;
# the left side (`yes`) cannot meaningfully fail, so pipefail adds no safety here.
- DL4006
# SC2086 — "Double quote to prevent globbing". `apk add --no-cache $PKGS` relies on
# intentional word-splitting of the dynamically assembled package list ($PKGS is built
# up via case statements for optional DB clients). Quoting would break the install.
- SC2086
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,19 @@ Modern PHP applications demand more than basic runtime environments. **Jardis PH
- **linux/arm64** – Apple Silicon (M1/M2/M3), AWS Graviton, Ampere, Raspberry Pi 4+

### Version Tags
- `8.2`, `8.3`, `8.4` – Specific PHP minor versions
- `latest` → Currently tracks `8.4` (highest stable version)

The image uses two tag families that serve different purposes:

| Tag | Example | Mutability | Purpose |
|---|---|---|---|
| `8.2`, `8.3`, `8.4` | `headgent/phpcli:8.3` | **Moving** | Rebuilt every 3 days; always carries the latest Alpine base-image and PECL patches for that PHP minor version. |
| `latest` | `headgent/phpcli:latest` | **Moving** | Tracks the highest stable version (currently `8.4`). |
| `<php>-YYYYMMDD` | `headgent/phpcli:8.3-20260613` | **Immutable** | A frozen snapshot of a specific build date. Never overwritten — use for reproducible builds and rollback to a known image state. |

### Which tag should I use?

- **Default — moving tag (`:8.3` / `:latest`).** Recommended for most consumers: you automatically receive security and dependency patches on every 3-day rebuild without changing your reference.
- **Pinning — date tag (`:8.3-20260613`).** Use when you need a reproducible, never-changing image: incident rollback, audited builds, or reproducing a past CI run. Bump the date deliberately when you want newer patches.

**Published under the `headgent/` Docker Hub namespace:** https://hub.docker.com/r/headgent/phpcli/tags

Expand Down
13 changes: 9 additions & 4 deletions support/makefile/docker.mk
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ PLATFORMS ?= linux/amd64 linux/arm64
PHP_VERSIONS := 8.2 8.3 8.4
LATEST_VERSION := $(word $(words $(PHP_VERSIONS)),$(PHP_VERSIONS))

# Immutable date tag (UTC) added alongside the moving :<php> tags in the push targets only.
# Lets consumers pin a reproducible image (e.g. headgent/phpcli:8.4-20260613) for rollback,
# while :<php>/:latest stay moving for patch hygiene. Override for reproducible re-tags.
IMAGE_DATE ?= $(shell date -u +%Y%m%d)

CACHE_BACKEND ?= auto # auto|none|local|registry|gha
CACHE_REF ?= # e.g. docker.io/yourorg/phpcli:buildcache (only for registry)
CACHE_DIR ?= .buildx-cache # only for local
Expand Down Expand Up @@ -71,9 +76,9 @@ build-remote-all: .buildx-create ## Build all remote php image for all defined P
PLATFORM_CSV="$$(printf '%s' "$(PLATFORMS)" | tr ' ' ',')"; \
for version in $(PHP_VERSIONS); do \
echo ">>> Building $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$$version (multi-arch: $$PLATFORM_CSV) ..."; \
tags="-t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$$version"; \
tags="-t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$$version -t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$$version-$(IMAGE_DATE)"; \
$(call buildx_one,$$PLATFORM_CSV,$$tags,--build-arg PHP_VERSION=$$version --push --provenance=mode=max,src/Dockerfile,./src); \
echo ">>> Pushed: $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$$version"; \
echo ">>> Pushed: $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$$version (+ :$$version-$(IMAGE_DATE))"; \
done; \
echo ">>> Setting latest tag -> $(LATEST_VERSION)"; \
docker buildx imagetools create \
Expand All @@ -85,7 +90,7 @@ build-remote-version: .buildx-create ## Build specific remote php image for PHP
@set -e; \
$(call cache_flags) ; \
PLATFORM_CSV="$$(printf '%s' "$(PLATFORMS)" | tr ' ' ',')"; \
tags="-t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$(PHP_VERSION)"; \
tags="-t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$(PHP_VERSION) -t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$(PHP_VERSION)-$(IMAGE_DATE)"; \
$(call buildx_one,$$PLATFORM_CSV,$$tags,--build-arg PHP_VERSION=$(PHP_VERSION) --push --provenance=mode=max,src/Dockerfile,./src); \
if [ "$(PHP_VERSION)" = "$(LATEST_VERSION)" ]; then \
echo ">>> Setting latest tag -> $(LATEST_VERSION)"; \
Expand All @@ -99,7 +104,7 @@ build-remote-latest: .buildx-create ## Build specific remote latest php image
@set -e; \
$(call cache_flags) ; \
PLATFORM_CSV="$$(printf '%s' "$(PLATFORMS)" | tr ' ' ',')"; \
tags="-t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):latest -t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$(LATEST_VERSION)"; \
tags="-t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):latest -t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$(LATEST_VERSION) -t $(DOCKER_HUB)/$(PHP_IMAGE_NAME):$(LATEST_VERSION)-$(IMAGE_DATE)"; \
$(call buildx_one,$$PLATFORM_CSV,$$tags,--build-arg PHP_VERSION=$(LATEST_VERSION) --push --provenance=mode=max,src/Dockerfile,./src)
.PHONY: build-remote-latest

Expand Down
Loading