diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ed5d125..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ca79ca5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1c2150..0e3f69e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,105 +3,85 @@ on: push: branches: - master + workflow_dispatch: + inputs: + legacy: + description: 'Also build legacy images (7.2, 7.3, 7.4, 8.0)' + required: false + default: false + type: boolean + +env: + LATEST_PHP: "8.5" + jobs: docker: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ["8.1", "8.2", "8.3", "8.4", "8.5"] + variant: ["debian", "alpine"] steps: - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 + - uses: actions/checkout@v5 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push 8.1 - uses: docker/build-push-action@v3 + - name: Compute tags + id: meta + run: | + image="${{ vars.BUILDKIT_IMAGE }}" + if [ "${{ matrix.variant }}" = "alpine" ]; then + file=./src/docker/Dockerfile.alpine + tags="${image}:${{ matrix.php }}-alpine" + [ "${{ matrix.php }}" = "${LATEST_PHP}" ] && tags="${tags},${image}:alpine" + else + file=./src/docker/Dockerfile + tags="${image}:${{ matrix.php }}" + [ "${{ matrix.php }}" = "${LATEST_PHP}" ] && tags="${tags},${image}:latest" + fi + echo "file=${file}" >> "$GITHUB_OUTPUT" + echo "tags=${tags}" >> "$GITHUB_OUTPUT" + - name: Build and push ${{ matrix.php }} ${{ matrix.variant }} + uses: docker/build-push-action@v6 with: - file: "./src/docker/Dockerfile" + context: . + file: ${{ steps.meta.outputs.file }} push: true + pull: true platforms: linux/amd64,linux/arm64 build-args: | - phpVersion=8.1 - tags: "${{ vars.BUILDKIT_IMAGE }}:8.1" - - - name: Build and push 8.1 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.1 - tags: "${{ vars.BUILDKIT_IMAGE }}:8.1-alpine" - - - name: Build and push 8.2 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.2 - tags: "${{vars.BUILDKIT_IMAGE}}:8.2" - - - name: Build and push 8.2 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.2 - tags: "${{vars.BUILDKIT_IMAGE}}:8.2-alpine" - - - name: Build and push 8.3 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.3 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.3" - - - name: Build and push 8.3 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.3 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.3-alpine" - - - name: Build and push 8.4 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.4 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.4-alpine" - - - name: Build and push 8.4 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.4 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.4" - "${{vars.BUILDKIT_IMAGE}}:latest" + phpVersion=${{ matrix.php }} + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha,scope=${{ matrix.php }}-${{ matrix.variant }} + cache-to: type=gha,mode=max,scope=${{ matrix.php }}-${{ matrix.variant }} + legacy: + if: ${{ github.event_name == 'workflow_dispatch' && inputs.legacy }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ["7.2", "7.3", "7.4", "8.0"] + steps: + - uses: actions/checkout@v5 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push ${{ matrix.php }} + uses: docker/build-push-action@v6 + with: + context: . + file: ./src/docker/${{ matrix.php }}/Dockerfile + push: true + tags: ${{ vars.BUILDKIT_IMAGE }}:${{ matrix.php }} diff --git a/.github/workflows/manual.yaml b/.github/workflows/manual.yaml deleted file mode 100644 index 2dbd04c..0000000 --- a/.github/workflows/manual.yaml +++ /dev/null @@ -1,138 +0,0 @@ -name: Manual-build -on: - workflow_dispatch: - inputs: - docker_tag: - description: 'Docker tag' - required: true - default: 'latest' - type: string -jobs: - docker: - runs-on: ubuntu-latest - steps: - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push 7.2 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/7.2/Dockerfile" - push: true - tags: "${{vars.BUILDKIT_IMAGE}}:7.2" - - - name: Build and push 7.3 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/7.3/Dockerfile" - push: true - tags: "${{vars.BUILDKIT_IMAGE}}:7.3" - - - name: Build and push 7.4 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/7.4/Dockerfile" - push: true - tags: "${{vars.BUILDKIT_IMAGE}}:7.4" - - - name: Build and push 8.0 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/8.0/Dockerfile" - push: true - tags: "${{vars.BUILDKIT_IMAGE}}:8.0" - - - name: Build and push 8.1 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.1 - tags: "${{ vars.BUILDKIT_IMAGE }}:8.1" - - - name: Build and push 8.1 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.1 - tags: "${{ vars.BUILDKIT_IMAGE }}:8.1-alpine" - - - name: Build and push 8.2 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.2 - tags: "${{vars.BUILDKIT_IMAGE}}:8.2" - - - name: Build and push 8.2 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.2 - tags: "${{vars.BUILDKIT_IMAGE}}:8.2-alpine" - - - name: Build and push 8.3 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.3 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.3" - - - name: Build and push 8.3 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.3 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.3-alpine" - - - name: Build and push 8.4 alpine - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile.alpine" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.4 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.4-alpine" - - - name: Build and push 8.4 - uses: docker/build-push-action@v3 - with: - file: "./src/docker/Dockerfile" - push: true - platforms: linux/amd64,linux/arm64 - build-args: | - phpVersion=8.4 - tags: | - "${{vars.BUILDKIT_IMAGE}}:8.4" - "${{vars.BUILDKIT_IMAGE}}:latest" \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ae41625..480f547 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,53 +1,31 @@ name: Tests on: push: + pull_request: + jobs: integration: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ["8.1", "8.2", "8.3", "8.4", "8.5"] + variant: ["debian", "alpine"] steps: - - uses: actions/checkout@v4 - - name: Build Docker Image - run: | - docker buildx build -f src/docker/Dockerfile --build-arg phpVersion=8.4 -t ${{ vars.BUILDKIT_IMAGE }}:latest --load . - - name: Verify Docker images - run: | - docker images - - name: Create Laravel project - run: | - composer create-project laravel/laravel laravel - - name: Fix Permission - run: chmod -R 777 ./laravel/storage - - name: Run docker-compose - run: - cp ./tests/compose.yaml compose.yaml && - docker compose -f "compose.yaml" up -d - - name: Create script.js for K6 test - run: | - touch script.js && cat > script.js < /dev/null ; do - echo "Waiting for database connection..." - sleep 5 - done - - name: Laravel database migration test - run: | - docker exec php-fpm php artisan migrate - + - uses: actions/checkout@v5 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.variant == 'alpine' && './src/docker/Dockerfile.alpine' || './src/docker/Dockerfile' }} + load: true + build-args: | + phpVersion=${{ matrix.php }} + tags: laravel-php-fpm:test + cache-from: type=gha,scope=${{ matrix.php }}-${{ matrix.variant }} + - name: Run integration tests + env: + IMAGE: laravel-php-fpm:test + run: ./tests/integration.sh diff --git a/.gitignore b/.gitignore index 94d3c9c..c91763a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.DS_Store /.history laravel -docker-compose.yml \ No newline at end of file +docker-compose.yml +.DS_Store +tests/modules.txt diff --git a/Makefile b/Makefile index 038bd4a..35642a6 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,40 @@ IMAGE_NAME?=jkaninda/laravel-php-fpm +PHP_VERSIONS?=8.1 8.2 8.3 8.4 8.5 +LATEST_PHP?=8.5 + .PHONY: all all: build + ##@ Build .PHONY: build -build: build-81 build-82 build-83 build-84 -.PHONY: build-80 -build-80: - docker build --build-arg phpVersion=8.0 -f src/docker/8.0/Dockerfile -t ${IMAGE_NAME}:8.0 . -.PHONY: build-81 -build-81: - docker build --build-arg phpVersion=8.1 -f src/docker/Dockerfile -t ${IMAGE_NAME}:8.1 . -.PHONY: build-82 -build-82: - docker build --build-arg phpVersion=8.2 -f src/docker/Dockerfile -t ${IMAGE_NAME}:8.2 . -.PHONY: build-83 -build-83: - docker build --build-arg phpVersion=8.3 -f src/docker/Dockerfile -t ${IMAGE_NAME}:8.3 . -.PHONY: build-84 -build-84: - docker build --build-arg phpVersion=8.4 -f src/docker/Dockerfile -t ${IMAGE_NAME}:8.4 . -.PHONY: build-81-alpine -build-81-alpine: - docker build --build-arg phpVersion=8.1 -f src/docker/Dockerfile.alpine -t ${IMAGE_NAME}:8.1-alpine . -.PHONY: build-84-alpine -build-84-alpine: - docker build --build-arg phpVersion=8.4 -f src/docker/Dockerfile.alpine -t ${IMAGE_NAME}:8.4-alpine . +build: $(addprefix build-,$(PHP_VERSIONS)) + +.PHONY: build-alpine +build-alpine: $(addsuffix -alpine,$(addprefix build-,$(PHP_VERSIONS))) + +.PHONY: build-latest +build-latest: build-$(LATEST_PHP) build-$(LATEST_PHP)-alpine + docker tag ${IMAGE_NAME}:$(LATEST_PHP) ${IMAGE_NAME}:latest + docker tag ${IMAGE_NAME}:$(LATEST_PHP)-alpine ${IMAGE_NAME}:alpine + +# Legacy images (7.2, 7.3, 7.4, 8.0) +.PHONY: build-7.2 build-7.3 build-7.4 build-8.0 +build-7.2 build-7.3 build-7.4 build-8.0: build-%: + docker build -f src/docker/$*/Dockerfile -t ${IMAGE_NAME}:$* . + +.PHONY: build-%-alpine +build-%-alpine: + docker build --build-arg phpVersion=$* -f src/docker/Dockerfile.alpine -t ${IMAGE_NAME}:$*-alpine . + +.PHONY: build-% +build-%: + docker build --build-arg phpVersion=$* -f src/docker/Dockerfile -t ${IMAGE_NAME}:$* . + +##@ Test +.PHONY: test +test: build-$(LATEST_PHP) + IMAGE=${IMAGE_NAME}:$(LATEST_PHP) ./tests/integration.sh + +.PHONY: test-alpine +test-alpine: build-$(LATEST_PHP)-alpine + IMAGE=${IMAGE_NAME}:$(LATEST_PHP)-alpine ./tests/integration.sh diff --git a/README.md b/README.md index 51e3822..4b91234 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,10 @@ This Docker image comes with **PHP-FPM**, offering a robust foundation for your - **Database**: MySQL and PostgreSQL. - **Caching**: Redis and Memcached. - **Messaging**: Kafka for event-driven architecture. - - **Task Scheduling**: Laravel Scheduler and Cron jobs support. -- **Custom Configuration**: Pre-configured with sensible defaults, allowing seamless customization. + - **Task Scheduling**: Laravel Scheduler and Queue Worker started automatically (Supervisord). +- **Custom Configuration**: Pre-configured with sensible defaults, tunable through environment variables. +- **Debian and Alpine variants**, multi-arch (`linux/amd64`, `linux/arm64`). +- **Production-ready**: healthcheck, graceful shutdown, OPcache, `supervisorctl` support. - **Event Handling**: Support for advanced event-driven processes. - **Optimized for Microservices**: Built with modern PHP microservices in mind. @@ -33,39 +35,48 @@ This image is ideal for developers looking for a streamlined, high-performance s - [Github](https://github.com/jkaninda/laravel-php-fpm) --- -## **Supported PHP Versions** -- 8.4 -- 8.3 -- 8.2 -- 8.1 -- 8.0 -- 7.4 -- 7.2 +## **Supported PHP Versions & Tags** + +| PHP | Debian tag | Alpine tag | +|-----|------------|------------| +| 8.5 | `8.5`, `latest` | `8.5-alpine`, `alpine` | +| 8.4 | `8.4` | `8.4-alpine` | +| 8.3 | `8.3` | `8.3-alpine` | +| 8.2 | `8.2` | `8.2-alpine` | +| 8.1 | `8.1` | `8.1-alpine` | + +Legacy images (no longer rebuilt automatically, PHP is end-of-life): `8.0`, `7.4`, `7.3`, `7.2`. + +> [!NOTE] +> PHP 8.1 is end-of-life upstream. The image is still built, but please plan an upgrade. --- ## **Specifications** ### **PHP Extensions** -- Composer +- Composer 2 - OpenSSL - XML -- PDO (MySQL and PostgreSQL) +- PDO (MySQL, PostgreSQL and SQLite) - Rdkafka - Redis - Mbstring +- Intl +- Exif - PCNTL - ZIP -- GD +- GD (FreeType, JPEG, PNG, WebP) - BCMath - Memcached - Opcache ### **Additional Features** -- Laravel Cron Jobs - Laravel Scheduler +- Laravel Queue Worker - Supervisord -- Node.js and NPM +- Node.js 24 LTS and NPM +- Git, Zip/Unzip, SQLite CLI --- @@ -122,7 +133,7 @@ services: - **Fix Permissions** ```sh - docker compose exec php-fpm chmod -R 777 storage bootstrap/cache + docker compose exec -u root php-fpm chown -R www-data:www-data storage bootstrap/cache ``` - **Run Laravel Migrations** @@ -135,6 +146,82 @@ services: docker exec -it php-fpm bash ``` +- **Run a one-off command** (the default supervisor stack is not started) + ```sh + docker run --rm -v "$PWD:/var/www/html" jkaninda/laravel-php-fpm composer install + ``` + +- **Restart the queue workers after a deployment** + ```sh + docker compose exec php-fpm supervisorctl restart all + ``` + +--- + +## **Environment Variables** + +### Laravel + +| Variable | Default | Description | +|----------|---------|-------------| +| `LARAVEL_SCHEDULER_ENABLED` | `true` | Run `php artisan schedule:run` every minute | +| `LARAVEL_QUEUE_WORKER_ENABLED` | `true` | Run `php artisan queue:work` | +| `LARAVEL_QUEUE_OPTIONS` | `--sleep=3 --tries=3` | Options passed to `queue:work` (e.g. `redis --queue=high,default`) | +| `LARAVEL_PROCS_NUMBER` | `1` | Number of queue worker processes | +| `APP_DIR` | `/var/www/html` | Application directory (where `artisan` is looked up) | + +The scheduler and queue worker are only started when an `artisan` file is found in `APP_DIR`. + +### PHP + +| Variable | Default | +|----------|---------| +| `PHP_MEMORY_LIMIT` | `256M` | +| `PHP_UPLOAD_MAX_FILESIZE` | `30M` | +| `PHP_POST_MAX_SIZE` | `30M` | +| `PHP_MAX_EXECUTION_TIME` | `30` | +| `PHP_DATE_TIMEZONE` | `UTC` | +| `PHP_DISPLAY_ERRORS` | `Off` | + +### OPcache + +| Variable | Default | Description | +|----------|---------|-------------| +| `PHP_OPCACHE_ENABLE` | `1` | | +| `PHP_OPCACHE_ENABLE_CLI` | `0` | | +| `PHP_OPCACHE_MEMORY_CONSUMPTION` | `512` | MB | +| `PHP_OPCACHE_MAX_ACCELERATED_FILES` | `50000` | | +| `PHP_OPCACHE_VALIDATE_TIMESTAMPS` | `0` | Set to `1` in development so code changes are picked up | +| `PHP_OPCACHE_REVALIDATE_FREQ` | `0` | | + +### PHP-FPM + +| Variable | Default | +|----------|---------| +| `PHP_FPM_PM` | `dynamic` | +| `PHP_FPM_PM_MAX_CHILDREN` | `20` | +| `PHP_FPM_PM_START_SERVERS` | `2` | +| `PHP_FPM_PM_MIN_SPARE_SERVERS` | `1` | +| `PHP_FPM_PM_MAX_SPARE_SERVERS` | `3` | +| `PHP_FPM_PM_MAX_REQUESTS` | `500` | + +Container environment variables (e.g. `DB_HOST`, `APP_KEY`) are passed to PHP-FPM workers (`clear_env = no`). + +### Development example + +```yaml +services: + php-fpm: + image: jkaninda/laravel-php-fpm:8.5 + environment: + PHP_OPCACHE_VALIDATE_TIMESTAMPS: 1 + PHP_DISPLAY_ERRORS: "On" + LARAVEL_QUEUE_OPTIONS: "redis --sleep=3 --tries=3" + LARAVEL_PROCS_NUMBER: 2 + volumes: + - ./:/var/www/html +``` + --- ## **Advanced Usage with Nginx** @@ -143,7 +230,6 @@ services: Example of using a custom nginx config: ```yaml -version: '3' services: php-fpm: image: jkaninda/laravel-php-fpm @@ -206,7 +292,7 @@ server { ### **Dockerfile Example** ```Dockerfile -FROM jkaninda/laravel-php-fpm:8.3 +FROM jkaninda/laravel-php-fpm:8.5 # Copy Laravel project files COPY . /var/www/html VOLUME /var/www/html/storage @@ -240,20 +326,32 @@ stdout_logfile=/var/www/html/storage/logs/kafka.log --- ## **Custom PHP Configurations** -Place your custom `php.ini` file at: -``` -/usr/local/etc/php/conf.d/ -``` +Most common settings can be changed with [environment variables](#environment-variables). +For anything else, either: + +- mount or copy your own `.ini` file into `/usr/local/etc/php/conf.d/`, or +- place a `php.ini` file at `conf/php/php.ini` in your project; it is copied into `conf.d` at startup. --- ## **Storage Permissions Fix** -If you encounter permission issues, run: +`www-data` has UID/GID `1000` in both Debian and Alpine variants. If you encounter permission issues, run: ```sh -docker compose exec php-fpm /bin/bash -chown -R www-data:www-data /var/www/html -chmod -R 775 /var/www/html/storage +docker compose exec -u root php-fpm chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache +docker compose exec -u root php-fpm chmod -R 775 /var/www/html/storage +``` + +To match your host user, rebuild with `--build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g)`. + +--- + +## **Building & Testing Locally** + +```sh +make build-8.5 # Debian image +make build-8.5-alpine # Alpine image +make test # Integration test (Laravel + MySQL + Nginx) ``` --- diff --git a/build.sh b/build.sh index 94b1177..3da6ba7 100755 --- a/build.sh +++ b/build.sh @@ -1,17 +1,17 @@ #!/bin/bash -if [ $# -eq 0 ] - then - tag='latest' - else - tag=$1 -fi -if [ $tag != 'latest' ] -then - echo 'Build from from tag' - docker build -f src/docker/${tag}/Dockerfile -t jkaninda/laravel-php-fpm:$tag . -else - echo 'Build latest' - docker build -f src/docker/8.3/Dockerfile -t jkaninda/laravel-php-fpm:$tag . - -fi +# Usage: ./build.sh [php-version] (default: 8.5) +# ./build.sh 8.4-alpine +set -e +tag=${1:-8.5} +version=${tag%-alpine} +if [ -f "src/docker/${version}/Dockerfile" ]; then + echo "Build legacy image ${tag}" + docker build -f "src/docker/${version}/Dockerfile" -t "jkaninda/laravel-php-fpm:${tag}" . +elif [ "${tag}" != "${version}" ]; then + echo "Build ${tag}" + docker build --build-arg phpVersion="${version}" -f src/docker/Dockerfile.alpine -t "jkaninda/laravel-php-fpm:${tag}" . +else + echo "Build ${tag}" + docker build --build-arg phpVersion="${version}" -f src/docker/Dockerfile -t "jkaninda/laravel-php-fpm:${tag}" . +fi diff --git a/example/docker-compose.yml b/example/docker-compose.yml index 5c206a0..f8d7804 100644 --- a/example/docker-compose.yml +++ b/example/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: php-fpm: image: jkaninda/laravel-php-fpm diff --git a/src/docker/7.2/Dockerfile b/src/docker/7.2/Dockerfile index f17c06d..1827561 100644 --- a/src/docker/7.2/Dockerfile +++ b/src/docker/7.2/Dockerfile @@ -2,6 +2,19 @@ FROM php:7.2-fpm ARG WORKDIR=/var/www/html ENV DOCUMENT_ROOT=${WORKDIR} ENV LARAVEL_PROCS_NUMBER=1 +ENV APP_DIR=/var/www/html \ + PHP_DATE_TIMEZONE=UTC \ + PHP_DISPLAY_ERRORS=Off \ + PHP_MEMORY_LIMIT=256M \ + PHP_UPLOAD_MAX_FILESIZE=30M \ + PHP_POST_MAX_SIZE=30M \ + PHP_MAX_EXECUTION_TIME=30 \ + PHP_OPCACHE_ENABLE=1 \ + PHP_OPCACHE_ENABLE_CLI=0 \ + PHP_OPCACHE_MEMORY_CONSUMPTION=512 \ + PHP_OPCACHE_MAX_ACCELERATED_FILES=50000 \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + PHP_OPCACHE_REVALIDATE_FREQ=0 ENV NODE_VERSION=16.x ARG GROUP_ID=1000 ARG USER_ID=1000 @@ -88,7 +101,7 @@ RUN ln -s /usr/local/bin/entrypoint.sh / ENTRYPOINT ["entrypoint.sh"] RUN usermod -u ${USER_ID} ${USER_NAME} -RUN groupmod -g ${USER_ID} ${GROUP_NAME} +RUN groupmod -g ${GROUP_ID} ${GROUP_NAME} RUN chown -R ${USER_NAME}:${GROUP_NAME} /var/www && \ chown -R ${USER_NAME}:${GROUP_NAME} /var/log/ && \ chown -R ${USER_NAME}:${GROUP_NAME} /etc/supervisor/conf.d/ && \ diff --git a/src/docker/7.3/Dockerfile b/src/docker/7.3/Dockerfile index 8e2aab3..13c7e01 100644 --- a/src/docker/7.3/Dockerfile +++ b/src/docker/7.3/Dockerfile @@ -2,6 +2,19 @@ FROM php:7.3-fpm ARG WORKDIR=/var/www/html ENV DOCUMENT_ROOT=${WORKDIR} ENV LARAVEL_PROCS_NUMBER=1 +ENV APP_DIR=/var/www/html \ + PHP_DATE_TIMEZONE=UTC \ + PHP_DISPLAY_ERRORS=Off \ + PHP_MEMORY_LIMIT=256M \ + PHP_UPLOAD_MAX_FILESIZE=30M \ + PHP_POST_MAX_SIZE=30M \ + PHP_MAX_EXECUTION_TIME=30 \ + PHP_OPCACHE_ENABLE=1 \ + PHP_OPCACHE_ENABLE_CLI=0 \ + PHP_OPCACHE_MEMORY_CONSUMPTION=512 \ + PHP_OPCACHE_MAX_ACCELERATED_FILES=50000 \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + PHP_OPCACHE_REVALIDATE_FREQ=0 ENV NODE_VERSION=16.x ARG GROUP_ID=1000 ARG USER_ID=1000 @@ -88,7 +101,7 @@ RUN ln -s /usr/local/bin/entrypoint.sh / ENTRYPOINT ["entrypoint.sh"] RUN usermod -u ${USER_ID} ${USER_NAME} -RUN groupmod -g ${USER_ID} ${GROUP_NAME} +RUN groupmod -g ${GROUP_ID} ${GROUP_NAME} RUN chown -R ${USER_NAME}:${GROUP_NAME} /var/www && \ chown -R ${USER_NAME}:${GROUP_NAME} /var/log/ && \ chown -R ${USER_NAME}:${GROUP_NAME} /etc/supervisor/conf.d/ && \ diff --git a/src/docker/7.4/Dockerfile b/src/docker/7.4/Dockerfile index a6444b1..01812f2 100644 --- a/src/docker/7.4/Dockerfile +++ b/src/docker/7.4/Dockerfile @@ -2,6 +2,19 @@ FROM php:7.4-fpm ARG WORKDIR=/var/www/html ENV DOCUMENT_ROOT=${WORKDIR} ENV LARAVEL_PROCS_NUMBER=1 +ENV APP_DIR=/var/www/html \ + PHP_DATE_TIMEZONE=UTC \ + PHP_DISPLAY_ERRORS=Off \ + PHP_MEMORY_LIMIT=256M \ + PHP_UPLOAD_MAX_FILESIZE=30M \ + PHP_POST_MAX_SIZE=30M \ + PHP_MAX_EXECUTION_TIME=30 \ + PHP_OPCACHE_ENABLE=1 \ + PHP_OPCACHE_ENABLE_CLI=0 \ + PHP_OPCACHE_MEMORY_CONSUMPTION=512 \ + PHP_OPCACHE_MAX_ACCELERATED_FILES=50000 \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + PHP_OPCACHE_REVALIDATE_FREQ=0 ENV NODE_VERSION=16.x ARG GROUP_ID=1000 ARG USER_ID=1000 @@ -89,7 +102,7 @@ RUN ln -s /usr/local/bin/entrypoint.sh / ENTRYPOINT ["entrypoint.sh"] RUN usermod -u ${USER_ID} ${USER_NAME} -RUN groupmod -g ${USER_ID} ${GROUP_NAME} +RUN groupmod -g ${GROUP_ID} ${GROUP_NAME} RUN chown -R ${USER_NAME}:${GROUP_NAME} /var/www && \ chown -R ${USER_NAME}:${GROUP_NAME} /var/log/ && \ chown -R ${USER_NAME}:${GROUP_NAME} /etc/supervisor/conf.d/ && \ diff --git a/src/docker/8.0/Dockerfile b/src/docker/8.0/Dockerfile index e6e4a29..a54b488 100644 --- a/src/docker/8.0/Dockerfile +++ b/src/docker/8.0/Dockerfile @@ -2,6 +2,19 @@ FROM php:8.0-fpm ARG WORKDIR=/var/www/html ENV DOCUMENT_ROOT=${WORKDIR} ENV LARAVEL_PROCS_NUMBER=1 +ENV APP_DIR=/var/www/html \ + PHP_DATE_TIMEZONE=UTC \ + PHP_DISPLAY_ERRORS=Off \ + PHP_MEMORY_LIMIT=256M \ + PHP_UPLOAD_MAX_FILESIZE=30M \ + PHP_POST_MAX_SIZE=30M \ + PHP_MAX_EXECUTION_TIME=30 \ + PHP_OPCACHE_ENABLE=1 \ + PHP_OPCACHE_ENABLE_CLI=0 \ + PHP_OPCACHE_MEMORY_CONSUMPTION=512 \ + PHP_OPCACHE_MAX_ACCELERATED_FILES=50000 \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + PHP_OPCACHE_REVALIDATE_FREQ=0 ENV NODE_VERSION=16.x ARG GROUP_ID=1000 ARG USER_ID=1000 @@ -89,7 +102,7 @@ RUN ln -s /usr/local/bin/entrypoint.sh / ENTRYPOINT ["entrypoint.sh"] RUN usermod -u ${USER_ID} ${USER_NAME} -RUN groupmod -g ${USER_ID} ${GROUP_NAME} +RUN groupmod -g ${GROUP_ID} ${GROUP_NAME} RUN chown -R ${USER_NAME}:${GROUP_NAME} /var/www && \ chown -R ${USER_NAME}:${GROUP_NAME} /var/log/ && \ chown -R ${USER_NAME}:${GROUP_NAME} /etc/supervisor/conf.d/ && \ diff --git a/src/docker/Dockerfile b/src/docker/Dockerfile index 94daed9..3e8c8ba 100644 --- a/src/docker/Dockerfile +++ b/src/docker/Dockerfile @@ -1,104 +1,103 @@ -ARG phpVersion=8.3-dev +# syntax=docker/dockerfile:1 +ARG phpVersion=8.5 +FROM composer:2 AS composer + FROM php:${phpVersion}-fpm ARG WORKDIR=/var/www/html -ENV DOCUMENT_ROOT=${WORKDIR} -ENV LARAVEL_PROCS_NUMBER=1 -ENV NODE_MAJOR=20 ARG GROUP_ID=1000 ARG USER_ID=1000 -ENV USER_NAME=www-data ARG GROUP_NAME=www-data -# Install system dependencies -RUN apt-get update && apt-get install -y \ - git \ - curl \ - libfreetype6-dev \ - libjpeg62-turbo-dev \ - libmemcached-dev \ - libzip-dev \ - libpng-dev \ - libonig-dev \ - libxml2-dev \ - librdkafka-dev \ - libpq-dev \ - openssh-server \ - zip \ - unzip \ - supervisor \ - sqlite3 \ - nano \ - cron -# Install Nodejs -RUN apt-get update && apt-get install -y ca-certificates curl gnupg -RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list -RUN apt-get update && apt-get install nodejs -y +ARG NODE_MAJOR=24 -# Clear cache -RUN apt-get clean && rm -rf /var/lib/apt/lists/* -# Install Kafka -RUN git clone https://github.com/arnaud-lb/php-rdkafka.git\ - && cd php-rdkafka \ - && phpize \ - && ./configure \ - && make all -j 5 \ - && make install +LABEL org.opencontainers.image.title="laravel-php-fpm" \ + org.opencontainers.image.description="PHP-FPM image for Laravel and PHP applications" \ + org.opencontainers.image.source="https://github.com/jkaninda/laravel-php-fpm" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.authors="Jonas Kaninda" -# Install Rdkafka and enable it -RUN docker-php-ext-enable rdkafka \ - && cd .. \ - && rm -rf /php-rdkafka +ENV DOCUMENT_ROOT=${WORKDIR} \ + APP_DIR=${WORKDIR} \ + USER_NAME=www-data \ + NODE_MAJOR=${NODE_MAJOR} \ + LARAVEL_PROCS_NUMBER=1 \ + LARAVEL_QUEUE_WORKER_ENABLED=true \ + LARAVEL_QUEUE_OPTIONS="--sleep=3 --tries=3" \ + LARAVEL_SCHEDULER_ENABLED=true \ + PHP_DATE_TIMEZONE=UTC \ + PHP_DISPLAY_ERRORS=Off \ + PHP_MEMORY_LIMIT=256M \ + PHP_UPLOAD_MAX_FILESIZE=30M \ + PHP_POST_MAX_SIZE=30M \ + PHP_MAX_EXECUTION_TIME=30 \ + PHP_OPCACHE_ENABLE=1 \ + PHP_OPCACHE_ENABLE_CLI=0 \ + PHP_OPCACHE_MEMORY_CONSUMPTION=512 \ + PHP_OPCACHE_MAX_ACCELERATED_FILES=50000 \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + PHP_OPCACHE_REVALIDATE_FREQ=0 \ + PHP_FPM_PM=dynamic \ + PHP_FPM_PM_MAX_CHILDREN=20 \ + PHP_FPM_PM_START_SERVERS=2 \ + PHP_FPM_PM_MIN_SPARE_SERVERS=1 \ + PHP_FPM_PM_MAX_SPARE_SERVERS=3 \ + PHP_FPM_PM_MAX_REQUESTS=500 -# Install PHP extensions zip, mbstring, exif, bcmath, intl -RUN docker-php-ext-configure gd --with-freetype --with-jpeg -RUN docker-php-ext-install zip mbstring exif pcntl bcmath -j$(nproc) gd intl +# Runtime tools and Node.js +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + ca-certificates curl gnupg git openssh-client unzip zip \ + supervisor cron sqlite3 nano; \ + mkdir -p /etc/apt/keyrings; \ + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \ + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" > /etc/apt/sources.list.d/nodesource.list; \ + apt-get update; \ + apt-get install -y --no-install-recommends nodejs; \ + rm -rf /var/lib/apt/lists/* -# Install Redis and enable it -RUN pecl install redis && docker-php-ext-enable redis +# PHP extensions (build dependencies are purged, runtime libraries are kept) +RUN set -eux; \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + libfreetype6-dev libjpeg62-turbo-dev libpng-dev libwebp-dev \ + libicu-dev libmemcached-dev libonig-dev libpq-dev librdkafka-dev \ + libxml2-dev libzip-dev zlib1g-dev; \ + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \ + docker-php-ext-install -j"$(nproc)" bcmath exif gd intl mbstring pcntl pdo_mysql pdo_pgsql zip; \ + pecl install redis memcached rdkafka; \ + docker-php-ext-enable redis memcached rdkafka; \ + # OPcache is always compiled in since PHP 8.5 + if ! php -m | grep -qi '^zend opcache$'; then docker-php-ext-install opcache; fi; \ + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark > /dev/null; \ + find /usr/local -type f \( -executable -o -name '*.so' \) -exec ldd '{}' ';' 2>/dev/null \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \ + | sort -u | xargs -r dpkg-query --search | awk -F: '!/^diversion/ { print $1 }' | sort -u | xargs -r apt-mark manual; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/* /tmp/pear ~/.pearrc; \ + ! php -m 2>&1 | grep -iE 'warning|unable to load' +COPY --from=composer /usr/bin/composer /usr/local/bin/composer - -# Install the php memcached extension -RUN pecl install memcached && docker-php-ext-enable memcached - -# Install the PHP pdo_mysql extention -RUN docker-php-ext-install pdo_mysql - -# Install the PHP pdo_pgsql extention -RUN docker-php-ext-install pdo_pgsql - -# Install PHP Opcache extention -RUN docker-php-ext-install opcache - - -# Install Composer -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - -# Set working directory WORKDIR $WORKDIR -RUN rm -Rf /var/www/* && \ -mkdir -p /var/www/html +RUN rm -Rf /var/www/* && mkdir -p ${WORKDIR} /etc/supervisor/conf.d /var/log/supervisor -ADD src/index.php $WORKDIR/index.php -ADD src/php.ini $PHP_INI_DIR/conf.d/ -ADD src/opcache.ini $PHP_INI_DIR/conf.d/ -ADD src/supervisor/supervisord.conf /etc/supervisor/supervisord.conf - -COPY src/entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/entrypoint.sh -RUN ln -s /usr/local/bin/entrypoint.sh / - - -ENTRYPOINT ["entrypoint.sh"] +COPY src/index.php $WORKDIR/index.php +COPY src/php.ini src/opcache.ini $PHP_INI_DIR/conf.d/ +COPY src/php-fpm.d/zz-laravel.conf /usr/local/etc/php-fpm.d/zz-laravel.conf +COPY src/supervisor/supervisord.conf /etc/supervisor/supervisord.conf +COPY --chmod=755 src/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN ln -s /usr/local/bin/entrypoint.sh /entrypoint.sh RUN usermod -u ${USER_ID} ${USER_NAME} && \ - groupmod -g ${USER_ID} ${GROUP_NAME} && \ - chown -R ${USER_NAME}:${GROUP_NAME} /var/www && \ - chown -R ${USER_NAME}:${GROUP_NAME} /var/log/ && \ - chown -R ${USER_NAME}:${GROUP_NAME} /etc/supervisor/conf.d/ && \ - chown -R ${USER_NAME}:${GROUP_NAME} $PHP_INI_DIR/conf.d/ && \ - chown -R ${USER_NAME}:${GROUP_NAME} /tmp + groupmod -g ${GROUP_ID} ${GROUP_NAME} && \ + chown -R ${USER_NAME}:${GROUP_NAME} /var/www /var/log /etc/supervisor/conf.d $PHP_INI_DIR/conf.d /tmp + +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --start-interval=2s --retries=3 \ + CMD php -r 'exit(@fsockopen("127.0.0.1", 9000) ? 0 : 1);' EXPOSE 9000 -CMD [ "entrypoint" ] +ENTRYPOINT ["entrypoint.sh"] +CMD ["entrypoint"] diff --git a/src/docker/Dockerfile.alpine b/src/docker/Dockerfile.alpine index a9d7d99..aad556c 100644 --- a/src/docker/Dockerfile.alpine +++ b/src/docker/Dockerfile.alpine @@ -1,24 +1,93 @@ -ARG phpVersion=8.3-dev +# syntax=docker/dockerfile:1 +ARG phpVersion=8.5 +FROM composer:2 AS composer + FROM php:${phpVersion}-fpm-alpine ARG WORKDIR=/var/www/html -ENV DOCUMENT_ROOT=${WORKDIR} -ENV LARAVEL_PROCS_NUMBER=1 ARG GROUP_ID=1000 ARG USER_ID=1000 -ENV USER_NAME=www-data ARG GROUP_NAME=www-data -# Install the PHP pdo_mysql extention -RUN docker-php-ext-install pdo_mysql +LABEL org.opencontainers.image.title="laravel-php-fpm" \ + org.opencontainers.image.description="PHP-FPM image for Laravel and PHP applications (Alpine)" \ + org.opencontainers.image.source="https://github.com/jkaninda/laravel-php-fpm" \ + org.opencontainers.image.licenses="MIT" \ + org.opencontainers.image.authors="Jonas Kaninda" -# Install Composer -RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer +ENV DOCUMENT_ROOT=${WORKDIR} \ + APP_DIR=${WORKDIR} \ + USER_NAME=www-data \ + LARAVEL_PROCS_NUMBER=1 \ + LARAVEL_QUEUE_WORKER_ENABLED=true \ + LARAVEL_QUEUE_OPTIONS="--sleep=3 --tries=3" \ + LARAVEL_SCHEDULER_ENABLED=true \ + PHP_DATE_TIMEZONE=UTC \ + PHP_DISPLAY_ERRORS=Off \ + PHP_MEMORY_LIMIT=256M \ + PHP_UPLOAD_MAX_FILESIZE=30M \ + PHP_POST_MAX_SIZE=30M \ + PHP_MAX_EXECUTION_TIME=30 \ + PHP_OPCACHE_ENABLE=1 \ + PHP_OPCACHE_ENABLE_CLI=0 \ + PHP_OPCACHE_MEMORY_CONSUMPTION=512 \ + PHP_OPCACHE_MAX_ACCELERATED_FILES=50000 \ + PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \ + PHP_OPCACHE_REVALIDATE_FREQ=0 \ + PHP_FPM_PM=dynamic \ + PHP_FPM_PM_MAX_CHILDREN=20 \ + PHP_FPM_PM_START_SERVERS=2 \ + PHP_FPM_PM_MIN_SPARE_SERVERS=1 \ + PHP_FPM_PM_MAX_SPARE_SERVERS=3 \ + PHP_FPM_PM_MAX_REQUESTS=500 + +# Runtime tools and Node.js +RUN apk add --no-cache bash curl git openssh-client unzip zip supervisor sqlite nano nodejs npm + +# PHP extensions (build dependencies are removed, runtime libraries are kept) +RUN set -eux; \ + apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \ + freetype-dev libjpeg-turbo-dev libpng-dev libwebp-dev \ + icu-dev libmemcached-dev oniguruma-dev libpq-dev librdkafka-dev \ + libxml2-dev libzip-dev zlib-dev cyrus-sasl-dev linux-headers; \ + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \ + docker-php-ext-install -j"$(nproc)" bcmath exif gd intl mbstring pcntl pdo_mysql pdo_pgsql zip; \ + pecl install redis memcached rdkafka; \ + docker-php-ext-enable redis memcached rdkafka; \ + # OPcache is always compiled in since PHP 8.5 + if ! php -m | grep -qi '^zend opcache$'; then docker-php-ext-install opcache; fi; \ + runDeps="$( \ + scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \ + | tr ',' '\n' | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + )"; \ + apk add --no-cache --virtual .php-ext-rundeps $runDeps; \ + apk del --no-network .build-deps; \ + rm -rf /tmp/pear ~/.pearrc; \ + ! php -m 2>&1 | grep -iE 'warning|unable to load' + +COPY --from=composer /usr/bin/composer /usr/local/bin/composer -# Set working directory WORKDIR $WORKDIR -RUN rm -Rf /var/www/* && \ -mkdir -p /var/www/html +RUN rm -Rf /var/www/* && mkdir -p ${WORKDIR} /etc/supervisor/conf.d /var/log/supervisor -ADD src/index.php $WORKDIR/index.php -ADD src/php.ini $PHP_INI_DIR/conf.d/ +COPY src/index.php $WORKDIR/index.php +COPY src/php.ini src/opcache.ini $PHP_INI_DIR/conf.d/ +COPY src/php-fpm.d/zz-laravel.conf /usr/local/etc/php-fpm.d/zz-laravel.conf +COPY src/supervisor/supervisord.conf /etc/supervisor/supervisord.conf +COPY --chmod=755 src/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN ln -s /usr/local/bin/entrypoint.sh /entrypoint.sh && \ + ln -sf /etc/supervisor/supervisord.conf /etc/supervisord.conf + +RUN apk add --no-cache --virtual .user-deps shadow && \ + usermod -u ${USER_ID} ${USER_NAME} && \ + groupmod -g ${GROUP_ID} ${GROUP_NAME} && \ + apk del --no-network .user-deps && \ + chown -R ${USER_NAME}:${GROUP_NAME} /var/www /var/log /etc/supervisor/conf.d $PHP_INI_DIR/conf.d /tmp + +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --start-interval=2s --retries=3 \ + CMD php -r 'exit(@fsockopen("127.0.0.1", 9000) ? 0 : 1);' + +EXPOSE 9000 +ENTRYPOINT ["entrypoint.sh"] +CMD ["entrypoint"] diff --git a/src/entrypoint.sh b/src/entrypoint.sh index 310d0c2..0cd84f4 100644 --- a/src/entrypoint.sh +++ b/src/entrypoint.sh @@ -1,13 +1,12 @@ -#!/bin/bash +#!/bin/sh set -e # Logging functions log() { - local level=$1 + level=$1 shift - { set +x; } 2> /dev/null - echo "[$level] $@" + echo "[$level] $*" } info() { @@ -23,6 +22,32 @@ fatal() { exit 1 } +is_true() { + case "$1" in + 1|true|TRUE|True|yes|YES|on|ON) return 0 ;; + *) return 1 ;; + esac +} + +APP_DIR="${APP_DIR:-/var/www/html}" +USER_NAME="${USER_NAME:-www-data}" +LARAVEL_PROCS_NUMBER="${LARAVEL_PROCS_NUMBER:-1}" +LARAVEL_QUEUE_OPTIONS="${LARAVEL_QUEUE_OPTIONS:---sleep=3 --tries=3}" + +# Check if custom php.ini file exists +PHP_INI_SOURCE="$APP_DIR/conf/php/php.ini" +PHP_INI_TARGET="$PHP_INI_DIR/conf.d/php.ini" + +if [ -f "$PHP_INI_SOURCE" ]; then + cp "$PHP_INI_SOURCE" "$PHP_INI_TARGET" + info "Custom php.ini file found and copied to $PHP_INI_TARGET" +fi + +# Run any other command (e.g. "php artisan migrate", "composer install", "bash") +if [ $# -gt 0 ] && [ "$1" != "entrypoint" ] && [ "$1" != "supervisord" ]; then + exec "$@" +fi + # Banner echo "" echo "***********************************************************" @@ -30,13 +55,14 @@ echo " Starting LARAVEL PHP-FPM Container " echo "***********************************************************" # Check if the artisan file exists -ARTISAN_PATH="/var/www/html/artisan" -if [[ -f "$ARTISAN_PATH" ]]; then +ARTISAN_PATH="$APP_DIR/artisan" +SUPERVISOR_TASK="/etc/supervisor/conf.d/laravel-worker.conf" +if [ -f "$ARTISAN_PATH" ]; then info "Artisan file found, creating Laravel supervisor config..." + : > "$SUPERVISOR_TASK" - # Create Laravel Supervisor config - SUPERVISOR_TASK="/etc/supervisor/conf.d/laravel-worker.conf" - cat > "$SUPERVISOR_TASK" <> "$SUPERVISOR_TASK" <> "$SUPERVISOR_TASK" </dev/null || true info "Artisan file not found at $ARTISAN_PATH" fi -# Check if custom php.ini file exists -PHP_INI_SOURCE="/var/www/html/conf/php/php.ini" -PHP_INI_TARGET="$PHP_INI_DIR/conf.d/php.ini" - -if [[ -f "$PHP_INI_SOURCE" ]]; then - cp "$PHP_INI_SOURCE" "$PHP_INI_TARGET" - info "Custom php.ini file found and copied to $PHP_INI_TARGET" -else - info "Custom php.ini file not found at $PHP_INI_SOURCE" - info "To use a custom php.ini file, place it at $PHP_INI_SOURCE" +# Start Supervisor (programs drop privileges to $USER_NAME themselves) +if [ "$(id -u)" = "0" ]; then + exec supervisord -c /etc/supervisor/supervisord.conf -u root fi - -# Start Supervisor -supervisord -c /etc/supervisor/supervisord.conf \ No newline at end of file +exec supervisord -c /etc/supervisor/supervisord.conf diff --git a/src/opcache.ini b/src/opcache.ini index 9710908..00877bf 100644 --- a/src/opcache.ini +++ b/src/opcache.ini @@ -1,12 +1,12 @@ [opcache] ; Determines if Zend OPCache is enabled -opcache.enable=1 +opcache.enable=${PHP_OPCACHE_ENABLE} ; Determines if Zend OPCache is enabled for the CLI version of PHP -;opcache.enable_cli=1 +opcache.enable_cli=${PHP_OPCACHE_ENABLE_CLI} ; The OPcache shared memory storage size. -opcache.memory_consumption=512 +opcache.memory_consumption=${PHP_OPCACHE_MEMORY_CONSUMPTION} ; The amount of memory for interned strings in Mbytes. opcache.interned_strings_buffer=64 @@ -15,7 +15,7 @@ opcache.interned_strings_buffer=64 ; Only numbers between 200 and 1000000 are allowed. ;If you have multiple PHP sites on the server then consider the value 130986 ; for magento 2, keep 65406 -opcache.max_accelerated_files=50000 +opcache.max_accelerated_files=${PHP_OPCACHE_MAX_ACCELERATED_FILES} ; The maximum percentage of "wasted" memory until a restart is scheduled. opcache.max_wasted_percentage=15 @@ -30,7 +30,7 @@ opcache.max_wasted_percentage=15 ; webserver for changes to the filesystem to take effect. ; For Development / testing, keep 1 ; For performance / production, keep 0 -opcache.validate_timestamps=0 +opcache.validate_timestamps=${PHP_OPCACHE_VALIDATE_TIMESTAMPS} ;opcache.revalidate_freq How often in seconds should the code ;cache expire and check if your code has changed. 0 means it @@ -39,7 +39,7 @@ opcache.validate_timestamps=0 ;should not be enabled by default, as long as it's disabled then any value for opcache. ;revalidate_freq will basically be ignored. You should really only ever enable ;this during development, you don't really want to enable this setting for a production application. -opcache.revalidate_freq=0 +opcache.revalidate_freq=${PHP_OPCACHE_REVALIDATE_FREQ} ; Enables or disables file search in include_path optimization ;opcache.revalidate_path=0 @@ -48,10 +48,6 @@ opcache.revalidate_freq=0 ; size of the optimized code. opcache.save_comments=1 -; If enabled, a fast shutdown sequence is used for the accelerated code -; Depending on the used Memory Manager this may cause some incompatibilities. -opcache.fast_shutdown=1 - ; Allow file existence override (file_exists, etc.) performance feature. ;opcache.enable_file_override=0 @@ -106,7 +102,7 @@ opcache.fast_shutdown=1 ; processes have to map shared memory into the same address space. This ; directive allows to manually fix the "Unable to reattach to base address" ; errors. -opcache.mmap_base=0x20000000 +;opcache.mmap_base= ; Enables and sets the second level cache directory. ; It should improve performance when SHM memory is full, at server restart or @@ -122,7 +118,7 @@ opcache.mmap_base=0x20000000 ; Implies opcache.file_cache_only=1 for a certain process that failed to ; reattach to the shared memory (for Windows only). Explicitly enabled file ; cache is required. -opcache.file_cache_fallback=1 +;opcache.file_cache_fallback=1 ; Enables or disables copying of PHP code (text segment) into HUGE PAGES. ; This should improve performance, but requires appropriate OS configuration. diff --git a/src/php-fpm.d/zz-laravel.conf b/src/php-fpm.d/zz-laravel.conf new file mode 100644 index 0000000..ccce079 --- /dev/null +++ b/src/php-fpm.d/zz-laravel.conf @@ -0,0 +1,15 @@ +[www] +; Expose container environment variables (DB_HOST, APP_KEY, ...) to PHP workers +clear_env = no + +; Process manager, tunable through PHP_FPM_* environment variables +pm = ${PHP_FPM_PM} +pm.max_children = ${PHP_FPM_PM_MAX_CHILDREN} +pm.start_servers = ${PHP_FPM_PM_START_SERVERS} +pm.min_spare_servers = ${PHP_FPM_PM_MIN_SPARE_SERVERS} +pm.max_spare_servers = ${PHP_FPM_PM_MAX_SPARE_SERVERS} +pm.max_requests = ${PHP_FPM_PM_MAX_REQUESTS} + +; Health endpoint for FastCGI probes +ping.path = /ping +ping.response = pong diff --git a/src/php.ini b/src/php.ini index 6358c68..67e281a 100644 --- a/src/php.ini +++ b/src/php.ini @@ -1,6 +1,9 @@ -date.timezone=UTC -display_errors=Off +; Values can be overridden with environment variables (see README) +date.timezone=${PHP_DATE_TIMEZONE} +display_errors=${PHP_DISPLAY_ERRORS} log_errors=On -upload_max_filesize= 30M -post_max_size= 30M -memory_limit = 256M +expose_php=Off +upload_max_filesize=${PHP_UPLOAD_MAX_FILESIZE} +post_max_size=${PHP_POST_MAX_SIZE} +memory_limit=${PHP_MEMORY_LIMIT} +max_execution_time=${PHP_MAX_EXECUTION_TIME} diff --git a/src/supervisor/supervisord.conf b/src/supervisor/supervisord.conf index ad0eef2..64c5881 100644 --- a/src/supervisor/supervisord.conf +++ b/src/supervisor/supervisord.conf @@ -1,23 +1,31 @@ [supervisord] nodaemon=true -user=%(ENV_USER_NAME)s logfile=/var/log/supervisor/supervisord.log logfile_maxbytes = 50MB pidfile=/tmp/supervisord.pid directory = /tmp +[unix_http_server] +file=/tmp/supervisor.sock +chmod=0700 + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix:///tmp/supervisor.sock + [program:php-fpm] -command=/usr/local/sbin/php-fpm +command=/usr/local/sbin/php-fpm --nodaemonize numprocs=1 autostart=true autorestart=true redirect_stderr=true stdout_logfile=/dev/stdout -stderr_logfile=/dev/stdout stdout_logfile_maxbytes=0 -user=www-data priority=1 +stopsignal=QUIT [include] -files = /etc/supervisor/conf.d/*.conf \ No newline at end of file +files = /etc/supervisor/conf.d/*.conf diff --git a/tests/compose.yaml b/tests/compose.yaml index c8e9865..e8787d0 100644 --- a/tests/compose.yaml +++ b/tests/compose.yaml @@ -1,41 +1,45 @@ -version: '3' services: - mysql: - image: mysql:8.0 - container_name: mysql - restart: unless-stopped - environment: - MYSQL_DATABASE: php - MYSQL_ROOT_PASSWORD: password - MYSQL_PASSWORD: password - MYSQL_USER: php - ports: - - 3306:3306 - php-fpm: - image: jkaninda/laravel-php-fpm:latest - container_name: php-fpm - restart: unless-stopped - #user: www-data #Use www-data user production usage - depends_on: - - mysql - environment: - DB_HOST: mysql - DB_PORT: 3306 - DB_DATABASE: php - DB_USERNAME: php - DB_PASSWORD: password - volumes: - - ./laravel:/var/www/html - #Nginx server - nginx-server: - image: jkaninda/nginx-fpm:stable - container_name: nginx-server - restart: unless-stopped - ports: - - 80:80 - volumes: + mysql: + image: mysql:8.4 + container_name: mysql + restart: unless-stopped + environment: + MYSQL_DATABASE: php + MYSQL_ROOT_PASSWORD: password + MYSQL_PASSWORD: password + MYSQL_USER: php + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-ppassword"] + interval: 5s + timeout: 5s + retries: 30 + php-fpm: + image: ${IMAGE:-jkaninda/laravel-php-fpm:latest} + container_name: php-fpm + restart: unless-stopped + depends_on: + mysql: + condition: service_healthy + environment: + DB_CONNECTION: mysql + DB_HOST: mysql + DB_PORT: 3306 + DB_DATABASE: php + DB_USERNAME: php + DB_PASSWORD: password + volumes: - ./laravel:/var/www/html - environment: - - DOCUMENT_ROOT=/var/www/html/public - - CLIENT_MAX_BODY_SIZE=20M - - PHP_FPM_HOST=php-fpm:9000 + nginx-server: + image: jkaninda/nginx-fpm:stable + container_name: nginx-server + restart: unless-stopped + depends_on: + - php-fpm + ports: + - "${HTTP_PORT:-80}:80" + volumes: + - ./laravel:/var/www/html + environment: + - DOCUMENT_ROOT=/var/www/html/public + - CLIENT_MAX_BODY_SIZE=20M + - PHP_FPM_HOST=php-fpm:9000 diff --git a/tests/integration.sh b/tests/integration.sh new file mode 100755 index 0000000..6021676 --- /dev/null +++ b/tests/integration.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Integration test: create a Laravel project with the image, serve it through +# Nginx, run migrations and check that PHP-FPM, the queue worker and the +# scheduler are running. +# Usage: IMAGE=jkaninda/laravel-php-fpm:8.5 [HTTP_PORT=8080] ./tests/integration.sh +set -eu + +IMAGE="${IMAGE:-jkaninda/laravel-php-fpm:latest}" +HTTP_PORT="${HTTP_PORT:-80}" +export IMAGE HTTP_PORT +cd "$(dirname "$0")" + +cleanup() { + docker compose logs php-fpm || true + docker compose down -v --remove-orphans || true +} +trap cleanup EXIT + +echo "==> Checking PHP extensions" +docker run --rm "$IMAGE" php -m > modules.txt +for ext in bcmath exif gd intl mbstring memcached pcntl pdo_mysql pdo_pgsql rdkafka redis zip "Zend OPcache"; do + grep -qx "$ext" modules.txt || { echo "Missing extension: $ext"; exit 1; } +done +if docker run --rm "$IMAGE" php -m 2>&1 | grep -iE 'warning|unable to load'; then + echo "PHP startup warnings detected"; exit 1 +fi +rm -f modules.txt +docker run --rm "$IMAGE" sh -c 'php -v && composer -V && node -v && npm -v' + +echo "==> Creating Laravel project" +rm -rf laravel +docker run --rm -v "$PWD:/app" -w /app "$IMAGE" \ + composer create-project --prefer-dist --no-interaction --no-progress laravel/laravel laravel +docker run --rm -v "$PWD/laravel:/var/www/html" "$IMAGE" \ + chown -R www-data:www-data storage bootstrap/cache database + +echo "==> Starting stack" +docker compose up -d + +echo "==> Waiting for PHP-FPM to be healthy" +i=0 +until [ "$(docker inspect -f '{{.State.Health.Status}}' php-fpm)" = "healthy" ]; do + i=$((i + 1)); [ "$i" -gt 60 ] && { echo "PHP-FPM not healthy"; exit 1; } + sleep 2 +done + +echo "==> Running migrations" +docker exec --user www-data php-fpm php artisan migrate --force + +echo "==> Checking HTTP response" +code=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$HTTP_PORT") +echo "HTTP $code" +[ "$code" = "200" ] + +echo "==> Checking supervisor programs" +for program in php-fpm Laravel-worker Laravel-scheduler; do + i=0 + until docker exec php-fpm supervisorctl status | grep "^$program" | grep -q RUNNING; do + i=$((i + 1)); [ "$i" -gt 30 ] && { echo "$program is not running"; docker exec php-fpm supervisorctl status; exit 1; } + sleep 2 + done +done +docker exec php-fpm supervisorctl status + +echo "==> All tests passed"