├── 8.x ├── shared │ ├── crontab │ ├── start-server.sh │ ├── php.ini │ └── entrypoint.sh ├── Dockerfile.octane.swoole ├── Dockerfile.octane.openswoole ├── Dockerfile.octane.roadrunner ├── .dockerignore ├── Dockerfile.octane.frankenphp └── Dockerfile ├── .idea ├── vcs.xml ├── .gitignore ├── laravel-alpine-images.iml ├── modules.xml └── php.xml ├── .github ├── actions │ ├── generate-image-tags │ │ ├── action.yml │ │ └── generate-image-tags.php │ ├── version-outdated │ │ └── action.yml │ └── runtime-version-compare │ │ └── action.yml └── workflows │ └── main.yml ├── LICENSE ├── .gitignore └── README.md /8.x/shared/crontab: -------------------------------------------------------------------------------- 1 | * * * * * su-exec laravel php $APP_DIRECTORY/artisan schedule:run > /proc/1/fd/1 2>&1 2 | -------------------------------------------------------------------------------- /8.x/shared/start-server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | php -d variables_order=EGPCS $APP_DIRECTORY/artisan serve --host=0.0.0.0 --port=80 -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/laravel-alpine-images.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /8.x/Dockerfile.octane.swoole: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION 2 | FROM ghcr.io/iksaku/laravel-alpine:${PHP_VERSION}-cli 3 | 4 | # Overwrite entrypoint to use Octane 5 | RUN sed -i \ 6 | 's/artisan serve/artisan octane:start --server=swoole/' \ 7 | /usr/local/bin/start-server 8 | 9 | # Install PHP extensions required by Octane 10 | RUN install-php-extensions swoole 11 | -------------------------------------------------------------------------------- /8.x/Dockerfile.octane.openswoole: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION 2 | FROM ghcr.io/iksaku/laravel-alpine:${PHP_VERSION}-cli 3 | 4 | # Overwrite entrypoint to use Octane 5 | RUN sed -i \ 6 | 's/artisan serve/artisan octane:start --server=swoole/' \ 7 | /usr/local/bin/start-server 8 | 9 | # Install PHP extensions required by Octane 10 | RUN install-php-extensions openswoole 11 | -------------------------------------------------------------------------------- /8.x/Dockerfile.octane.roadrunner: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION 2 | FROM ghcr.io/iksaku/laravel-alpine:${PHP_VERSION}-cli 3 | 4 | # Overwrite entrypoint to use Octane 5 | RUN sed -i \ 6 | 's/artisan serve/artisan octane:start --server=roadrunner --rpc-port=6001/' \ 7 | /usr/local/bin/start-server 8 | 9 | # Install PHP extensions required by Octane 10 | RUN install-php-extensions sockets 11 | 12 | # Install RoadRunner binary 13 | COPY --from=ghcr.io/roadrunner-server/roadrunner:latest /usr/bin/rr /usr/local/bin/rr 14 | 15 | RUN setcap "cap_net_bind_service=+ep" /usr/local/bin/rr -------------------------------------------------------------------------------- /8.x/.dockerignore: -------------------------------------------------------------------------------- 1 | # Based of @fideloper's Fly.io template 2 | # https://github.com/superfly/flyctl/blob/698f3bda500015354773a2544c90f0b2a6b5ca3a/scanner/templates/laravel/.dockerignore 3 | 4 | # 1. Ignore Laravel-specific files we don't need 5 | bootstrap/cache/* 6 | storage/framework/cache/* 7 | storage/framework/sessions/* 8 | storage/framework/views/* 9 | storage/logs/* 10 | *.env* 11 | .rr.yaml 12 | rr 13 | 14 | # 2. Ignore common files/directories we don't need 15 | .vscode 16 | .idea 17 | vendor 18 | **/*node_modules 19 | **.git 20 | **.gitignore 21 | **.gitattributes 22 | **.sass-cache 23 | **/*~ 24 | **/*.log 25 | **/.DS_Store 26 | **/Thumbs.db 27 | -------------------------------------------------------------------------------- /.github/actions/generate-image-tags/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Generate Image Tags' 2 | 3 | inputs: 4 | php: 5 | description: 'PHP version and (optional) variant' 6 | required: true 7 | octane-runtime: 8 | description: 'Octane runtime and version' 9 | required: false 10 | 11 | outputs: 12 | tags: 13 | description: 'JSON-encoded generated tags' 14 | value: ${{ steps.tags.outputs.tags }} 15 | 16 | runs: 17 | using: composite 18 | steps: 19 | - name: Generate Tags 20 | id: tags 21 | shell: bash 22 | working-directory: ${{ github.action_path }} 23 | run: | 24 | echo "tags=$(php generate-image-tags.php ${{ inputs.php }} ${{ inputs.octane-runtime }})" >> $GITHUB_OUTPUT 25 | -------------------------------------------------------------------------------- /8.x/Dockerfile.octane.frankenphp: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION 2 | 3 | # Workaround as `COPY --from` doesn't support argument interpolation 4 | FROM dunglas/frankenphp:latest-php${PHP_VERSION}-alpine AS frankenphp 5 | 6 | FROM ghcr.io/iksaku/laravel-alpine:${PHP_VERSION}-zts 7 | 8 | RUN apk add --no-cache ca-certificates 9 | 10 | # Overwrite entrypoint to use Octane 11 | RUN sed -i \ 12 | 's/artisan serve/artisan octane:start --server=frankenphp --admin-port=2019/' \ 13 | /usr/local/bin/start-server 14 | 15 | # Install FrankenPHP binary 16 | COPY --from=frankenphp /usr/local/bin/frankenphp /usr/local/bin/frankenphp 17 | 18 | RUN setcap "cap_net_bind_service=+ep" /usr/local/bin/frankenphp 19 | 20 | EXPOSE 443 21 | EXPOSE 443/udp 22 | EXPOSE 2019 23 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 13 | 14 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /8.x/shared/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | expose_php = Off 3 | display_errors = Off 4 | display_startup_errors = Off 5 | 6 | log_errors = On 7 | ; /dev/stdout goes to /dev/null so don't use that 8 | error_log = /dev/stderr 9 | 10 | max_execution_time = 60 11 | 12 | post_max_size = 100M 13 | upload_max_filesize = 100M 14 | memory_limit = 128M 15 | 16 | ; This directive determines which super global arrays are registered when PHP 17 | ; starts up. G,P,C,E & S are abbreviations for the following respective super 18 | ; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty 19 | ; paid for the registration of these arrays and because ENV is not as commonly 20 | ; used as the others, ENV is not recommended on productions servers. You 21 | ; can still get access to the environment variables through getenv() should you 22 | ; need to. 23 | ; Default Value: "EGPCS" 24 | ; Development Value: "GPCS" 25 | ; Production Value: "GPCS"; 26 | ; http://php.net/variables-order 27 | variables_order = EGPCS 28 | -------------------------------------------------------------------------------- /8.x/shared/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if [ "$LARAVEL_SAIL" = '1' ]; then 4 | if ! id 'sail' &>/dev/null; then 5 | useradd -MNo -g laravel -u $(id -u laravel) sail 6 | fi 7 | 8 | if [ ! -z "$WWWUSER" ]; then 9 | usermod -ou $WWWUSER sail &>/dev/null 10 | fi 11 | fi 12 | 13 | if [ ! -z "$WWWUSER" ]; then 14 | usermod -ou $WWWUSER laravel &>/dev/null 15 | fi 16 | 17 | if [ ! -z "$WWWGROUP" ]; then 18 | groupmod -og $WWWGROUP laravel &>/dev/null 19 | fi 20 | 21 | chown -R laravel:laravel $APP_DIRECTORY 22 | 23 | if [ $# -gt 0 ]; then 24 | # Execute given command under container's user. 25 | su-exec laravel "$@" 26 | else 27 | if [ -d $APP_DIRECTORY/.deploy ] && [ "$LARAVEL_SAIL" != '1' ] && [ "$RUN_DEPLOY_SCRIPTS" = '1' ]; then 28 | for SCRIPT in $APP_DIRECTORY/.deploy/*.sh; do 29 | su-exec laravel sh $SCRIPT 30 | done 31 | fi 32 | 33 | # If no command is given, execute start script. 34 | su-exec laravel /usr/local/bin/start-server 35 | fi 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jorge González 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /8.x/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION 2 | ARG VARIANT 3 | FROM php:${PHP_VERSION}-${VARIANT}-alpine as base 4 | 5 | ENV APP_DIRECTORY=/var/www/html 6 | 7 | WORKDIR ${APP_DIRECTORY} 8 | 9 | # Get php extension installer 10 | ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ 11 | RUN chmod +x /usr/local/bin/install-php-extensions 12 | 13 | # Install system dependencies 14 | RUN set -eux \ 15 | && apk --update add --no-cache --purge \ 16 | # Required to serve our Laravel application 17 | libcap \ 18 | su-exec \ 19 | # Compatibility layer for user and group commands 20 | shadow \ 21 | # Install missing PHP extensions required by Laravel 22 | && install-php-extensions \ 23 | @composer \ 24 | bcmath \ 25 | intl \ 26 | pcntl \ 27 | mysqli pdo_mysql \ 28 | pgsql pdo_pgsql \ 29 | redis 30 | 31 | RUN setcap "cap_net_bind_service=+ep" /usr/local/bin/php 32 | 33 | COPY shared/entrypoint.sh /usr/local/bin/entrypoint 34 | COPY shared/start-server.sh /usr/local/bin/start-server 35 | RUN chmod +x /usr/local/bin/entrypoint /usr/local/bin/start-server 36 | 37 | COPY shared/php.ini /usr/local/etc/php/conf.d/php.ini 38 | 39 | # Install Laravel Scheduler to crontab. 40 | # Expected to be run crond as root, then su-exec takes over. 41 | COPY shared/crontab /var/spool/cron/crontabs/root 42 | 43 | RUN useradd -ms /bin/sh -u 1337 -U laravel 44 | 45 | EXPOSE 80 46 | 47 | ENTRYPOINT ["entrypoint"] 48 | -------------------------------------------------------------------------------- /.github/actions/version-outdated/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Check if version is outdated' 2 | 3 | inputs: 4 | tool-name: 5 | description: 'Name of the tool to echo results of' 6 | required: true 7 | base: 8 | description: 'Base (current) version for comparison' 9 | required: true 10 | head: 11 | description: 'Head (newer) version for comparison' 12 | required: true 13 | 14 | outputs: 15 | outdated: 16 | description: | 17 | Whether the provided "Base version" is up to date with "Head version". 18 | If "Base version" or "Head version" is empty, then we will mark "outdated" as "true". 19 | value: ${{ steps.compare.outputs.outdated }} 20 | 21 | runs: 22 | using: composite 23 | steps: 24 | - name: 'Compare Versions' 25 | id: compare 26 | shell: bash 27 | run: | 28 | if [ -z "${{ inputs.base }}" ] || [ -z "${{ inputs.head }}" ]; then 29 | echo "outdated=true" >> $GITHUB_OUTPUT 30 | else 31 | echo "outdated=$(php -r 'var_export(version_compare("${{ inputs.head }}", "${{ inputs.base }}", ">"));')" >> $GITHUB_OUTPUT 32 | fi 33 | - name: 'Output results' 34 | shell: bash 35 | run: | 36 | if [ -z "${{ inputs.base }}" ] || [ -z "${{ inputs.head }}" ]; then 37 | echo "${{ inputs.tool-name }} (${{ inputs.base || inputs.head }}) appears to be new!." 38 | elif [ "${{ steps.compare.outputs.outdated }}" == "true" ]; then 39 | echo "A new version of ${{ inputs.tool-name }} is available (${{ inputs.base }} -> ${{ inputs.head }})." 40 | else 41 | echo "${{ inputs.tool-name }} is up to date (${{ inputs.base }})." 42 | fi -------------------------------------------------------------------------------- /.github/actions/runtime-version-compare/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Check if runtime is up to date' 2 | 3 | inputs: 4 | runtime-name: 5 | description: 'Name of the runtime to print debug messages' 6 | required: true 7 | base-image: 8 | description: 'The base image that may or may not be up to date' 9 | required: true 10 | upstream-image: 11 | description: 'The upstream image that should always be up to date' 12 | required: true 13 | version-command: 14 | description: 'Shared command used to extract runtime versions from both base and upstream images.' 15 | required: false 16 | 17 | outputs: 18 | outdated: 19 | description: 'Whether the runtime is up to date' 20 | value: ${{ steps.compare.outputs.outdated }} 21 | version: 22 | description: | 23 | Most up to date runtime version. 24 | If upstream-version results in a falsy (empty) value, use the base-version instead. 25 | value: ${{ steps.upstream-version.outputs.version || steps.base-version.outputs.version }} 26 | 27 | runs: 28 | using: composite 29 | steps: 30 | - name: 'Get base runtime version' 31 | id: base-version 32 | shell: bash 33 | run: echo "version=$(docker run --rm ${{ inputs.base-image }} ${{ inputs.version-command }})" >> $GITHUB_OUTPUT 34 | 35 | - name: 'Get upstream runtime version' 36 | id: upstream-version 37 | shell: bash 38 | run: echo "version=$(docker run --rm ${{ inputs.upstream-image }} ${{ inputs.version-command }})" >> $GITHUB_OUTPUT 39 | 40 | - name: 'Compare runtime versions' 41 | if: ${{ !cancelled() }} 42 | id: compare 43 | uses: ./.github/actions/version-outdated 44 | with: 45 | tool-name: ${{ inputs.runtime-name }} 46 | base: ${{ steps.base-version.outputs.version }} 47 | head: ${{ steps.upstream-version.outputs.version }} 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### VisualStudioCode template 2 | .vscode/* 3 | !.vscode/settings.json 4 | !.vscode/tasks.json 5 | !.vscode/launch.json 6 | !.vscode/extensions.json 7 | !.vscode/*.code-snippets 8 | 9 | # Local History for Visual Studio Code 10 | .history/ 11 | 12 | # Built Visual Studio Code Extensions 13 | *.vsix 14 | 15 | ### JetBrains template 16 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 17 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 18 | 19 | # User-specific stuff 20 | .idea/**/workspace.xml 21 | .idea/**/tasks.xml 22 | .idea/**/usage.statistics.xml 23 | .idea/**/dictionaries 24 | .idea/**/shelf 25 | 26 | # AWS User-specific 27 | .idea/**/aws.xml 28 | 29 | # Generated files 30 | .idea/**/contentModel.xml 31 | 32 | # Sensitive or high-churn files 33 | .idea/**/dataSources/ 34 | .idea/**/dataSources.ids 35 | .idea/**/dataSources.local.xml 36 | .idea/**/sqlDataSources.xml 37 | .idea/**/dynamic.xml 38 | .idea/**/uiDesigner.xml 39 | .idea/**/dbnavigator.xml 40 | 41 | # Gradle 42 | .idea/**/gradle.xml 43 | .idea/**/libraries 44 | 45 | # Gradle and Maven with auto-import 46 | # When using Gradle or Maven with auto-import, you should exclude module files, 47 | # since they will be recreated, and may cause churn. Uncomment if using 48 | # auto-import. 49 | # .idea/artifacts 50 | # .idea/compiler.xml 51 | # .idea/jarRepositories.xml 52 | # .idea/modules.xml 53 | # .idea/*.iml 54 | # .idea/modules 55 | # *.iml 56 | # *.ipr 57 | 58 | # CMake 59 | cmake-build-*/ 60 | 61 | # Mongo Explorer plugin 62 | .idea/**/mongoSettings.xml 63 | 64 | # File-based project format 65 | *.iws 66 | 67 | # IntelliJ 68 | out/ 69 | 70 | # mpeltonen/sbt-idea plugin 71 | .idea_modules/ 72 | 73 | # JIRA plugin 74 | atlassian-ide-plugin.xml 75 | 76 | # Cursive Clojure plugin 77 | .idea/replstate.xml 78 | 79 | # SonarLint plugin 80 | .idea/sonarlint/ 81 | 82 | # Crashlytics plugin (for Android Studio and IntelliJ) 83 | com_crashlytics_export_strings.xml 84 | crashlytics.properties 85 | crashlytics-build.properties 86 | fabric.properties 87 | 88 | # Editor-based Rest Client 89 | .idea/httpRequests 90 | 91 | # Android studio 3.1+ serialized cache file 92 | .idea/caches/build_file_checksums.ser 93 | 94 | -------------------------------------------------------------------------------- /.github/actions/generate-image-tags/generate-image-tags.php: -------------------------------------------------------------------------------- 1 | priority = Tag::$nextPriority--; 13 | } 14 | 15 | public function append(string $append): Tag { 16 | $this->value .= $append; 17 | 18 | return $this; 19 | } 20 | 21 | public function suffix(string $suffix): Tag { 22 | $this->suffix = $suffix; 23 | 24 | return $this; 25 | } 26 | 27 | public function __clone(): void { 28 | $this->priority = Tag::$nextPriority--; 29 | } 30 | 31 | public function __toString(): string { 32 | $properties = [ 33 | 'type' => 'raw', 34 | 'value' => $this->value, 35 | ]; 36 | 37 | if (!is_null($this->prefix) && trim($this->prefix) !== '') { 38 | $properties['prefix'] = $this->prefix; 39 | } 40 | 41 | if (!is_null($this->suffix) && trim($this->suffix) !== '') { 42 | $properties['suffix'] = $this->suffix; 43 | } 44 | 45 | if ($this->priority > 0) { 46 | $properties['priority'] = $this->priority; 47 | } 48 | 49 | return implode(',', array_map( 50 | fn (mixed $value, string $key) => "{$key}={$value}", 51 | $properties, 52 | array_keys($properties) 53 | )); 54 | } 55 | } 56 | 57 | function destructure_defaults(int $values, array $arr): array { 58 | return array_replace( 59 | array_fill(0, $values, ''), 60 | $arr 61 | ); 62 | } 63 | 64 | function generate_semver_tags(string $semver): array { 65 | $parts = explode('.', $semver); 66 | 67 | if (count($parts) === 1) return $parts; 68 | 69 | return array_map( 70 | fn (int $length) => implode('.', array_slice($parts, 0, $length)), 71 | range(2, count($parts)) 72 | ); 73 | } 74 | 75 | [$php_version, $octane_runtime] = destructure_defaults(2, array_slice($argv, 1)); 76 | 77 | [$php_version, $php_variant] = destructure_defaults(2, explode(':', $php_version)); 78 | 79 | $tags = array_map( 80 | fn (string $semver) => new Tag(value: $semver), 81 | generate_semver_tags($php_version) 82 | ); 83 | 84 | if (trim($php_variant) !== '') { 85 | if ($php_variant === 'cli') { 86 | $tags = array_merge( 87 | $tags, 88 | array_map( 89 | fn (Tag $tag) => (clone $tag)->append('-cli'), 90 | $tags 91 | ) 92 | ); 93 | } else { 94 | array_walk( 95 | $tags, 96 | fn (Tag $tag) => $tag->append("-{$php_variant}") 97 | ); 98 | } 99 | } else if (trim($octane_runtime) !== '') { 100 | if (!str_contains($octane_runtime, ':')) { 101 | throw new \RuntimeException('Octane Runtime is missing version.'); 102 | } 103 | 104 | [$octane_runtime, $octane_runtime_version] = explode(':', $octane_runtime); 105 | 106 | if (trim($octane_runtime_version) === '') { 107 | throw new \RuntimeException('Octane Runtime is missing version.'); 108 | } 109 | 110 | $tags = array_merge( 111 | ...array_map( 112 | fn (Tag $tag) => [ 113 | $tag->append("-octane-{$octane_runtime}"), 114 | ...array_map( 115 | fn (string $semver) => (clone $tag)->append("-{$semver}"), 116 | generate_semver_tags($octane_runtime_version) 117 | ) 118 | ], 119 | $tags 120 | ) 121 | ); 122 | } 123 | 124 | $tags = array_merge( 125 | $tags, 126 | array_map( 127 | fn (Tag $tag) => (clone $tag)->suffix('-{{sha}}'), 128 | $tags 129 | ) 130 | ); 131 | 132 | usort($tags, fn (Tag $a, Tag $b) => $b->priority <=> $a->priority); 133 | 134 | echo json_encode(join(PHP_EOL, $tags)); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Alpine Docker Images 2 | 3 | This project provides Docker images containing the *bare minimum* requirements 4 | to run your Laravel application. We use PHP's official CLI/ZTS images based 5 | on Alpine Linux to further reduce the size of these images. 6 | 7 | You may notice that your favorite PHP extensions and NodeJS are _missing_ from 8 | these images, but this is a _feature_ ✨. The intention of these 9 | images is to provide the *absolute minimum requirements* so that you, the 10 | developer, pull these images, and further customize them with everything 11 | you need. 12 | 13 | ## Available Images 14 | 15 | You can choose the PHP version you want to run by specifying it as the tag for your image. 16 | Currently, we build base images for PHP versions `8.0` to `8.3`: 17 | * `ghcr.io/iksaku/laravel-alpine:8.0` 18 | * `ghcr.io/iksaku/laravel-alpine:8.1` 19 | * `ghcr.io/iksaku/laravel-alpine:8.2` 20 | * `ghcr.io/iksaku/laravel-alpine:8.3` 21 | 22 | ### Laravel Octane Images 23 | 24 | We also build images compatible with [Laravel Octane](https://laravel.com/docs/octane), 25 | you can choose your favorite flavor from the following list: 26 | 27 | | Runtime | Supported PHP versions | Image name example | 28 | | ------------ | ---------------------- | ----------------------------------------------------- | 29 | | `frankenphp` | `8.2`-`8.3` | `ghcr.io/iksaku/laravel-alpine:8.3-octane-frankenphp` | 30 | | `openswoole` | `8.0`-`8.3` | `ghcr.io/iksaku/laravel-alpine:8.3-octane-openswoole` | 31 | | `roadrunner` | `8.0`-`8.3` | `ghcr.io/iksaku/laravel-alpine:8.3-octane-roadrunner` | 32 | | `swoole` | `8.0`-`8.3` | `ghcr.io/iksaku/laravel-alpine:8.3-octane-swoole` | 33 | 34 | ### Available PHP extensions 35 | 36 | Pre-installed PHP extensions in these images follow [Laravel's Requirements](https://laravel.com/docs/deployment#server-requirements) 37 | and also include a few extras for Database and Octane support: 38 | 39 | | Name | Availability | 40 | | -------------- | ------------------------ | 41 | | bcmath | ✓ | 42 | | ctype | ✓ | 43 | | curl | ✓ | 44 | | dom | ✓ | 45 | | fileinfo | ✓ | 46 | | intl | ✓ | 47 | | json | ✓ | 48 | | mbstring | ✓ | 49 | | mysqli | ✓ | 50 | | openssl | ✓ | 51 | | pcre | ✓ | 52 | | pdo | ✓ | 53 | | pdo_mysql | ✓ | 54 | | pdo_pgsql | ✓ | 55 | | pdo_sqlite | ✓ | 56 | | pgsql | ✓ | 57 | | redis | ✓ | 58 | | sqlite3 | ✓ | 59 | | tokenizer | ✓ | 60 | | xml | ✓ | 61 | | composer | ✓ | 62 | | pcntl | ✓ | 63 | | openswoole | Octane-only (OpenSwoole) | 64 | | sockets | Octane-only (RoadRunner) | 65 | | swoole | Octane-only (Swoole) | 66 | 67 | > [!TIP] 68 | > You can always view the list of installed extensions from your terminal: 69 | > `docker run --rm ghcr.io/iksaku/laravel-alpine:8.1 php -m` 70 | 71 | ### Installing additional PHP extensions 72 | 73 | Our images come with [mlocati's `install-php-extension`](https://github.com/mlocati/docker-php-extension-installer) 74 | binary available, so you can install additional PHP extensions that you may need: 75 | 76 | ```dockerfile 77 | FROM ghcr.io/iksaku/laravel-alpine:8.1 78 | 79 | RUN install-php-extension \ 80 | ffi \ 81 | vips \ 82 | yaml 83 | ``` 84 | 85 | You can see all available extensions at [`install-php-extension`'s repo](https://github.com/mlocati/docker-php-extension-installer#supported-php-extensions). 86 | 87 | ## Running on Laravel Sail 88 | 89 | As opposed to [Laravel Sail](https://laravel.com/docs/sail), you don't need to import 90 | this repository as a package nor _publish_ our `Dockerfile` assets most of the time, 91 | you can use our images directly in your `docker-compose.yml` file or use them 92 | as a base for your custom `Dockerfile`. 93 | 94 | ### About container permissions 95 | Before proceeding, it is important to know that our images dynamically adjust Group and User 96 | permissions when your container _starts_, while Laravel Sail adjust the Group permissions 97 | _on build_, so the first change you must make is to move the `WWWGROUP` argument to be 98 | part of the `environment` variables of your `docker-compose.yml` file: 99 | ```diff 100 | services: 101 | laravel.test: 102 | build: 103 | context: ./vendor/laravel/sail/runtimes/8.1 104 | - args: 105 | - WWWGROUP: '${WWWGROUP}' 106 | # ... 107 | envrionment: 108 | WWWUSER: '${WWWUSER}' 109 | + WWWGROUP: '${WWWGROUP}' 110 | # ... 111 | ``` 112 | 113 | ### Running base images 114 | To run our images in Laravel Sail, you can replace the `build` option with `image` 115 | in your `docker-compose.yml` file: 116 | ```diff 117 | services: 118 | laravel.test: 119 | - build: 120 | - context: ./vendor/laravel/sail/runtimes/8.1 121 | + image: ghcr.io/iksaku/laravel-alpine:8.1 122 | ``` 123 | 124 | > [!TIP] 125 | > You can always update to the latest version of your chosen image 126 | > using `sail pull` 📥. 127 | 128 | ### Running octane images 129 | Octane images also run in Laravel Sail; you need to change your `image` 130 | reference to specify you want to run octane: 131 | ```diff 132 | services: 133 | laravel.test: 134 | - image: ghcr.io/iksaku/laravel-alpine:8.1 135 | + image: ghcr.io/iksaku/laravel-alpine:8.1-octane-roadrunner 136 | ``` 137 | 138 | ### Running modified images 139 | If you want to customize your image further, then work with the `build` option in your 140 | `docker-compose.yml` file: 141 | 142 | ```sh 143 | super-duper-project 144 | ├── app 145 | ├── bootstrap 146 | │ ... 147 | ├── docker 148 | │   └── super-duper-runtime 149 | │   └── Dockerfile 150 | ├── docker-compose.yml 151 | │ ... 152 | ``` 153 | 154 | ```diff 155 | services: 156 | laravel.test: 157 | build: 158 | - image: ghcr.io/iksaku/laravel-alpine:8.1 159 | + context: ./docker/super-duper-runtime 160 | ``` 161 | 162 | ## Deploying to Production 163 | 164 | Published images can also be used in production, but to run the best 165 | in-class service, it is 100% recommended that you take a look into customizing 166 | the image to your needs, as well as to make sure that the build process is 167 | tailored to your needs. 168 | 169 | The following script can be a good starting point for most projects: 170 | 171 | ```dockerfile 172 | # Based on @fideloper's fly.io laravel template 173 | # https://github.com/superfly/flyctl/blob/94fec0925c75cfe30921f1a4df25fa9cbf2877e9/scanner/templates/laravel/Dockerfile 174 | 175 | FROM ghcr.io/iksaku/laravel-alpine:8.1 176 | 177 | # Run the installed Laravel Scheduler in the background. 178 | # You can replace the default entry by copying your own crontab 179 | # file in container's /var/spool/cron/crontabs/root 180 | RUN crond 181 | 182 | # Copy our project files into the container 183 | COPY . /var/www/html 184 | 185 | # Install composer dependencies 186 | RUN composer install --optimize-autoloader --no-dev 187 | 188 | # Make sure our container has the correct permissions 189 | # to tap into our project storage 190 | RUN mkdir -p storage/logs \ 191 | && chmod -R ug+w /var/www/html/storage \ 192 | && chmod -R 755 /var/www/html 193 | 194 | # (Optional) Allow requests when running behind a proxy (i.e., fly.io) 195 | RUN sed -i 's/protected \$proxies/protected \$proxies = "*"/g' app/Http/Middleware/TrustProxies.php 196 | ``` 197 | 198 | When deploying your code to different environments, commonly `staging` or `production`, a 199 | need to execute specific commands or script arises, mainly when dealing with Database migrations, 200 | linking storage folders, or performing a general app optimization. 201 | 202 | To help out with these tasks, our images support executing scripts inside a `.deploy` 203 | directory before running the Laravel server. 204 | To keep things simple, we do not check for a specific list of environments, instead we 205 | execute deployment scripts when a `RUN_DEPLOY_SCRIPTS` environment variable is available 206 | and has a value of `1`; otherwise, we ignore deployment scripts and jump straight 207 | into server execution. 208 | 209 | > [!IMPORTANT] 210 | > `RUN_DEPLOY_SCRIPTS` is ignored when running in Laravel Sail to prevent unintended 211 | > side effects. 212 | 213 | If you have multiple scripts, you can number them in the order they should be executed: 214 | 215 | ``` 216 | my-laravel-app/ 217 | ├── .deploy/ 218 | │ ├── 01_migrate_database.sh 219 | │ ├── 02_optimize_application.sh 220 | ├── app/ 221 | ├── bootstrap/ 222 | │ ... 223 | ``` 224 | 225 | > [!IMPORTANT] 226 | > Deploy scripts should be suffixed with the `.sh` extension and will be run using 227 | > Alpine's `sh` shell as we do not have `bash`. 228 | 229 | ### Deploying to Fly.io 230 | 231 | It is recommended that when you deploy your code to a production environment you 232 | run the following commands: 233 | * `php artisan optimize` 234 | * `php artisan migrate --force` 235 | 236 | It is easy to add such commands to a before/after deploy hook on most cases, but 237 | when deploying to [Fly.io](https://fly.io/) it is rather troublesome that you can't 238 | execute these commands in your `Dockerfile`, as it has no access to envrionment variables 239 | during build. 240 | 241 | Another way to execute deployment commands when deploying to Fly.io is by creating a 242 | `on_deploy.sh` script on your app's root directory, and then calling this script from 243 | your `fly.toml` file using the [`deploy.release_command`](https://fly.io/docs/reference/configuration/#run-one-off-commands-before-releasing-a-deployment) 244 | property: 245 | 246 | ```sh 247 | #!/usr/bin/env sh 248 | 249 | php artisan migrate --force 250 | ``` 251 | 252 | ```toml 253 | # fly.toml 254 | 255 | # ... 256 | 257 | [deploy] 258 | release_command = "sh ./on_deploy.sh" 259 | 260 | # ... 261 | ``` 262 | 263 | When using this method, Fly will spawn a _temporary_ (or _ephemeral_) virtual machine with 264 | access to your app's environment to execute this script, and later on, it will destroy such 265 | VM and queue another VM to take over the given environment. This means that any file-based 266 | changes done while executing the `release_command` will not be persisted, so only procedures 267 | that ping other services not in the VM, like Database migrations, will persist. 268 | 269 | Use the above mentioned `.deploy` directory if you are planning to execute commands like 270 | `artisan storage:link` or `artisan optimize`. 271 | 272 | > [!NOTE] 273 | > The image's default entrypoint will manage this command, making 274 | > your script execution to be done by the `laravel` user, which is the 275 | > default one configured with all app permissions in your container. 276 | 277 | #### Running multiple processes 278 | Fly's support for [multiple process groups](https://fly.io/docs/apps/processes/) allow to run multiple 279 | commands (or processes) in separate machines. This is useful when you want also run a queue worker or 280 | a scheduler: 281 | 282 | ```toml 283 | # fly.toml 284 | 285 | # ... 286 | 287 | [processes] 288 | # This is the default process group running our Laravel app 289 | app = "" 290 | # Run the Laravel scheduler using Alpine's crond in the foreground (for logs) 291 | scheduler = "crond -f" 292 | # Run the Laravel queue worker 293 | worker = "php artisan queue:work" 294 | ``` 295 | 296 | For more information on how process groups work and how you can scale them, check out: 297 | * [Run Multiple Process Groups in an App](https://fly.io/docs/apps/processes/). 298 | * [Scale Process Groups](https://fly.io/docs/apps/scale-count/#scale-by-process-group). 299 | 300 | ## Credits 301 | 302 | As this intends to be an alternative version of Laravel Sail images, most of the credit 303 | goes to the [@laravel](https://laravel.com/team) team itself for building and 304 | maintaining Laravel Sail for us 💖. 305 | 306 | Also, much inspiration (and snippets) come from [@fideloper](https://github.com/fideloper)'s 307 | public work on [Fly.io's Laravel Template](https://github.com/superfly/flyctl) 🔮. 308 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Image Builder 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**.md' 9 | pull_request: 10 | paths-ignore: 11 | - '**.md' 12 | schedule: 13 | - cron: '0 7 * * 1' 14 | 15 | jobs: 16 | build: 17 | name: php-${{ matrix.php }}-${{ matrix.variant }} 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 20 20 | strategy: 21 | matrix: 22 | php: 23 | - '8.0' 24 | - '8.1' 25 | - '8.2' 26 | - '8.3' 27 | variant: 28 | - cli 29 | - zts 30 | steps: 31 | - name: Checkout Code 32 | uses: actions/checkout@v4 33 | 34 | - name: Check if PHP version is up to date 35 | id: php 36 | uses: ./.github/actions/runtime-version-compare 37 | with: 38 | runtime-name: PHP ${{ matrix.php }} (${{ matrix.variant }}) 39 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-${{ matrix.variant }} 40 | upstream-image: php:${{ matrix.php }}-${{ matrix.variant }}-alpine 41 | version-command: php -r 'echo PHP_VERSION;' 42 | 43 | - name: Get Docker Context 44 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' }} 45 | run: echo "DOCKER_BUILD_CONTEXT=$(echo ${{ matrix.php }} | sed -r 's/^([0-9]+)\..*$/\1.x/')" >> $GITHUB_ENV 46 | 47 | - name: Generate Image Tags 48 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' }} 49 | id: tags 50 | uses: ./.github/actions/generate-image-tags 51 | with: 52 | php: ${{ steps.php.outputs.version }}:${{ matrix.variant }} 53 | 54 | - name: Generate Image Metadata 55 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' }} 56 | id: meta 57 | uses: docker/metadata-action@v5 58 | with: 59 | images: ghcr.io/iksaku/laravel-alpine 60 | tags: ${{ fromJSON(steps.tags.outputs.tags) }} 61 | 62 | - name: Set up QEMU 63 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' }} 64 | uses: docker/setup-qemu-action@v3 65 | 66 | - name: Set up Docker Buildx 67 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' }} 68 | uses: docker/setup-buildx-action@v3 69 | 70 | - name: Login to GitHub Container Registry 71 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' }} 72 | uses: docker/login-action@v3 73 | with: 74 | registry: ghcr.io 75 | username: ${{ github.actor }} 76 | password: ${{ secrets.GITHUB_TOKEN }} 77 | 78 | - name: Build and push 79 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' }} 80 | uses: docker/build-push-action@v5 81 | with: 82 | context: ${{ env.DOCKER_BUILD_CONTEXT }} 83 | build-args: | 84 | PHP_VERSION=${{ matrix.php }} 85 | VARIANT=${{ matrix.variant }} 86 | platforms: linux/amd64,linux/arm64 87 | push: ${{ contains(fromJSON('["push", "schedule"]'), github.event_name) && github.ref == 'refs/heads/main' }} 88 | tags: ${{ steps.meta.outputs.tags }} 89 | 90 | build-octane-frankenphp: 91 | name: php-${{ matrix.php }}-octane-frankenphp 92 | runs-on: ubuntu-latest 93 | timeout-minutes: 40 94 | needs: build 95 | strategy: 96 | matrix: 97 | php: 98 | - '8.2' 99 | - '8.3' 100 | steps: 101 | - name: Checkout Code 102 | uses: actions/checkout@v4 103 | 104 | - name: Check if PHP version is up to date 105 | id: php 106 | uses: ./.github/actions/runtime-version-compare 107 | with: 108 | runtime-name: PHP ${{ matrix.php }} 109 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-frankenphp 110 | upstream-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }} 111 | version-command: php -r 'echo PHP_VERSION;' 112 | - name: Check if FrankenPHP version is up to date 113 | id: frankenphp 114 | uses: ./.github/actions/runtime-version-compare 115 | with: 116 | runtime-name: Octane FrankenPHP 117 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-frankenphp 118 | upstream-image: dunglas/frankenphp:latest-php${{ matrix.php }}-alpine 119 | version-command: frankenphp -v | sed -r 's/FrankenPHP v([0-9\.]+) .*/\1/' 120 | 121 | - name: Get Docker Context 122 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.frankenphp.outputs.outdated == 'true' }} 123 | run: echo "DOCKER_BUILD_CONTEXT=$(echo ${{ matrix.php }} | sed -r 's/^([0-9]+)\..*$/\1.x/')" >> $GITHUB_ENV 124 | 125 | - name: Generate Image Tags 126 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.frankenphp.outputs.outdated == 'true' }} 127 | id: tags 128 | uses: ./.github/actions/generate-image-tags 129 | with: 130 | php: ${{ steps.php.outputs.version }} 131 | octane-runtime: frankenphp:${{ steps.frankenphp.outputs.version }} 132 | 133 | - name: Generate Image Metadata 134 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.frankenphp.outputs.outdated == 'true' }} 135 | id: meta 136 | uses: docker/metadata-action@v5 137 | with: 138 | images: ghcr.io/iksaku/laravel-alpine 139 | tags: ${{ fromJSON(steps.tags.outputs.tags) }} 140 | 141 | - name: Set up QEMU 142 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.frankenphp.outputs.outdated == 'true' }} 143 | uses: docker/setup-qemu-action@v3 144 | 145 | - name: Set up Docker Buildx 146 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.frankenphp.outputs.outdated == 'true' }} 147 | uses: docker/setup-buildx-action@v3 148 | 149 | - name: Login to GitHub Container Registry 150 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.frankenphp.outputs.outdated == 'true' }} 151 | uses: docker/login-action@v3 152 | with: 153 | registry: ghcr.io 154 | username: ${{ github.actor }} 155 | password: ${{ secrets.GITHUB_TOKEN }} 156 | 157 | - name: Build and push 158 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.frankenphp.outputs.outdated == 'true' }} 159 | uses: docker/build-push-action@v5 160 | with: 161 | context: ${{ env.DOCKER_BUILD_CONTEXT }} 162 | file: ${{ env.DOCKER_BUILD_CONTEXT }}/Dockerfile.octane.frankenphp 163 | build-args: | 164 | PHP_VERSION=${{ matrix.php }} 165 | platforms: linux/amd64,linux/arm64 166 | push: ${{ contains(fromJSON('["push", "schedule"]'), github.event_name) && github.ref == 'refs/heads/main' }} 167 | tags: ${{ steps.meta.outputs.tags }} 168 | 169 | build-octane-openswoole: 170 | name: php-${{ matrix.php }}-octane-openswoole 171 | runs-on: ubuntu-latest 172 | timeout-minutes: 40 173 | needs: build 174 | strategy: 175 | matrix: 176 | php: 177 | - '8.0' 178 | - '8.1' 179 | - '8.2' 180 | - '8.3' 181 | steps: 182 | - name: Checkout Code 183 | uses: actions/checkout@v4 184 | 185 | - name: Check if PHP version is up to date 186 | id: php 187 | uses: ./.github/actions/runtime-version-compare 188 | with: 189 | runtime-name: PHP ${{ matrix.php }} 190 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-openswoole 191 | upstream-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }} 192 | version-command: php -r 'echo PHP_VERSION;' 193 | - name: Check if OpenSwoole version is up to date 194 | id: openswoole 195 | uses: ./.github/actions/runtime-version-compare 196 | with: 197 | runtime-name: Octane OpenSwoole 198 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-openswoole 199 | upstream-image: openswoole/swoole:php${{ matrix.php }}-alpine 200 | version-command: php -r 'echo OPENSWOOLE_VERSION;' 201 | 202 | - name: Get Docker Context 203 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.openswoole.outputs.outdated == 'true' }} 204 | run: echo "DOCKER_BUILD_CONTEXT=$(echo ${{ matrix.php }} | sed -r 's/^([0-9]+)\..*$/\1.x/')" >> $GITHUB_ENV 205 | 206 | - name: Generate Image Tags 207 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.openswoole.outputs.outdated == 'true' }} 208 | id: tags 209 | uses: ./.github/actions/generate-image-tags 210 | with: 211 | php: ${{ steps.php.outputs.version }} 212 | octane-runtime: openswoole:${{ steps.openswoole.outputs.version }} 213 | 214 | - name: Generate Image Metadata 215 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.openswoole.outputs.outdated == 'true' }} 216 | id: meta 217 | uses: docker/metadata-action@v5 218 | with: 219 | images: ghcr.io/iksaku/laravel-alpine 220 | tags: ${{ fromJSON(steps.tags.outputs.tags) }} 221 | 222 | - name: Set up QEMU 223 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.openswoole.outputs.outdated == 'true' }} 224 | uses: docker/setup-qemu-action@v3 225 | 226 | - name: Set up Docker Buildx 227 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.openswoole.outputs.outdated == 'true' }} 228 | uses: docker/setup-buildx-action@v3 229 | 230 | - name: Login to GitHub Container Registry 231 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.openswoole.outputs.outdated == 'true' }} 232 | uses: docker/login-action@v3 233 | with: 234 | registry: ghcr.io 235 | username: ${{ github.actor }} 236 | password: ${{ secrets.GITHUB_TOKEN }} 237 | 238 | - name: Build and push 239 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.openswoole.outputs.outdated == 'true' }} 240 | uses: docker/build-push-action@v5 241 | with: 242 | context: ${{ env.DOCKER_BUILD_CONTEXT }} 243 | file: ${{ env.DOCKER_BUILD_CONTEXT }}/Dockerfile.octane.openswoole 244 | build-args: | 245 | PHP_VERSION=${{ matrix.php }} 246 | platforms: linux/amd64,linux/arm64 247 | push: ${{ contains(fromJSON('["push", "schedule"]'), github.event_name) && github.ref == 'refs/heads/main' }} 248 | tags: ${{ steps.meta.outputs.tags }} 249 | 250 | build-octane-roadrunner: 251 | name: php-${{ matrix.php }}-octane-roadrunner 252 | runs-on: ubuntu-latest 253 | timeout-minutes: 5 254 | needs: build 255 | strategy: 256 | matrix: 257 | php: 258 | - '8.0' 259 | - '8.1' 260 | - '8.2' 261 | - '8.3' 262 | steps: 263 | - name: Checkout Code 264 | uses: actions/checkout@v4 265 | 266 | - name: Check if PHP version is up to date 267 | id: php 268 | uses: ./.github/actions/runtime-version-compare 269 | with: 270 | runtime-name: PHP ${{ matrix.php }} 271 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-roadrunner 272 | upstream-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }} 273 | version-command: php -r 'echo PHP_VERSION;' 274 | - name: Check if RoadRunner version is up to date 275 | id: roadrunner 276 | uses: ./.github/actions/runtime-version-compare 277 | with: 278 | runtime-name: RoadRunner 279 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-roadrunner rr 280 | upstream-image: ghcr.io/roadrunner-server/roadrunner:latest 281 | version-command: -v | sed -r 's/^rr version ([0-9\.]+) .*/\1/' 282 | 283 | - name: Get Docker Context 284 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.roadrunner.outputs.outdated == 'true' }} 285 | run: echo "DOCKER_BUILD_CONTEXT=$(echo ${{ matrix.php }} | sed -r 's/^([0-9]+)\..*$/\1.x/')" >> $GITHUB_ENV 286 | 287 | - name: Generate Image Tags 288 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.roadrunner.outputs.outdated == 'true' }} 289 | id: tags 290 | uses: ./.github/actions/generate-image-tags 291 | with: 292 | php: ${{ steps.php.outputs.version }} 293 | octane-runtime: roadrunner:${{ steps.roadrunner.outputs.version }} 294 | 295 | - name: Generate Image Metadata 296 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.roadrunner.outputs.outdated == 'true' }} 297 | id: meta 298 | uses: docker/metadata-action@v5 299 | with: 300 | images: ghcr.io/iksaku/laravel-alpine 301 | tags: ${{ fromJSON(steps.tags.outputs.tags) }} 302 | 303 | - name: Set up QEMU 304 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.roadrunner.outputs.outdated == 'true' }} 305 | uses: docker/setup-qemu-action@v3 306 | 307 | - name: Set up Docker Buildx 308 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.roadrunner.outputs.outdated == 'true' }} 309 | uses: docker/setup-buildx-action@v3 310 | 311 | - name: Login to GitHub Container Registry 312 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.roadrunner.outputs.outdated == 'true' }} 313 | uses: docker/login-action@v3 314 | with: 315 | registry: ghcr.io 316 | username: ${{ github.actor }} 317 | password: ${{ secrets.GITHUB_TOKEN }} 318 | 319 | - name: Build and push 320 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.roadrunner.outputs.outdated == 'true' }} 321 | uses: docker/build-push-action@v5 322 | with: 323 | context: ${{ env.DOCKER_BUILD_CONTEXT }} 324 | file: ${{ env.DOCKER_BUILD_CONTEXT }}/Dockerfile.octane.roadrunner 325 | build-args: | 326 | PHP_VERSION=${{ matrix.php }} 327 | platforms: linux/amd64,linux/arm64 328 | push: ${{ contains(fromJSON('["push", "schedule"]'), github.event_name) && github.ref == 'refs/heads/main' }} 329 | tags: ${{ steps.meta.outputs.tags }} 330 | 331 | build-octane-swoole: 332 | name: php-${{ matrix.php }}-octane-swoole 333 | runs-on: ubuntu-latest 334 | timeout-minutes: 40 335 | needs: build 336 | strategy: 337 | matrix: 338 | php: 339 | - '8.0' 340 | - '8.1' 341 | - '8.2' 342 | - '8.3' 343 | steps: 344 | - name: Checkout Code 345 | uses: actions/checkout@v4 346 | 347 | - name: Check if PHP version is up to date 348 | id: php 349 | uses: ./.github/actions/runtime-version-compare 350 | with: 351 | runtime-name: PHP ${{ matrix.php }} 352 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-swoole 353 | upstream-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }} 354 | version-command: php -r 'echo PHP_VERSION;' 355 | - name: Check if Swoole version is up to date 356 | id: swoole 357 | uses: ./.github/actions/runtime-version-compare 358 | with: 359 | runtime-name: Octane Swoole 360 | base-image: ghcr.io/iksaku/laravel-alpine:${{ matrix.php }}-octane-swoole 361 | upstream-image: phpswoole/swoole:php${{ matrix.php }}-alpine 362 | version-command: php -r 'echo swoole_version();' 363 | 364 | - name: Get Docker Context 365 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.swoole.outputs.outdated == 'true' }} 366 | run: echo "DOCKER_BUILD_CONTEXT=$(echo ${{ matrix.php }} | sed -r 's/^([0-9]+)\..*$/\1.x/')" >> $GITHUB_ENV 367 | 368 | - name: Generate Image Tags 369 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.swoole.outputs.outdated == 'true' }} 370 | id: tags 371 | uses: ./.github/actions/generate-image-tags 372 | with: 373 | php: ${{ steps.php.outputs.version }} 374 | octane-runtime: swoole:${{ steps.swoole.outputs.version }} 375 | 376 | - name: Generate Image Metadata 377 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.swoole.outputs.outdated == 'true' }} 378 | id: meta 379 | uses: docker/metadata-action@v5 380 | with: 381 | images: ghcr.io/iksaku/laravel-alpine 382 | tags: ${{ fromJSON(steps.tags.outputs.tags) }} 383 | 384 | - name: Set up QEMU 385 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.swoole.outputs.outdated == 'true' }} 386 | uses: docker/setup-qemu-action@v3 387 | 388 | - name: Set up Docker Buildx 389 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.swoole.outputs.outdated == 'true' }} 390 | uses: docker/setup-buildx-action@v3 391 | 392 | - name: Login to GitHub Container Registry 393 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.swoole.outputs.outdated == 'true' }} 394 | uses: docker/login-action@v3 395 | with: 396 | registry: ghcr.io 397 | username: ${{ github.actor }} 398 | password: ${{ secrets.GITHUB_TOKEN }} 399 | 400 | - name: Build and push 401 | if: ${{ github.event_name != 'schedule' || steps.php.outputs.outdated == 'true' || steps.swoole.outputs.outdated == 'true' }} 402 | uses: docker/build-push-action@v5 403 | with: 404 | context: ${{ env.DOCKER_BUILD_CONTEXT }} 405 | file: ${{ env.DOCKER_BUILD_CONTEXT }}/Dockerfile.octane.swoole 406 | build-args: | 407 | PHP_VERSION=${{ matrix.php }} 408 | platforms: linux/amd64,linux/arm64 409 | push: ${{ contains(fromJSON('["push", "schedule"]'), github.event_name) && github.ref == 'refs/heads/main' }} 410 | tags: ${{ steps.meta.outputs.tags }} 411 | --------------------------------------------------------------------------------