diff --git a/.github/workflows/build-public.yml b/.github/workflows/build-public.yml new file mode 100644 index 00000000..e519fe74 --- /dev/null +++ b/.github/workflows/build-public.yml @@ -0,0 +1,61 @@ +on: + push: + +name: Build +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: "Check out code" + uses: actions/checkout@v4 + + - name: "Install dependencies" + uses: php-actions/composer@v6 + if: steps.cache-vendor.outputs.cache-hit != 'true' # Skip if cache hit + with: + command: install + dev: no + only_args: --no-ansi --no-interaction --no-scripts --prefer-dist --ignore-platform-reqs --classmap-authoritative + php_version: 8.3 + + - name: "Use Node.js" + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: "Install npm dependencies" + run: npm ci + + - name: "Build" + run: npm run build + + - name: "Login to GitHub Container Registry" + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: "Docker meta" + id: "meta" + uses: docker/metadata-action@v5 + with: + images: solidtime/solidtime + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: "Set up Docker Buildx" + uses: docker/setup-buildx-action@v3 + + - name: "Build and push" + uses: docker/build-push-action@v5 + with: + context: . + file: docker/prod/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/README.md b/README.md index bfe85a6e..194788ff 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ System Requirements: * Docker -* PHP 8.2 +* PHP 8.3 * Composer ```bash diff --git a/composer.json b/composer.json index d5514d3b..132a65f1 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "korridor/laravel-model-validation-rules": "^3.0", "laravel/framework": "^11.0", "laravel/jetstream": "^5.0", + "laravel/octane": "^2.3", "laravel/passport": "^12.0", "laravel/tinker": "^2.8", "nwidart/laravel-modules": "^11.0.3", diff --git a/composer.lock b/composer.lock index 27ac4f3f..991c087e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1481cd817f30abf0429363c9ad89b5dd", + "content-hash": "4296162fd8b0dc950f30b50ee266bbb1", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -3496,6 +3496,91 @@ }, "time": "2024-02-28T15:07:15+00:00" }, + { + "name": "laminas/laminas-diactoros", + "version": "3.3.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45", + "reference": "74cfb9a7522ffd2a161d1ebe10db2fc2abb9df45", + "shasum": "" + }, + "require": { + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", + "psr/http-factory": "^1.0.2", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "psr/http-factory-implementation": "^1.1 || ^2.0", + "psr/http-message-implementation": "^1.1 || ^2.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.9.0", + "laminas/laminas-coding-standard": "~2.5.0", + "php-http/psr7-integration-tests": "^1.3", + "phpunit/phpunit": "^9.6.16", + "psalm/plugin-phpunit": "^0.18.4", + "vimeo/psalm": "^5.22.1" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2024-02-16T16:06:16+00:00" + }, { "name": "laravel/fortify", "version": "v1.21.1", @@ -3833,6 +3918,95 @@ }, "time": "2024-03-19T20:10:08+00:00" }, + { + "name": "laravel/octane", + "version": "v2.3.7", + "source": { + "type": "git", + "url": "https://github.com/laravel/octane.git", + "reference": "4890de0c6a79bd163a52b99b91c727cf0020cc2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/octane/zipball/4890de0c6a79bd163a52b99b91c727cf0020cc2c", + "reference": "4890de0c6a79bd163a52b99b91c727cf0020cc2c", + "shasum": "" + }, + "require": { + "laminas/laminas-diactoros": "^3.0", + "laravel/framework": "^10.10.1|^11.0", + "laravel/serializable-closure": "^1.3.0", + "nesbot/carbon": "^2.66.0|^3.0", + "php": "^8.1.0", + "symfony/console": "^6.0|^7.0", + "symfony/psr-http-message-bridge": "^2.2.0|^6.4|^7.0" + }, + "conflict": { + "spiral/roadrunner": "<2023.1.0", + "spiral/roadrunner-cli": "<2.6.0", + "spiral/roadrunner-http": "<3.3.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.6.1", + "inertiajs/inertia-laravel": "^0.6.9|^1.0", + "laravel/scout": "^10.2.1", + "laravel/socialite": "^5.6.1", + "livewire/livewire": "^2.12.3|^3.0", + "mockery/mockery": "^1.5.1", + "nunomaduro/collision": "^6.4.0|^7.5.2|^8.0", + "orchestra/testbench": "^8.5.2|^9.0", + "phpstan/phpstan": "^1.10.15", + "phpunit/phpunit": "^10.4", + "spiral/roadrunner-cli": "^2.6.0", + "spiral/roadrunner-http": "^3.3.0" + }, + "bin": [ + "bin/roadrunner-worker", + "bin/swoole-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Octane\\OctaneServiceProvider" + ], + "aliases": { + "Octane": "Laravel\\Octane\\Facades\\Octane" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Octane\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Supercharge your Laravel application's performance.", + "keywords": [ + "frankenphp", + "laravel", + "octane", + "roadrunner", + "swoole" + ], + "support": { + "issues": "https://github.com/laravel/octane/issues", + "source": "https://github.com/laravel/octane" + }, + "time": "2024-04-01T13:36:49+00:00" + }, { "name": "laravel/passport", "version": "v12.0.2", diff --git a/config/octane.php b/config/octane.php new file mode 100644 index 00000000..51b66638 --- /dev/null +++ b/config/octane.php @@ -0,0 +1,240 @@ + env('OCTANE_SERVER', 'swoole'), + + /* + |-------------------------------------------------------------------------- + | Force HTTPS + |-------------------------------------------------------------------------- + | + | When this configuration value is set to "true", Octane will inform the + | framework that all absolute links must be generated using the HTTPS + | protocol. Otherwise your links may be generated using plain HTTP. + | + */ + + 'https' => env('OCTANE_HTTPS', false), + + /* + |-------------------------------------------------------------------------- + | Octane Listeners + |-------------------------------------------------------------------------- + | + | All of the event listeners for Octane's events are defined below. These + | listeners are responsible for resetting your application's state for + | the next request. You may even add your own listeners to the list. + | + */ + + 'listeners' => [ + WorkerStarting::class => [ + EnsureUploadedFilesAreValid::class, + EnsureUploadedFilesCanBeMoved::class, + ], + + RequestReceived::class => [ + ...Octane::prepareApplicationForNextOperation(), + ...Octane::prepareApplicationForNextRequest(), + // + ], + + RequestHandled::class => [ + // + ], + + RequestTerminated::class => [ + // FlushUploadedFiles::class, + ], + + TaskReceived::class => [ + ...Octane::prepareApplicationForNextOperation(), + // + ], + + TaskTerminated::class => [ + // + ], + + TickReceived::class => [ + ...Octane::prepareApplicationForNextOperation(), + // + ], + + TickTerminated::class => [ + // + ], + + OperationTerminated::class => [ + FlushOnce::class, + FlushTemporaryContainerInstances::class, + // DisconnectFromDatabases::class, + // CollectGarbage::class, + ], + + WorkerErrorOccurred::class => [ + ReportException::class, + StopWorkerIfNecessary::class, + ], + + WorkerStopping::class => [ + CloseMonologHandlers::class, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Warm / Flush Bindings + |-------------------------------------------------------------------------- + | + | The bindings listed below will either be pre-warmed when a worker boots + | or they will be flushed before every new request. Flushing a binding + | will force the container to resolve that binding again when asked. + | + */ + + 'warm' => [ + ...Octane::defaultServicesToWarm(), + ], + + 'flush' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Octane Swoole Tables + |-------------------------------------------------------------------------- + | + | While using Swoole, you may define additional tables as required by the + | application. These tables can be used to store data that needs to be + | quickly accessed by other workers on the particular Swoole server. + | + */ + + 'tables' => [ + 'example:1000' => [ + 'name' => 'string:1000', + 'votes' => 'int', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Octane Swoole Cache Table + |-------------------------------------------------------------------------- + | + | While using Swoole, you may leverage the Octane cache, which is powered + | by a Swoole table. You may set the maximum number of rows as well as + | the number of bytes per row using the configuration options below. + | + */ + + 'cache' => [ + 'rows' => 1000, + 'bytes' => 10000, + ], + + /* + |-------------------------------------------------------------------------- + | File Watching + |-------------------------------------------------------------------------- + | + | The following list of files and directories will be watched when using + | the --watch option offered by Octane. If any of the directories and + | files are changed, Octane will automatically reload your workers. + | + */ + + 'watch' => [ + 'app', + 'bootstrap', + 'config', + 'database/**/*.php', + 'public/**/*.php', + 'resources/**/*.php', + 'routes', + 'composer.lock', + '.env', + ], + + /* + |-------------------------------------------------------------------------- + | Garbage Collection Threshold + |-------------------------------------------------------------------------- + | + | When executing long-lived PHP scripts such as Octane, memory can build + | up before being cleared by PHP. You can force Octane to run garbage + | collection if your application consumes this amount of megabytes. + | + */ + + 'garbage' => 50, + + /* + |-------------------------------------------------------------------------- + | Maximum Execution Time + |-------------------------------------------------------------------------- + | + | The following setting configures the maximum execution time for requests + | being handled by Octane. You may set this value to 0 to indicate that + | there isn't a specific time limit on Octane request execution time. + | + */ + + 'max_execution_time' => 30, + + /** + * Custom swoole config + * + * @source https://github.com/exaco/laravel-octane-dockerfile?tab=readme-ov-file#recommended-swoole-options-in-octanephp + */ + 'swoole' => [ + 'options' => [ + 'http_compression' => true, + 'http_compression_level' => 6, // 1 - 9 + 'compression_min_length' => 20, + 'package_max_length' => 20 * 1024 * 1024, // 20MB + 'open_http2_protocol' => true, + 'document_root' => public_path(), + 'enable_static_handler' => true, + ], + ], + +]; diff --git a/docker-compose.yml b/docker-compose.yml index 73e7452d..b8faeca6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: laravel.test: build: - context: ./docker/8.3 + context: ./docker/local/8.3 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' @@ -53,7 +53,7 @@ services: POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' volumes: - 'sail-pgsql:/var/lib/postgresql/data' - - './docker/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' + - './docker/local/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' networks: - sail healthcheck: @@ -76,7 +76,7 @@ services: POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' volumes: - 'sail-pgsql-test:/var/lib/postgresql/data' - - './docker/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' + - './docker/local/pgsql/create-testing-database.sql:/docker-entrypoint-initdb.d/10-create-testing-database.sql' networks: - sail healthcheck: diff --git a/docker/8.3/Dockerfile b/docker/local/8.3/Dockerfile similarity index 100% rename from docker/8.3/Dockerfile rename to docker/local/8.3/Dockerfile diff --git a/docker/8.3/php.ini b/docker/local/8.3/php.ini similarity index 100% rename from docker/8.3/php.ini rename to docker/local/8.3/php.ini diff --git a/docker/8.3/start-container b/docker/local/8.3/start-container similarity index 100% rename from docker/8.3/start-container rename to docker/local/8.3/start-container diff --git a/docker/8.3/supervisord.conf b/docker/local/8.3/supervisord.conf similarity index 100% rename from docker/8.3/supervisord.conf rename to docker/local/8.3/supervisord.conf diff --git a/docker/pgsql/create-testing-database.sql b/docker/local/pgsql/create-testing-database.sql similarity index 100% rename from docker/pgsql/create-testing-database.sql rename to docker/local/pgsql/create-testing-database.sql diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile new file mode 100644 index 00000000..2ab5104e --- /dev/null +++ b/docker/prod/Dockerfile @@ -0,0 +1,121 @@ +# Accepted values: 8.3 - 8.2 +ARG PHP_VERSION=8.3 + +ARG DOCKER_FILES_BASE_PATH="docker/prod" + +ARG COMPOSER_VERSION=latest + +########################################### + +FROM composer:${COMPOSER_VERSION} AS vendor + +FROM php:${PHP_VERSION}-cli-bookworm AS base + +LABEL maintainer="solidtime " +LABEL org.opencontainers.image.title="solidtime" +LABEL org.opencontainers.image.description="solidtime is a modern open source timetracker for Freelancers and Agencies" +LABEL org.opencontainers.image.source="https://github.com/solidtime-io/solidtime" +LABEL org.opencontainers.image.licenses="AGPL" + +ARG WWWUSER=1000 +ARG WWWGROUP=1000 +ARG TZ=UTC + +ENV DEBIAN_FRONTEND=noninteractive \ + TERM=xterm-color \ + WITH_HORIZON=false \ + WITH_SCHEDULER=false \ + OCTANE_SERVER=swoole \ + USER=octane \ + ROOT=/var/www/html \ + COMPOSER_FUND=0 \ + COMPOSER_MAX_PARALLEL_HTTP=24 + +WORKDIR ${ROOT} + +SHELL ["/bin/bash", "-eou", "pipefail", "-c"] + +RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ + && echo ${TZ} > /etc/timezone + +ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ + +RUN apt-get update; \ + apt-get upgrade -yqq; \ + apt-get install -yqq --no-install-recommends --show-progress \ + apt-utils \ + curl \ + wget \ + nano \ + ncdu \ + ca-certificates \ + supervisor \ + libsodium-dev \ + # Install PHP extensions + && install-php-extensions \ + bz2 \ + pcntl \ + mbstring \ + bcmath \ + sockets \ + pgsql \ + pdo_pgsql \ + opcache \ + exif \ + pdo_mysql \ + zip \ + intl \ + gd \ + redis \ + rdkafka \ + memcached \ + igbinary \ + ldap \ + swoole \ + && apt-get -y autoremove \ + && apt-get clean \ + && docker-php-source delete \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && rm /var/log/lastlog /var/log/faillog + +RUN wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64" \ + -O /usr/bin/supercronic \ + && chmod +x /usr/bin/supercronic \ + && mkdir -p /etc/supercronic \ + && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --verbose --no-interaction" > /etc/supercronic/laravel + +RUN userdel --remove --force www-data \ + && groupadd --force -g ${WWWGROUP} ${USER} \ + && useradd -ms /bin/bash --no-log-init --no-user-group -g ${WWWGROUP} -u ${WWWUSER} ${USER} + +RUN chown -R ${USER}:${USER} ${ROOT} /var/{log,run} \ + && chmod -R a+rw /var/{log,run} + +RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini + +USER ${USER} + +COPY --chown=${USER}:${USER} --from=vendor /usr/bin/composer /usr/bin/composer + +COPY --chown=${USER}:${USER} . . + +RUN mkdir -p \ + storage/framework/{sessions,views,cache,testing} \ + storage/logs \ + bootstrap/cache && chmod -R a+rw storage + +COPY --chown=${USER}:${USER} docker/prod/deployment/supervisord.*.conf /etc/supervisor/conf.d/ +COPY --chown=${USER}:${USER} docker/prod/deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini +COPY --chown=${USER}:${USER} docker/prod/deployment/start-container /usr/local/bin/start-container + +RUN php artisan storage:link + +RUN chmod +x /usr/local/bin/start-container + +RUN cat docker/prod/deployment/utilities.sh >> ~/.bashrc + +EXPOSE 8000 + +ENTRYPOINT ["start-container"] + +HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD php artisan octane:status || exit 1 diff --git a/docker/prod/LICENSE b/docker/prod/LICENSE new file mode 100644 index 00000000..96aa7dde --- /dev/null +++ b/docker/prod/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exa Company + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docker/prod/README.md b/docker/prod/README.md new file mode 100644 index 00000000..0adef933 --- /dev/null +++ b/docker/prod/README.md @@ -0,0 +1,128 @@ +# Laravel Octane Dockerfile +License +GitHub release (latest by date) +GitHub closed pull requests +GitHub Workflow Status +GitHub Workflow Status +GitHub Workflow Status + + +Production-ready Dockerfiles for [Laravel Octane](https://github.com/laravel/octane) +powered web services and microservices. + +The Docker configuration provides the following setup: + +- PHP 8.2 and 8.3 official Debian-based images +- Preconfigured JIT compiler and OPcache + +## Container modes + +You can run the Docker container in different modes: + +| Mode | `CONTAINER_MODE` | HTTP server | +| --------------------- | ---------------- | ------------------- | +| HTTP Server (default) | `http` | FrankenPHP / Swoole / RoadRunner | +| Horizon | `horizon` | - | +| Scheduler | `scheduler` | - | +| Worker | `worker` | - | + +## Usage + +### Building Docker image +1. Clone this repository: +``` +git clone --depth 1 git@github.com:exaco/laravel-octane-dockerfile.git +``` +2. Copy cloned directory content including `deployment` directory, `Dockerfile`, and `.dockerignore` into your Octane powered Laravel project +3. Change the directory to your Laravel project +4. Build your image: +``` +docker build -t : -f .Dockerfile . +``` +### Running Docker container + +```bash +# HTTP mode +docker run -p :80 --rm : + +# Horizon mode +docker run -e CONTAINER_MODE=horizon --rm : + +# Scheduler mode +docker run -e CONTAINER_MODE=scheduler --rm : + +# HTTP mode with Horizon +docker run -e WITH_HORIZON=true -p :80 --rm : + +# HTTP mode with Scheduler +docker run -e WITH_SCHEDULER=true -p :80 --rm : + +# HTTP mode with Scheduler and Horizon +docker run -e WITH_SCHEDULER=true -e WITH_HORIZON=true -p :80 --rm : + +# Worker mode +docker run -e CONTAINER_MODE=worker -e WORKER_COMMAND="php /var/www/html/artisan foo:bar" --rm : + +# Running a single command +docker run --rm : php artisan about +``` + +## Configuration + +### Recommended `Swoole` options in `octane.php` + +```php +// config/octane.php + +return [ + 'swoole' => [ + 'options' => [ + 'http_compression' => true, + 'http_compression_level' => 6, // 1 - 9 + 'compression_min_length' => 20, + 'package_max_length' => 20 * 1024 * 1024, // 20MB + 'open_http2_protocol' => true, + 'document_root' => public_path(), + 'enable_static_handler' => true, + ] + ] +]; +``` + +## Utilities + +Also, some useful Bash functions and aliases are added in `utilities.sh` that maybe help. + +## Notes + +- Laravel Octane logs request information only in the `local` environment. +- Please be aware of `.dockerignore` content + +## ToDo +- [x] Add support for PHP 8.3 +- [x] Add support for worker mode +- [ ] Build assets with Bun +- [ ] Create standalone and self-executable app +- [x] Add support for Horizon +- [x] Add support for RoadRunner +- [x] Add support for FrankenPHP +- [x] Add support for the full-stack apps (Front-end assets) +- [ ] Add support `testing` environment and CI +- [x] Add support for the Laravel scheduler +- [ ] Add support for Laravel Dusk +- [x] Support more PHP extensions +- [x] Add tests +- [ ] Add Alpine-based images + +## Contributing + +Thank you for considering contributing! If you find an issue, or have a better way to do something, feel free to open an +issue, or a PR. + +## Credits +- [SMortexa](https://github.com/smortexa) +- [All contributors](https://github.com/exaco/laravel-octane-dockerfile/graphs/contributors) + +## License + +This repository is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/docker/prod/deployment/php.ini b/docker/prod/deployment/php.ini new file mode 100644 index 00000000..816d9422 --- /dev/null +++ b/docker/prod/deployment/php.ini @@ -0,0 +1,29 @@ +[PHP] +post_max_size = 100M +upload_max_filesize = 100M +expose_php = 0 +realpath_cache_size = 16M +realpath_cache_ttl = 360 + +[Opcache] +opcache.enable = 1 +opcache.enable_cli = 1 +opcache.memory_consumption = 256M +opcache.use_cwd = 0 +opcache.max_file_size = 0 +opcache.max_accelerated_files = 32531 +opcache.validate_timestamps = 0 +opcache.file_update_protection = 0 +opcache.interned_strings_buffer = 16 +opcache.file_cache = 60 + +[JIT] +opcache.jit_buffer_size = 128M +opcache.jit = function +opcache.jit_prof_threshold = 0.001 +opcache.jit_max_root_traces = 2048 +opcache.jit_max_side_traces = 256 + +[zlib] +zlib.output_compression = On +zlib.output_compression_level = 9 diff --git a/docker/prod/deployment/start-container b/docker/prod/deployment/start-container new file mode 100644 index 00000000..dbc5737b --- /dev/null +++ b/docker/prod/deployment/start-container @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -e + +container_mode=${CONTAINER_MODE:-http} +octane_server=${OCTANE_SERVER} +echo "Container mode: $container_mode" + +initialStuff() { + php artisan optimize:clear; \ + php artisan event:cache; \ + php artisan config:cache; \ + php artisan route:cache; +} + +if [ "$1" != "" ]; then + exec "$@" +elif [ ${container_mode} = "http" ]; then + echo "Octane Server: $octane_server" + initialStuff + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.swoole.conf +elif [ ${container_mode} = "horizon" ]; then + initialStuff + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.horizon.conf +elif [ ${container_mode} = "scheduler" ]; then + initialStuff + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.scheduler.conf +elif [ ${container_mode} = "worker" ]; then + initialStuff + exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.worker.conf +else + echo "Container mode mismatched." + exit 1 +fi diff --git a/docker/prod/deployment/supervisord.horizon.conf b/docker/prod/deployment/supervisord.horizon.conf new file mode 100644 index 00000000..6d03393b --- /dev/null +++ b/docker/prod/deployment/supervisord.horizon.conf @@ -0,0 +1,17 @@ +[supervisord] +nodaemon=true +user=%(ENV_USER)s +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:horizon] +process_name=%(program_name)s_%(process_num)02d +command=php %(ENV_ROOT)s/artisan horizon +user=%(ENV_USER)s +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stopwaitsecs=3600 diff --git a/docker/prod/deployment/supervisord.scheduler.conf b/docker/prod/deployment/supervisord.scheduler.conf new file mode 100644 index 00000000..fb4b1b25 --- /dev/null +++ b/docker/prod/deployment/supervisord.scheduler.conf @@ -0,0 +1,27 @@ +[supervisord] +nodaemon=true +user=%(ENV_USER)s +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:scheduler] +process_name=%(program_name)s_%(process_num)02d +command=supercronic -overlapping /etc/supercronic/laravel +user=%(ENV_USER)s +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:clear-scheduler-cache] +process_name=%(program_name)s_%(process_num)02d +command=php %(ENV_ROOT)s/artisan schedule:clear-cache +user=%(ENV_USER)s +autostart=true +autorestart=false +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 \ No newline at end of file diff --git a/docker/prod/deployment/supervisord.swoole.conf b/docker/prod/deployment/supervisord.swoole.conf new file mode 100644 index 00000000..5495c456 --- /dev/null +++ b/docker/prod/deployment/supervisord.swoole.conf @@ -0,0 +1,42 @@ +[supervisord] +nodaemon=true +user=%(ENV_USER)s +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:octane] +process_name=%(program_name)s_%(process_num)02d +command=php %(ENV_ROOT)s/artisan octane:start --server=swoole --host=0.0.0.0 --port=8000 +user=%(ENV_USER)s +autostart=true +autorestart=true +environment=LARAVEL_OCTANE="1" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:horizon] +process_name=%(program_name)s_%(process_num)02d +command=php %(ENV_ROOT)s/artisan horizon +user=%(ENV_USER)s +autostart=%(ENV_WITH_HORIZON)s +autorestart=true +stdout_logfile=%(ENV_ROOT)s/horizon.log +stopwaitsecs=3600 + +[program:scheduler] +process_name=%(program_name)s_%(process_num)02d +command=supercronic -overlapping /etc/supercronic/laravel +user=%(ENV_USER)s +autostart=%(ENV_WITH_SCHEDULER)s +autorestart=true +stdout_logfile=%(ENV_ROOT)s/scheduler.log + +[program:clear-scheduler-cache] +process_name=%(program_name)s_%(process_num)02d +command=php %(ENV_ROOT)s/artisan schedule:clear-cache +user=%(ENV_USER)s +autostart=%(ENV_WITH_SCHEDULER)s +autorestart=false +stdout_logfile=%(ENV_ROOT)s/scheduler.log diff --git a/docker/prod/deployment/supervisord.worker.conf b/docker/prod/deployment/supervisord.worker.conf new file mode 100644 index 00000000..efd760d0 --- /dev/null +++ b/docker/prod/deployment/supervisord.worker.conf @@ -0,0 +1,16 @@ +[supervisord] +nodaemon=true +user=%(ENV_USER)s +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:worker] +process_name=%(program_name)s_%(process_num)02d +command=%(ENV_WORKER_COMMAND)s +user=%(ENV_USER)s +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/docker/prod/deployment/utilities.sh b/docker/prod/deployment/utilities.sh new file mode 100644 index 00000000..cdaf2833 --- /dev/null +++ b/docker/prod/deployment/utilities.sh @@ -0,0 +1,12 @@ +tinker() { + if [ -z "$1" ]; then + php artisan tinker + else + php artisan tinker --execute="\"dd($1);\"" + fi +} + +# Commonly used aliases +alias ..="cd .." +alias ...="cd ../.." +alias art="php artisan"