diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5c58655 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +.git +.github +.gitignore +.env +.env.example +.editorconfig +node_modules +vendor +storage/logs/* +storage/framework/cache/* +storage/framework/sessions/* +storage/framework/views/* +bootstrap/cache/* +docker +tests +phpunit.xml +README.md +*.md +.vscode +.idea +.DS_Store +Thumbs.db diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..2e13b4f --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,60 @@ +name: Docker Publish + +on: + push: + branches: + - master + tags: + - "v*.*.*" + pull_request: + branches: + - master + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix=sha- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64 + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.rr.yaml b/.rr.yaml new file mode 100644 index 0000000..6b24dd2 --- /dev/null +++ b/.rr.yaml @@ -0,0 +1,42 @@ +version: "3" + +server: + command: "php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=8080 --workers=auto --max-requests=1000 --rpc-port=6001" + relay: pipes + +http: + address: "0.0.0.0:8080" + middleware: + - gzip + - static + static: + dir: "/var/www/html/public" + forbid: + - ".php" + - ".htaccess" + response: + X-Request-ID: "" + pool: + num_workers: 0 + max_jobs: 1000 + allocate_timeout: 60s + destroy_timeout: 60s + supervisor: + max_worker_memory: 128 + +logs: + mode: production + level: warn + encoding: json + output: stderr + +status: + address: "0.0.0.0:2114" + +reload: + interval: 1s + patterns: [".php"] + services: + http: + recursive: true + dirs: ["/var/www/html"] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4b3a97b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,100 @@ +# Stage 1 — Frontend Build +FROM node:20-alpine AS frontend + +WORKDIR /app + +COPY package.json package-lock.json* yarn.lock* ./ + +RUN if [ -f yarn.lock ]; then \ + yarn install --frozen-lockfile; \ + elif [ -f package-lock.json ]; then \ + npm ci; \ + else \ + npm install; \ + fi + +COPY resources/ resources/ +COPY vite.config.js ./ +COPY postcss.config.js* ./ +COPY tailwind.config.js* ./ + +RUN npm run build + +# Stage 2 — Composer Dependencies +FROM composer:2 AS composer + +WORKDIR /app + +COPY composer.json composer.lock ./ +RUN composer install --no-dev --no-interaction --no-scripts --no-autoloader --prefer-dist --ignore-platform-reqs + +COPY . . +RUN composer dump-autoload --optimize --no-dev --ignore-platform-reqs + +# Stage 3 — RoadRunner Binary +FROM ghcr.io/roadrunner-server/roadrunner:2024 AS roadrunner + +# Stage 4 — Production Image +FROM php:8.3-cli-alpine AS production + +LABEL maintainer="codenteq" \ + org.opencontainers.image.source="https://github.com/codenteq/eventeq" \ + org.opencontainers.image.description="Eventeq production image" + +ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ + +RUN apk add --no-cache wget imagemagick \ + && install-php-extensions \ + pdo_mysql \ + pdo_pgsql \ + pgsql \ + gd \ + zip \ + bcmath \ + intl \ + xml \ + mbstring \ + soap \ + pcntl \ + sockets \ + opcache \ + redis \ + imagick + +RUN cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +RUN { \ + echo "memory_limit=256M"; \ + echo "upload_max_filesize=64M"; \ + echo "post_max_size=64M"; \ + echo "max_execution_time=120"; \ + echo "opcache.enable=1"; \ + echo "opcache.validate_timestamps=0"; \ + } > "$PHP_INI_DIR/conf.d/app.ini" + +WORKDIR /var/www/html + +COPY --from=roadrunner /usr/bin/rr /usr/local/bin/rr +COPY --from=composer /app/vendor ./vendor +COPY --from=composer /app . +COPY --from=frontend /app/public/build ./public/build + +RUN mkdir -p \ + storage/logs \ + storage/framework/sessions \ + storage/framework/views \ + storage/framework/cache/data \ + bootstrap/cache \ + && chown -R www-data:www-data storage bootstrap/cache \ + && chmod -R 775 storage bootstrap/cache + +COPY .rr.yaml . + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -qO- http://localhost:8080/up || exit 1 + +USER www-data + +CMD ["sh", "-c", "php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan migrate --force && rr serve -c /var/www/html/.rr.yaml"] diff --git a/composer.json b/composer.json index 2369d6a..e6e0245 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "filament/filament": "^3.2", "inertiajs/inertia-laravel": "^1.2", "laravel/framework": "^11.9", + "laravel/octane": "^2.0", "laravel/pulse": "^1.4", "laravel/tinker": "^2.9", "maatwebsite/excel": "^3.1", diff --git a/composer.lock b/composer.lock index 477fc0f..651e954 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": "8f2d9a0d430f784b32f944d13fd9c3c6", + "content-hash": "d1427ba7a959ee34025136f14462324c", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -2770,6 +2770,94 @@ }, "time": "2025-05-19T14:14:41+00:00" }, + { + "name": "laminas/laminas-diactoros", + "version": "3.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "60c182916b2749480895601649563970f3f12ec4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/60c182916b2749480895601649563970f3f12ec4", + "reference": "60c182916b2749480895601649563970f3f12ec4", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "conflict": { + "amphp/amp": "<2.6.4" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.1 || ^2.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^2.2.0", + "laminas/laminas-coding-standard": "~3.1.0", + "php-http/psr7-integration-tests": "^1.4.0", + "phpunit/phpunit": "^10.5.36", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\Diactoros", + "config-provider": "Laminas\\Diactoros\\ConfigProvider" + } + }, + "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": "2025-10-12T15:31:36+00:00" + }, { "name": "laravel/framework", "version": "v11.45.1", @@ -2985,6 +3073,96 @@ }, "time": "2025-06-03T14:01:40+00:00" }, + { + "name": "laravel/octane", + "version": "v2.13.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/octane.git", + "reference": "c343716659c280a7613a0c10d3241215512355ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/octane/zipball/c343716659c280a7613a0c10d3241215512355ee", + "reference": "c343716659c280a7613a0c10d3241215512355ee", + "shasum": "" + }, + "require": { + "laminas/laminas-diactoros": "^3.0", + "laravel/framework": "^10.10.1|^11.0|^12.0", + "laravel/prompts": "^0.1.24|^0.2.0|^0.3.0", + "laravel/serializable-closure": "^1.3|^2.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": "^1.3.2|^2.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.21|^9.0|^10.0", + "phpstan/phpstan": "^2.1.7", + "phpunit/phpunit": "^10.4|^11.5", + "spiral/roadrunner-cli": "^2.6.0", + "spiral/roadrunner-http": "^3.3.0" + }, + "bin": [ + "bin/roadrunner-worker", + "bin/swoole-server" + ], + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Octane": "Laravel\\Octane\\Facades\\Octane" + }, + "providers": [ + "Laravel\\Octane\\OctaneServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "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": "2026-01-22T17:24:46+00:00" + }, { "name": "laravel/prompts", "version": "v0.3.5", @@ -8152,6 +8330,94 @@ ], "time": "2025-04-17T09:11:12+00:00" }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "929ffe10bbfbb92e711ac3818d416f9daffee067" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/929ffe10bbfbb92e711ac3818d416f9daffee067", + "reference": "929ffe10bbfbb92e711ac3818d416f9daffee067", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-03T23:30:35+00:00" + }, { "name": "symfony/routing", "version": "v7.3.0", @@ -11236,5 +11502,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" }