From 8096c110b10af243f9efcb18537b8789098a82db Mon Sep 17 00:00:00 2001 From: nfebe Date: Mon, 16 Mar 2026 02:22:48 +0100 Subject: [PATCH] feat: Add package variant for Laravel package development Lightweight PHP CLI image (package-8.2, package-8.4) for developing and testing Laravel packages with Orchestra Testbench. Includes shared package.mk with standardized make targets. CI workflow updated to build and publish both app and package variants. Signed-off-by: nfebe --- .github/workflows/publish.yml | 127 +++++++++++++++++++++++++++++++--- README.md | 104 +++++++++++++++++++++++++--- package/Dockerfile | 29 ++++++++ package/package.mk | 67 ++++++++++++++++++ 4 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 package/Dockerfile create mode 100644 package/package.mk diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fbcee7e..a7f2af0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,7 @@ env: IMAGE_NAME: whilesmartphp/laravel-dev jobs: - build: + build-app: runs-on: ${{ matrix.runner }} permissions: contents: read @@ -50,30 +50,88 @@ jobs: platforms: ${{ matrix.platform }} build-args: | PHP_VERSION=${{ matrix.php_version }} - cache-from: type=gha,scope=${{ matrix.php_version }}-${{ matrix.platform }} - cache-to: type=gha,mode=max,scope=${{ matrix.php_version }}-${{ matrix.platform }} + cache-from: type=gha,scope=app-${{ matrix.php_version }}-${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=app-${{ matrix.php_version }}-${{ matrix.platform }} outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} - name: Export digest if: github.event_name != 'pull_request' run: | - mkdir -p /tmp/digests/${{ matrix.php_version }} + mkdir -p /tmp/digests/app-${{ matrix.php_version }} digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${{ matrix.php_version }}/${digest#sha256:}" + touch "/tmp/digests/app-${{ matrix.php_version }}/${digest#sha256:}" - name: Upload digest if: github.event_name != 'pull_request' uses: actions/upload-artifact@v4 with: - name: digest-${{ matrix.php_version }}-${{ matrix.runner }} - path: /tmp/digests/${{ matrix.php_version }} + name: digest-app-${{ matrix.php_version }}-${{ matrix.runner }} + path: /tmp/digests/app-${{ matrix.php_version }} if-no-files-found: error retention-days: 1 - merge: + build-package: + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + + strategy: + matrix: + php_version: ['8.2', '8.4'] + platform: ['linux/amd64', 'linux/arm64'] + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: ./package + platforms: ${{ matrix.platform }} + build-args: | + PHP_VERSION=${{ matrix.php_version }} + cache-from: type=gha,scope=package-${{ matrix.php_version }}-${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=package-${{ matrix.php_version }}-${{ matrix.platform }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} + + - name: Export digest + if: github.event_name != 'pull_request' + run: | + mkdir -p /tmp/digests/package-${{ matrix.php_version }} + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/package-${{ matrix.php_version }}/${digest#sha256:}" + + - name: Upload digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: digest-package-${{ matrix.php_version }}-${{ matrix.runner }} + path: /tmp/digests/package-${{ matrix.php_version }} + if-no-files-found: error + retention-days: 1 + + merge-app: runs-on: ubuntu-latest if: github.event_name != 'pull_request' - needs: build + needs: build-app permissions: contents: read packages: write @@ -93,7 +151,7 @@ jobs: - name: Download digests uses: actions/download-artifact@v4 with: - pattern: digest-${{ matrix.php_version }}-* + pattern: digest-app-${{ matrix.php_version }}-* merge-multiple: true path: /tmp/digests @@ -118,3 +176,52 @@ jobs: - name: Inspect image run: | docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ matrix.php_version }} + + merge-package: + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + needs: build-package + permissions: + contents: read + packages: write + + strategy: + matrix: + php_version: ['8.2', '8.4'] + + steps: + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Download digests + uses: actions/download-artifact@v4 + with: + pattern: digest-package-${{ matrix.php_version }}-* + merge-multiple: true + path: /tmp/digests + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=package-${{ matrix.php_version }} + type=raw,value=package-${{ matrix.php_version }}-{{sha}} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:package-${{ matrix.php_version }} diff --git a/README.md b/README.md index 10b42a9..039244f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,27 @@ # Laravel Dev Docker Image -Centralized Laravel development Docker image with nginx, PHP-FPM, and supervisor. +Centralized Laravel development Docker images with two variants: -## Image +- **App** — Full Laravel application image with nginx, PHP-FPM, and supervisor +- **Package** — Lightweight CLI image for package development and testing + +## Images ``` +# App variant (nginx + PHP-FPM + supervisor) ghcr.io/whilesmartphp/laravel-dev:8.4 ghcr.io/whilesmartphp/laravel-dev:8.2 + +# Package variant (PHP CLI only) +ghcr.io/whilesmartphp/laravel-dev:package-8.4 +ghcr.io/whilesmartphp/laravel-dev:package-8.2 ``` -## What's Included +## App Variant + +Full-stack Laravel development environment. + +### What's Included - **PHP-FPM** (8.2 or 8.4) - **Nginx** with Laravel-optimized config @@ -18,8 +30,6 @@ ghcr.io/whilesmartphp/laravel-dev:8.2 - **PHP Extensions:** pdo_mysql, pdo_pgsql, mbstring, exif, pcntl, bcmath, gd, zip - **Shared `laravel.mk`** at `/usr/local/share/laravel.mk` -## Usage - ### docker-compose.yml ```yaml @@ -39,11 +49,9 @@ services: - '.:/var/www/html' ``` -The `HOST_UID`/`HOST_GID` build args map container permissions to your host user, avoiding file ownership issues. - ### Makefile -Copy `laravel.mk` into your project. It works standalone or with a thin project `Makefile`: +Copy `laravel.mk` into your project or include it: ```makefile include laravel.mk @@ -77,8 +85,6 @@ TEST_CMD = php artisan test --coverage LINT_CMD = composer phpcs:test && composer phpmd && composer pint:test include laravel.mk - -# Add project-specific targets below ``` | Variable | Default | Description | @@ -88,7 +94,7 @@ include laravel.mk | `TEST_CMD` | `php artisan test` | Command used by `make test` | | `LINT_CMD` | `composer pint:test` | Command used by `make lint` | -### Available Targets +### App Targets | Target | Description | |--------|-------------| @@ -116,6 +122,82 @@ include laravel.mk | `clean` | Remove everything (containers, images, volumes) | | `help` | Show available targets | +--- + +## Package Variant + +Lightweight image for developing and testing Laravel packages with Orchestra Testbench. + +### What's Included + +- **PHP CLI** (8.2 or 8.4) +- **Composer** (latest) +- **PHP Extensions:** pdo_mysql, pdo_pgsql, mbstring, zip +- **Shared `package.mk`** at `/usr/local/share/package.mk` + +### docker-compose.yml + +```yaml +name: my-package +services: + app: + image: ghcr.io/whilesmartphp/laravel-dev:package-8.4 + volumes: + - .:/app + working_dir: /app + command: tail -f /dev/null + environment: + - APP_ENV=testing +``` + +### Makefile + +Include `package.mk` from the image: + +```makefile +include package.mk +``` + +Or override defaults before the include: + +```makefile +TEST_CMD = ./vendor/bin/testbench package:test --parallel +LINT_FIX_CMD = ./vendor/bin/pint + +include package.mk +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `TEST_CMD` | `./vendor/bin/testbench package:test` | Command used by `make test` | +| `LINT_CMD` | `composer pint:test` | Command used by `make lint` | +| `LINT_FIX_CMD` | `composer pint` | Command used by `make lint-fix` | + +### Package Targets + +| Target | Description | +|--------|-------------| +| `up` | Start containers | +| `down` | Stop and remove containers | +| `restart` | Restart containers | +| `logs` | Show container logs | +| `bash` | Open a shell in the container | +| `install` | Install dependencies (starts containers first) | +| `update` | Update dependencies | +| `test` | Run tests | +| `lint` | Check code formatting | +| `lint-fix` | Fix code formatting | +| `build` | Build workbench | +| `serve` | Start development server | +| `autoload` | Dump composer autoloader | +| `fresh` | Fresh start (down + up + install) | +| `setup` | Complete setup (fresh + test) | +| `check` | Run all checks (lint + test) | +| `clean` | Remove everything (containers, images, volumes) | +| `help` | Show available targets | + +--- + ## Production For production, use [`ghcr.io/whilesmartphp/frankenphp`](https://github.com/whilesmartphp/frakenphp) instead. diff --git a/package/Dockerfile b/package/Dockerfile new file mode 100644 index 0000000..84c7482 --- /dev/null +++ b/package/Dockerfile @@ -0,0 +1,29 @@ +ARG PHP_VERSION=8.4 +FROM php:${PHP_VERSION}-cli + +ARG HOST_UID=1000 +ARG HOST_GID=1000 + +RUN apt-get update && apt-get install -y \ + git \ + libzip-dev \ + libonig-dev \ + libpq-dev \ + unzip \ + && docker-php-ext-install pdo_mysql pdo_pgsql mbstring zip \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +RUN set -eux; \ + groupadd -o -g ${HOST_GID} devuser; \ + useradd -o -u ${HOST_UID} -g devuser -m -s /bin/bash devuser; \ + mkdir -p /home/devuser/.composer/cache; \ + chown -R devuser:devuser /home/devuser; \ + git config --global --add safe.directory '*' + +COPY package.mk /usr/local/share/package.mk + +WORKDIR /app + +USER devuser diff --git a/package/package.mk b/package/package.mk new file mode 100644 index 0000000..4c31e47 --- /dev/null +++ b/package/package.mk @@ -0,0 +1,67 @@ +.DEFAULT_GOAL := help + +HOST_UID := $(or $(shell id -u 2>/dev/null),1000) +HOST_GID := $(or $(shell id -g 2>/dev/null),1000) + +DC = HOST_UID=$(HOST_UID) HOST_GID=$(HOST_GID) docker compose +EXEC = $(DC) exec app + +TEST_CMD ?= ./vendor/bin/testbench package:test +LINT_CMD ?= composer pint:test +LINT_FIX_CMD ?= composer pint + +up: ## Start containers + $(DC) up -d + +down: ## Stop and remove containers + $(DC) down + +restart: ## Restart containers + $(DC) restart + +logs: ## Show container logs + $(DC) logs -f + +bash: ## Open a shell in the container + $(EXEC) bash + +install: up ## Install dependencies + $(EXEC) composer install + +update: ## Update dependencies + $(EXEC) composer update + +test: ## Run tests + $(EXEC) $(TEST_CMD) + +lint: ## Check code formatting + $(EXEC) $(LINT_CMD) + +lint-fix: ## Fix code formatting + $(EXEC) $(LINT_FIX_CMD) + +build: ## Build workbench + $(EXEC) composer build + +serve: ## Start development server + $(EXEC) composer serve + +autoload: ## Dump composer autoloader + $(EXEC) composer dump-autoload + +fresh: down up install ## Fresh start: down, up, install + +setup: fresh test ## Complete setup with tests + @echo "Setup complete!" + +check: lint test ## Run all checks (formatting + tests) + @echo "All checks passed!" + +clean: ## Remove containers, images, and volumes + $(DC) down --rmi all -v + +help: ## Show this help message + @echo 'Usage: make [target]' + @echo '' + @echo 'Targets:' + @egrep '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sed 's/^[^:]*://' | sort -u | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'