├── .dockerignore ├── .env.production ├── .github └── workflows │ ├── docker-compose-test.yml │ ├── frankenphp-test.yml │ ├── roadrunner-test.yml │ └── swoole-test.yml ├── .gitignore ├── FrankenPHP.Alpine.Dockerfile ├── FrankenPHP.Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── RoadRunner.Alpine.Dockerfile ├── RoadRunner.Dockerfile ├── Swoole.Alpine.Dockerfile ├── Swoole.Dockerfile ├── deployment ├── healthcheck ├── octane │ ├── FrankenPHP │ │ ├── Caddyfile │ │ └── supervisord.frankenphp.conf │ ├── RoadRunner │ │ ├── .rr.prod.yaml │ │ └── supervisord.roadrunner.conf │ └── Swoole │ │ └── supervisord.swoole.conf ├── php.ini ├── start-container ├── supervisord.conf ├── supervisord.horizon.conf ├── supervisord.reverb.conf ├── supervisord.scheduler.conf └── supervisord.worker.conf ├── docker-compose.production.yml └── static-build.Dockerfile /.dockerignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /storage/app/public 8 | /storage/debugbar 9 | /storage/ssr 10 | /storage/clockwork 11 | /storage/logs 12 | /storage/pail 13 | .phpunit.result.cache 14 | Homestead.json 15 | Homestead.yaml 16 | npm-debug.log 17 | yarn-error.log 18 | /vendor 19 | .env.backup 20 | /.idea/sonarlint 21 | .phpstorm.meta.php 22 | _ide_helper_models.php 23 | _ide_helper.php 24 | .php-cs-fixer.cache 25 | .husky 26 | /.vscode 27 | **/.DS_Store 28 | /public/page-cache 29 | .phpunit.database.checksum 30 | .phpunit.cache 31 | rr 32 | .rr.yaml 33 | frankenphp 34 | .config 35 | .data 36 | .git 37 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | APP_NAME= 2 | VITE_APP_NAME="${APP_NAME}" 3 | APP_ENV=production 4 | APP_KEY=base64:pKFQK9r60jaB/4CB5UOz1sIabob9yF1qt9rIrsS/UUw= 5 | APP_DEBUG=false 6 | APP_HOST=localhost 7 | APP_URL=https://${APP_HOST} 8 | APP_TIMEZONE=UTC 9 | ASSET_URL="/" # for relative asset path 10 | 11 | OCTANE_HTTPS=true 12 | 13 | HASH_DRIVER=argon2id 14 | 15 | LOG_CHANNEL=stack 16 | LOG_STACK=daily,errorlog 17 | LOG_DEPRECATIONS_CHANNEL=null 18 | LOG_LEVEL=debug 19 | 20 | DB_CONNECTION=pgsql 21 | DB_HOST=pgsql 22 | DB_PORT=5432 23 | DB_DATABASE=userdb 24 | DB_USERNAME=user 25 | DB_PASSWORD="123456" 26 | 27 | PGADMIN_DEFAULT_EMAIL=user@localhost.test 28 | PGADMIN_DEFAULT_PASSWORD="123456" 29 | 30 | BROADCAST_CONNECTION=reverb 31 | 32 | CACHE_STORE=redis 33 | CACHE_PREFIX="${APP_NAME}_" 34 | 35 | QUEUE_CONNECTION=redis 36 | 37 | SESSION_DRIVER=redis 38 | SESSION_LIFETIME=180 39 | SESSION_COOKIE="_${APP_NAME}" 40 | 41 | MEMCACHED_HOST=127.0.0.1 42 | 43 | REDIS_HOST=redis 44 | REDIS_PASSWORD="123456" 45 | REDIS_PORT=6379 46 | 47 | MAIL_MAILER=smtp 48 | MAIL_DRIVER=smtp 49 | MAIL_HOST=smtp.googlemail.com 50 | MAIL_PORT=465 51 | MAIL_USERNAME= 52 | MAIL_PASSWORD= 53 | MAIL_ENCRYPTION=ssl 54 | MAIL_FROM_ADDRESS= 55 | MAIL_FROM_NAME="${APP_NAME}" 56 | 57 | MINIO_ROOT_USER=user 58 | MINIO_ROOT_PASSWORD="123456789" 59 | 60 | FILESYSTEM_DISK=s3 61 | AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER} 62 | AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD} 63 | AWS_DEFAULT_REGION=us-east-1 64 | AWS_BUCKET=assets 65 | AWS_ENDPOINT=http://localhost:9000 66 | AWS_USE_PATH_STYLE_ENDPOINT=true 67 | AWS_URL=http://localhost:9000/assets 68 | 69 | SCOUT_DRIVER=typesense 70 | 71 | TYPESENSE_HOST=typesense 72 | TYPESENSE_PORT=8108 73 | TYPESENSE_PROTOCOL=http 74 | TYPESENSE_API_KEY="123456" 75 | 76 | REVERB_APP_ID=123456 77 | REVERB_APP_KEY=user 78 | REVERB_APP_SECRET=123456 79 | REVERB_HOST=${APP_HOST} 80 | REVERB_PORT=8080 81 | REVERB_SCHEME=http 82 | 83 | VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" 84 | VITE_REVERB_HOST="${REVERB_HOST}" 85 | VITE_REVERB_PORT="${REVERB_PORT}" 86 | VITE_REVERB_SCHEME="${REVERB_SCHEME}" 87 | -------------------------------------------------------------------------------- /.github/workflows/docker-compose-test.yml: -------------------------------------------------------------------------------- 1 | name: Docker Compose test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | paths-ignore: 10 | - '**.md' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | 15 | jobs: 16 | build: 17 | name: Build and Run Docker Compose 18 | runs-on: ubuntu-24.04 19 | timeout-minutes: 30 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Setup PHP with Composer and extensions 25 | uses: shivammathur/setup-php@v2 26 | with: 27 | extensions: dom, curl, libxml, mbstring, zip 28 | tools: composer:v2 29 | coverage: none 30 | 31 | - name: Get Composer cache directory 32 | id: composer-cache 33 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 34 | 35 | - name: Cache PHP dependencies 36 | uses: actions/cache@v3 37 | with: 38 | path: ${{ steps.composer-cache.outputs.dir }} 39 | key: "${{ runner.os }}-composer" 40 | restore-keys: "${{ runner.os }}-composer-" 41 | 42 | - name: Create app directory 43 | run: mkdir -p /var/www 44 | 45 | - name: Install a fresh Laravel app 46 | run: sudo composer create-project laravel/laravel app 47 | working-directory: /var/www 48 | 49 | - name: Install Laravel Octane 50 | run: sudo script -e -c "composer require --no-interaction laravel/octane laravel/horizon" 51 | working-directory: /var/www/app 52 | 53 | - name: Install Laravel Reverb 54 | run: sudo script -e -c "php artisan install:broadcasting --reverb --no-interaction --force" 55 | working-directory: /var/www/app 56 | 57 | - name: Copy required content 58 | run: sudo cp -R FrankenPHP.Dockerfile docker-compose.production.yml Makefile .env.production .dockerignore deployment/ /var/www/app/ 59 | 60 | - name: Prepare the environment 61 | run: sudo mkdir -p storage/framework/{sessions,views,cache,testing} storage/logs && sudo chmod -R a+rw storage 62 | working-directory: /var/www/app 63 | 64 | - name: Run the Docker Compose 65 | run: sudo make down:with-volumes && sudo make build && sudo make up 66 | working-directory: /var/www/app 67 | 68 | - name: Wait for the container 69 | run: sleep 60s 70 | 71 | - name: Print the container logs 72 | run: sudo docker ps -a && docker logs app-app-1 73 | 74 | - name: Check application health 75 | run: curl -f -s -o /dev/null -w "%{http_code}" --insecure https://localhost/up 76 | -------------------------------------------------------------------------------- /.github/workflows/frankenphp-test.yml: -------------------------------------------------------------------------------- 1 | name: FrankenPHP test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | paths-ignore: 10 | - '**.md' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | 15 | jobs: 16 | debian-build: 17 | name: Build and Run Debian-based Docker image 18 | runs-on: ubuntu-24.04 19 | strategy: 20 | fail-fast: true 21 | matrix: 22 | php: [ 8.3, 8.4 ] 23 | timeout-minutes: 15 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v2 27 | 28 | - name: Setup PHP with Composer and extensions 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: "${{ matrix.php }}" 32 | extensions: dom, curl, libxml, mbstring, zip 33 | tools: composer:v2 34 | coverage: none 35 | 36 | - name: Get Composer cache directory 37 | id: composer-cache 38 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 39 | 40 | - name: Cache PHP dependencies 41 | uses: actions/cache@v3 42 | with: 43 | path: ${{ steps.composer-cache.outputs.dir }} 44 | key: "${{ runner.os }}-composer-${{ matrix.setup }}" 45 | restore-keys: "${{ runner.os }}-composer-" 46 | 47 | - name: Create app directory 48 | run: mkdir -p /var/www 49 | 50 | - name: Install a fresh Laravel app 51 | run: sudo composer create-project laravel/laravel app 52 | working-directory: /var/www 53 | 54 | - name: Install Laravel Octane 55 | run: sudo composer require laravel/octane 56 | working-directory: /var/www/app 57 | 58 | - name: Copy required content to dockerize the app 59 | run: sudo cp -R FrankenPHP.Dockerfile .dockerignore deployment/ /var/www/app/ 60 | 61 | - name: Build image 62 | run: docker build -t app:local --build-arg PHP_VERSION=${{ matrix.php }} -f FrankenPHP.Dockerfile . 63 | working-directory: /var/www/app 64 | 65 | - name: Run the Docker container 66 | run: docker run -d --name app --rm -p 8000:8000 app:local 67 | working-directory: /var/www/app 68 | 69 | - name: Wait for the container 70 | run: sleep 30s 71 | 72 | - name: Print the container logs 73 | run: docker logs app 74 | 75 | - name: Check application health 76 | run: curl -f -s -o /dev/null -w "%{http_code}" http://localhost:8000/up 77 | 78 | alpine-build: 79 | name: Build and Run Alpine-based Docker image 80 | runs-on: ubuntu-24.04 81 | strategy: 82 | fail-fast: true 83 | matrix: 84 | php: [ 8.3, 8.4 ] 85 | timeout-minutes: 15 86 | steps: 87 | - name: Checkout code 88 | uses: actions/checkout@v2 89 | 90 | - name: Setup PHP with Composer and extensions 91 | uses: shivammathur/setup-php@v2 92 | with: 93 | php-version: "${{ matrix.php }}" 94 | extensions: dom, curl, libxml, mbstring, zip 95 | tools: composer:v2 96 | coverage: none 97 | 98 | - name: Get Composer cache directory 99 | id: composer-cache 100 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 101 | 102 | - name: Cache PHP dependencies 103 | uses: actions/cache@v3 104 | with: 105 | path: ${{ steps.composer-cache.outputs.dir }} 106 | key: "${{ runner.os }}-composer-${{ matrix.setup }}" 107 | restore-keys: "${{ runner.os }}-composer-" 108 | 109 | - name: Create app directory 110 | run: mkdir -p /var/www 111 | 112 | - name: Install a fresh Laravel app 113 | run: sudo composer create-project laravel/laravel app 114 | working-directory: /var/www 115 | 116 | - name: Install Laravel Octane 117 | run: sudo composer require laravel/octane 118 | working-directory: /var/www/app 119 | 120 | - name: Copy required content to dockerize the app 121 | run: sudo cp -R FrankenPHP.Alpine.Dockerfile .dockerignore deployment/ /var/www/app/ 122 | 123 | - name: Build image 124 | run: docker build -t app:local --build-arg PHP_VERSION=${{ matrix.php }} -f FrankenPHP.Alpine.Dockerfile . 125 | working-directory: /var/www/app 126 | 127 | - name: Run the Docker container 128 | run: docker run -d --name app --rm -p 8000:8000 app:local 129 | working-directory: /var/www/app 130 | 131 | - name: Wait for the container 132 | run: sleep 30s 133 | 134 | - name: Print the container logs 135 | run: docker logs app 136 | 137 | - name: Check application health 138 | run: curl -f -s -o /dev/null -w "%{http_code}" http://localhost:8000/up -------------------------------------------------------------------------------- /.github/workflows/roadrunner-test.yml: -------------------------------------------------------------------------------- 1 | name: RoadRunner test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | paths-ignore: 10 | - '**.md' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | 15 | jobs: 16 | debian-build: 17 | name: Build and Run Debian-based Docker image 18 | runs-on: ubuntu-24.04 19 | strategy: 20 | fail-fast: true 21 | matrix: 22 | php: [ 8.3, 8.4 ] 23 | timeout-minutes: 15 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v2 27 | 28 | - name: Setup PHP with Composer and extensions 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: "${{ matrix.php }}" 32 | extensions: dom, curl, libxml, mbstring, zip 33 | tools: composer:v2 34 | coverage: none 35 | 36 | - name: Get Composer cache directory 37 | id: composer-cache 38 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 39 | 40 | - name: Cache PHP dependencies 41 | uses: actions/cache@v3 42 | with: 43 | path: ${{ steps.composer-cache.outputs.dir }} 44 | key: "${{ runner.os }}-composer-${{ matrix.setup }}" 45 | restore-keys: "${{ runner.os }}-composer-" 46 | 47 | - name: Create app directory 48 | run: mkdir -p /var/www 49 | 50 | - name: Install a fresh Laravel app 51 | run: sudo composer create-project laravel/laravel app 52 | working-directory: /var/www 53 | 54 | - name: Install Laravel Octane 55 | run: sudo composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies 56 | working-directory: /var/www/app 57 | 58 | - name: Copy required content to dockerize the app 59 | run: sudo cp -R RoadRunner.Dockerfile .dockerignore deployment/ /var/www/app/ 60 | 61 | - name: Build image 62 | run: docker build -t app:local --build-arg PHP_VERSION=${{ matrix.php }} -f RoadRunner.Dockerfile . 63 | working-directory: /var/www/app 64 | 65 | - name: Run the Docker container 66 | run: docker run -d --name app --rm -p 8000:8000 app:local 67 | working-directory: /var/www/app 68 | 69 | - name: Wait for the container 70 | run: sleep 30s 71 | 72 | - name: Print the container logs 73 | run: docker logs app 74 | 75 | - name: Check application health 76 | run: curl -f -s -o /dev/null -w "%{http_code}" http://localhost:8000/up 77 | 78 | alpine-build: 79 | name: Build and Run Alpine-based Docker image 80 | runs-on: ubuntu-24.04 81 | strategy: 82 | fail-fast: true 83 | matrix: 84 | php: [ 8.3, 8.4 ] 85 | timeout-minutes: 15 86 | steps: 87 | - name: Checkout code 88 | uses: actions/checkout@v2 89 | 90 | - name: Setup PHP with Composer and extensions 91 | uses: shivammathur/setup-php@v2 92 | with: 93 | php-version: "${{ matrix.php }}" 94 | extensions: dom, curl, libxml, mbstring, zip 95 | tools: composer:v2 96 | coverage: none 97 | 98 | - name: Get Composer cache directory 99 | id: composer-cache 100 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 101 | 102 | - name: Cache PHP dependencies 103 | uses: actions/cache@v3 104 | with: 105 | path: ${{ steps.composer-cache.outputs.dir }} 106 | key: "${{ runner.os }}-composer-${{ matrix.setup }}" 107 | restore-keys: "${{ runner.os }}-composer-" 108 | 109 | - name: Create app directory 110 | run: mkdir -p /var/www 111 | 112 | - name: Install a fresh Laravel app 113 | run: sudo composer create-project laravel/laravel app 114 | working-directory: /var/www 115 | 116 | - name: Install Laravel Octane 117 | run: sudo composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http --with-all-dependencies 118 | working-directory: /var/www/app 119 | 120 | - name: Copy required content to dockerize the app 121 | run: sudo cp -R RoadRunner.Alpine.Dockerfile .dockerignore deployment/ /var/www/app/ 122 | 123 | - name: Build image 124 | run: docker build -t app:local --build-arg PHP_VERSION=${{ matrix.php }} -f RoadRunner.Alpine.Dockerfile . 125 | working-directory: /var/www/app 126 | 127 | - name: Run the Docker container 128 | run: docker run -d --name app --rm -p 8000:8000 app:local 129 | working-directory: /var/www/app 130 | 131 | - name: Wait for the container 132 | run: sleep 30s 133 | 134 | - name: Print the container logs 135 | run: docker logs app 136 | 137 | - name: Check application health 138 | run: curl -f -s -o /dev/null -w "%{http_code}" http://localhost:8000/up -------------------------------------------------------------------------------- /.github/workflows/swoole-test.yml: -------------------------------------------------------------------------------- 1 | name: Swoole test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | paths-ignore: 10 | - '**.md' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | 15 | jobs: 16 | debian-build: 17 | name: Build and Run Debian-based Docker image 18 | runs-on: ubuntu-24.04 19 | strategy: 20 | fail-fast: true 21 | matrix: 22 | php: [ 8.3, 8.4 ] 23 | timeout-minutes: 15 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v2 27 | 28 | - name: Setup PHP with Composer and extensions 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: "${{ matrix.php }}" 32 | extensions: dom, curl, libxml, mbstring, zip 33 | tools: composer:v2 34 | coverage: none 35 | 36 | - name: Get Composer cache directory 37 | id: composer-cache 38 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 39 | 40 | - name: Cache PHP dependencies 41 | uses: actions/cache@v3 42 | with: 43 | path: ${{ steps.composer-cache.outputs.dir }} 44 | key: "${{ runner.os }}-composer-${{ matrix.setup }}" 45 | restore-keys: "${{ runner.os }}-composer-" 46 | 47 | - name: Create app directory 48 | run: mkdir -p /var/www 49 | 50 | - name: Install a fresh Laravel app 51 | run: sudo composer create-project laravel/laravel app 52 | working-directory: /var/www 53 | 54 | - name: Install Laravel Octane 55 | run: sudo composer require laravel/octane 56 | working-directory: /var/www/app 57 | 58 | - name: Copy required content to dockerize the app 59 | run: sudo cp -R Swoole.Dockerfile .dockerignore deployment/ /var/www/app/ 60 | 61 | - name: Build image 62 | run: docker build -t app:local --build-arg PHP_VERSION=${{ matrix.php }} -f Swoole.Dockerfile . 63 | working-directory: /var/www/app 64 | 65 | - name: Run the Docker container 66 | run: docker run -d --name app --rm -p 8000:8000 app:local 67 | working-directory: /var/www/app 68 | 69 | - name: Wait for the container 70 | run: sleep 30s 71 | 72 | - name: Print the container logs 73 | run: docker logs app 74 | 75 | - name: Check application health 76 | run: curl -f -s -o /dev/null -w "%{http_code}" http://localhost:8000/up 77 | 78 | alpine-build: 79 | name: Build and Run Alpine-based Docker image 80 | runs-on: ubuntu-24.04 81 | strategy: 82 | fail-fast: true 83 | matrix: 84 | php: [ 8.3, 8.4 ] 85 | timeout-minutes: 15 86 | steps: 87 | - name: Checkout code 88 | uses: actions/checkout@v2 89 | 90 | - name: Setup PHP with Composer and extensions 91 | uses: shivammathur/setup-php@v2 92 | with: 93 | php-version: "${{ matrix.php }}" 94 | extensions: dom, curl, libxml, mbstring, zip 95 | tools: composer:v2 96 | coverage: none 97 | 98 | - name: Get Composer cache directory 99 | id: composer-cache 100 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 101 | 102 | - name: Cache PHP dependencies 103 | uses: actions/cache@v3 104 | with: 105 | path: ${{ steps.composer-cache.outputs.dir }} 106 | key: "${{ runner.os }}-composer-${{ matrix.setup }}" 107 | restore-keys: "${{ runner.os }}-composer-" 108 | 109 | - name: Create app directory 110 | run: mkdir -p /var/www 111 | 112 | - name: Install a fresh Laravel app 113 | run: sudo composer create-project laravel/laravel app 114 | working-directory: /var/www 115 | 116 | - name: Install Laravel Octane 117 | run: sudo composer require laravel/octane 118 | working-directory: /var/www/app 119 | 120 | - name: Copy required content to dockerize the app 121 | run: sudo cp -R Swoole.Alpine.Dockerfile .dockerignore deployment/ /var/www/app/ 122 | 123 | - name: Build image 124 | run: docker build -t app:local --build-arg PHP_VERSION=${{ matrix.php }} -f Swoole.Alpine.Dockerfile . 125 | working-directory: /var/www/app 126 | 127 | - name: Run the Docker container 128 | run: docker run -d --name app --rm -p 8000:8000 app:local 129 | working-directory: /var/www/app 130 | 131 | - name: Wait for the container 132 | run: sleep 30s 133 | 134 | - name: Print the container logs 135 | run: docker logs app 136 | 137 | - name: Check application health 138 | run: curl -f -s -o /dev/null -w "%{http_code}" http://localhost:8000/up -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | **/.DS_Store 4 | .config 5 | .data -------------------------------------------------------------------------------- /FrankenPHP.Alpine.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.4 2 | ARG FRANKENPHP_VERSION=1.6 3 | ARG COMPOSER_VERSION=2.8 4 | ARG BUN_VERSION="latest" 5 | ARG APP_ENV 6 | 7 | FROM composer:${COMPOSER_VERSION} AS vendor 8 | 9 | FROM dunglas/frankenphp:${FRANKENPHP_VERSION}-builder-php${PHP_VERSION}-alpine AS upstream 10 | 11 | COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy 12 | 13 | RUN CGO_ENABLED=1 \ 14 | XCADDY_SETCAP=1 \ 15 | XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ 16 | CGO_CFLAGS=$(php-config --includes) \ 17 | CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ 18 | xcaddy build \ 19 | --output /usr/local/bin/frankenphp \ 20 | --with github.com/dunglas/frankenphp=./ \ 21 | --with github.com/dunglas/frankenphp/caddy=./caddy/ \ 22 | --with github.com/dunglas/caddy-cbrotli 23 | 24 | FROM dunglas/frankenphp:${FRANKENPHP_VERSION}-php${PHP_VERSION}-alpine AS base 25 | 26 | COPY --from=upstream /usr/local/bin/frankenphp /usr/local/bin/frankenphp 27 | 28 | LABEL maintainer="SMortexa " 29 | LABEL org.opencontainers.image.title="Laravel Octane Dockerfile" 30 | LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane" 31 | LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile 32 | LABEL org.opencontainers.image.licenses=MIT 33 | 34 | ARG WWWUSER=1000 35 | ARG WWWGROUP=1000 36 | ARG TZ=UTC 37 | ARG APP_DIR=/var/www/html 38 | ARG APP_ENV 39 | ARG APP_HOST 40 | 41 | ENV TERM=xterm-color \ 42 | OCTANE_SERVER=frankenphp \ 43 | TZ=${TZ} \ 44 | USER=octane \ 45 | ROOT=${APP_DIR} \ 46 | APP_ENV=${APP_ENV} \ 47 | COMPOSER_FUND=0 \ 48 | COMPOSER_MAX_PARALLEL_HTTP=24 \ 49 | XDG_CONFIG_HOME=${APP_DIR}/.config \ 50 | XDG_DATA_HOME=${APP_DIR}/.data \ 51 | SERVER_NAME=${APP_HOST} 52 | 53 | WORKDIR ${ROOT} 54 | 55 | SHELL ["/bin/sh", "-eou", "pipefail", "-c"] 56 | 57 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ 58 | && echo ${TZ} > /etc/timezone 59 | 60 | RUN apk update; \ 61 | apk upgrade; \ 62 | apk add --no-cache \ 63 | curl \ 64 | wget \ 65 | vim \ 66 | tzdata \ 67 | git \ 68 | ncdu \ 69 | procps \ 70 | unzip \ 71 | ca-certificates \ 72 | supervisor \ 73 | libsodium-dev \ 74 | brotli \ 75 | # Install PHP extensions (included with dunglas/frankenphp) 76 | && install-php-extensions \ 77 | bz2 \ 78 | pcntl \ 79 | mbstring \ 80 | bcmath \ 81 | sockets \ 82 | pgsql \ 83 | pdo_pgsql \ 84 | opcache \ 85 | exif \ 86 | pdo_mysql \ 87 | zip \ 88 | uv \ 89 | vips \ 90 | intl \ 91 | gd \ 92 | redis \ 93 | rdkafka \ 94 | memcached \ 95 | igbinary \ 96 | ldap \ 97 | && docker-php-source delete \ 98 | && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* 99 | 100 | RUN arch="$(apk --print-arch)" \ 101 | && case "$arch" in \ 102 | armhf) _cronic_fname='supercronic-linux-arm' ;; \ 103 | aarch64) _cronic_fname='supercronic-linux-arm64' ;; \ 104 | x86_64) _cronic_fname='supercronic-linux-amd64' ;; \ 105 | x86) _cronic_fname='supercronic-linux-386' ;; \ 106 | *) echo >&2 "error: unsupported architecture: $arch"; exit 1 ;; \ 107 | esac \ 108 | && wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/${_cronic_fname}" \ 109 | -O /usr/bin/supercronic \ 110 | && chmod +x /usr/bin/supercronic \ 111 | && mkdir -p /etc/supercronic \ 112 | && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel 113 | 114 | RUN addgroup -g ${WWWGROUP} ${USER} \ 115 | && adduser -D -h ${ROOT} -G ${USER} -u ${WWWUSER} -s /bin/sh ${USER} \ 116 | && setcap -r /usr/local/bin/frankenphp 117 | 118 | RUN mkdir -p /var/log/supervisor /var/run/supervisor \ 119 | && chown -R ${USER}:${USER} ${ROOT} /var/log /var/run \ 120 | && chmod -R a+rw ${ROOT} /var/log /var/run 121 | 122 | RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini 123 | 124 | USER ${USER} 125 | 126 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=vendor /usr/bin/composer /usr/bin/composer 127 | 128 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.conf /etc/ 129 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/FrankenPHP/supervisord.frankenphp.conf /etc/supervisor/conf.d/ 130 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.*.conf /etc/supervisor/conf.d/ 131 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/start-container /usr/local/bin/start-container 132 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/healthcheck /usr/local/bin/healthcheck 133 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini 134 | 135 | RUN chmod +x /usr/local/bin/start-container /usr/local/bin/healthcheck 136 | 137 | ########################################### 138 | 139 | FROM base AS common 140 | 141 | USER ${USER} 142 | 143 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 144 | 145 | RUN composer install \ 146 | --no-dev \ 147 | --no-interaction \ 148 | --no-autoloader \ 149 | --no-ansi \ 150 | --no-scripts \ 151 | --audit 152 | 153 | ########################################### 154 | # Build frontend assets with Bun 155 | ########################################### 156 | 157 | FROM oven/bun:${BUN_VERSION} AS build 158 | 159 | ARG APP_ENV 160 | 161 | ENV ROOT=/var/www/html \ 162 | APP_ENV=${APP_ENV} \ 163 | NODE_ENV=${APP_ENV:-production} 164 | 165 | WORKDIR ${ROOT} 166 | 167 | COPY --link package.json bun.lock* ./ 168 | 169 | RUN bun install --frozen-lockfile 170 | 171 | COPY --link . . 172 | COPY --link --from=common ${ROOT}/vendor vendor 173 | 174 | RUN bun run build 175 | 176 | ########################################### 177 | 178 | FROM common AS runner 179 | 180 | USER ${USER} 181 | 182 | ENV WITH_HORIZON=false \ 183 | WITH_SCHEDULER=false \ 184 | WITH_REVERB=false 185 | 186 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 187 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public 188 | 189 | RUN mkdir -p \ 190 | storage/framework/sessions \ 191 | storage/framework/views \ 192 | storage/framework/cache \ 193 | storage/framework/testing \ 194 | storage/logs \ 195 | bootstrap/cache && chmod -R a+rw storage 196 | 197 | RUN composer install \ 198 | --classmap-authoritative \ 199 | --no-interaction \ 200 | --no-ansi \ 201 | --no-dev \ 202 | && composer clear-cache 203 | 204 | EXPOSE 8000 205 | EXPOSE 443 206 | EXPOSE 443/udp 207 | EXPOSE 2019 208 | EXPOSE 8080 209 | 210 | ENTRYPOINT ["start-container"] 211 | 212 | HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD healthcheck || exit 1 213 | -------------------------------------------------------------------------------- /FrankenPHP.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.4 2 | ARG FRANKENPHP_VERSION=1.6 3 | ARG COMPOSER_VERSION=2.8 4 | ARG BUN_VERSION="latest" 5 | ARG APP_ENV 6 | 7 | FROM composer:${COMPOSER_VERSION} AS vendor 8 | 9 | FROM dunglas/frankenphp:${FRANKENPHP_VERSION}-builder-php${PHP_VERSION} AS upstream 10 | 11 | COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy 12 | 13 | RUN CGO_ENABLED=1 \ 14 | XCADDY_SETCAP=1 \ 15 | XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ 16 | CGO_CFLAGS=$(php-config --includes) \ 17 | CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ 18 | xcaddy build \ 19 | --output /usr/local/bin/frankenphp \ 20 | --with github.com/dunglas/frankenphp=./ \ 21 | --with github.com/dunglas/frankenphp/caddy=./caddy/ \ 22 | --with github.com/dunglas/caddy-cbrotli 23 | 24 | FROM dunglas/frankenphp:${FRANKENPHP_VERSION}-php${PHP_VERSION} AS base 25 | 26 | COPY --from=upstream /usr/local/bin/frankenphp /usr/local/bin/frankenphp 27 | 28 | LABEL maintainer="SMortexa " 29 | LABEL org.opencontainers.image.title="Laravel Octane Dockerfile" 30 | LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane" 31 | LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile 32 | LABEL org.opencontainers.image.licenses=MIT 33 | 34 | ARG WWWUSER=1000 35 | ARG WWWGROUP=1000 36 | ARG TZ=UTC 37 | ARG APP_DIR=/var/www/html 38 | ARG APP_ENV 39 | ARG APP_HOST 40 | 41 | ENV DEBIAN_FRONTEND=noninteractive \ 42 | TERM=xterm-color \ 43 | OCTANE_SERVER=frankenphp \ 44 | TZ=${TZ} \ 45 | USER=octane \ 46 | ROOT=${APP_DIR} \ 47 | APP_ENV=${APP_ENV} \ 48 | COMPOSER_FUND=0 \ 49 | COMPOSER_MAX_PARALLEL_HTTP=24 \ 50 | XDG_CONFIG_HOME=${APP_DIR}/.config \ 51 | XDG_DATA_HOME=${APP_DIR}/.data \ 52 | SERVER_NAME=${APP_HOST} 53 | 54 | WORKDIR ${ROOT} 55 | 56 | SHELL ["/bin/bash", "-eou", "pipefail", "-c"] 57 | 58 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ 59 | && echo ${TZ} > /etc/timezone 60 | 61 | RUN apt-get update; \ 62 | apt-get upgrade -yqq; \ 63 | apt-get install -yqq --no-install-recommends --show-progress \ 64 | apt-utils \ 65 | curl \ 66 | wget \ 67 | vim \ 68 | git \ 69 | ncdu \ 70 | procps \ 71 | unzip \ 72 | ca-certificates \ 73 | supervisor \ 74 | libsodium-dev \ 75 | libbrotli-dev \ 76 | # Install PHP extensions (included with dunglas/frankenphp) 77 | && install-php-extensions \ 78 | bz2 \ 79 | pcntl \ 80 | mbstring \ 81 | bcmath \ 82 | sockets \ 83 | pgsql \ 84 | pdo_pgsql \ 85 | opcache \ 86 | exif \ 87 | pdo_mysql \ 88 | zip \ 89 | uv \ 90 | vips \ 91 | intl \ 92 | gd \ 93 | redis \ 94 | rdkafka \ 95 | memcached \ 96 | igbinary \ 97 | ldap \ 98 | && apt-get -y autoremove \ 99 | && apt-get clean \ 100 | && docker-php-source delete \ 101 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ 102 | && rm /var/log/lastlog /var/log/faillog 103 | 104 | RUN arch="$(uname -m)" \ 105 | && case "$arch" in \ 106 | armhf) _cronic_fname='supercronic-linux-arm' ;; \ 107 | aarch64) _cronic_fname='supercronic-linux-arm64' ;; \ 108 | x86_64) _cronic_fname='supercronic-linux-amd64' ;; \ 109 | x86) _cronic_fname='supercronic-linux-386' ;; \ 110 | *) echo >&2 "error: unsupported architecture: $arch"; exit 1 ;; \ 111 | esac \ 112 | && wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/${_cronic_fname}" \ 113 | -O /usr/bin/supercronic \ 114 | && chmod +x /usr/bin/supercronic \ 115 | && mkdir -p /etc/supercronic \ 116 | && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel 117 | 118 | RUN userdel --remove --force www-data \ 119 | && groupadd --force -g ${WWWGROUP} ${USER} \ 120 | && useradd -ms /bin/bash --no-log-init --no-user-group -g ${WWWGROUP} -u ${WWWUSER} ${USER} \ 121 | && setcap -r /usr/local/bin/frankenphp 122 | 123 | RUN chown -R ${USER}:${USER} ${ROOT} /var/{log,run} \ 124 | && chmod -R a+rw ${ROOT} /var/{log,run} 125 | 126 | RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini 127 | 128 | USER ${USER} 129 | 130 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=vendor /usr/bin/composer /usr/bin/composer 131 | 132 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.conf /etc/ 133 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/FrankenPHP/supervisord.frankenphp.conf /etc/supervisor/conf.d/ 134 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.*.conf /etc/supervisor/conf.d/ 135 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/start-container /usr/local/bin/start-container 136 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/healthcheck /usr/local/bin/healthcheck 137 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini 138 | 139 | RUN chmod +x /usr/local/bin/start-container /usr/local/bin/healthcheck 140 | 141 | ########################################### 142 | 143 | FROM base AS common 144 | 145 | USER ${USER} 146 | 147 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 148 | 149 | RUN composer install \ 150 | --no-dev \ 151 | --no-interaction \ 152 | --no-autoloader \ 153 | --no-ansi \ 154 | --no-scripts \ 155 | --audit 156 | 157 | ########################################### 158 | # Build frontend assets with Bun 159 | ########################################### 160 | 161 | FROM oven/bun:${BUN_VERSION} AS build 162 | 163 | ARG APP_ENV 164 | 165 | ENV ROOT=/var/www/html \ 166 | APP_ENV=${APP_ENV} \ 167 | NODE_ENV=${APP_ENV:-production} 168 | 169 | WORKDIR ${ROOT} 170 | 171 | COPY --link package.json bun.lock* ./ 172 | 173 | RUN bun install --frozen-lockfile 174 | 175 | COPY --link . . 176 | COPY --link --from=common ${ROOT}/vendor vendor 177 | 178 | RUN bun run build 179 | 180 | ########################################### 181 | 182 | FROM common AS runner 183 | 184 | USER ${USER} 185 | 186 | ENV WITH_HORIZON=false \ 187 | WITH_SCHEDULER=false \ 188 | WITH_REVERB=false 189 | 190 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 191 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public 192 | 193 | RUN mkdir -p \ 194 | storage/framework/{sessions,views,cache,testing} \ 195 | storage/logs \ 196 | bootstrap/cache && chmod -R a+rw storage 197 | 198 | RUN composer install \ 199 | --classmap-authoritative \ 200 | --no-interaction \ 201 | --no-ansi \ 202 | --no-dev \ 203 | && composer clear-cache 204 | 205 | EXPOSE 8000 206 | EXPOSE 443 207 | EXPOSE 443/udp 208 | EXPOSE 2019 209 | EXPOSE 8080 210 | 211 | ENTRYPOINT ["start-container"] 212 | 213 | HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD healthcheck || exit 1 214 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Exa Company 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make 2 | # Makefile readme (en): 3 | 4 | SHELL = /bin/bash 5 | DC_RUN_ARGS = --env-file ./.env.production --profile app --profile administration -f docker-compose.production.yml 6 | HOST_UID=$(shell id -u) 7 | HOST_GID=$(shell id -g) 8 | 9 | .PHONY : help up down shell\:app stop-all ps update build restart down-up images\:list images\:clean logs\:app logs containers\:health command\:app 10 | .DEFAULT_GOAL : help 11 | 12 | # This will output the help for each task. thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html 13 | help: ## Show this help 14 | @printf "\033[33m%s:\033[0m\n" 'Available commands' 15 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf " \033[32m%-18s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 16 | 17 | up: ## Up containers 18 | docker compose ${DC_RUN_ARGS} up -d --remove-orphans 19 | 20 | logs: ## Tail all containers logs 21 | docker compose ${DC_RUN_ARGS} logs -f 22 | 23 | logs\:app: ## app container logs 24 | docker compose ${DC_RUN_ARGS} logs app 25 | 26 | down: ## Stop containers 27 | docker compose ${DC_RUN_ARGS} down 28 | 29 | down\:with-volumes: ## Stop containers and remove volumes 30 | docker compose ${DC_RUN_ARGS} down -v 31 | 32 | shell\:app: ## Start shell into app container 33 | docker compose ${DC_RUN_ARGS} exec app sh 34 | 35 | command\:app: ## Run a command in the app container 36 | docker compose ${DC_RUN_ARGS} exec app sh -c "$(command)" 37 | 38 | stop-all: ## Stop all containers 39 | docker stop $(shell docker ps -a -q) 40 | 41 | ps: ## Containers status 42 | docker compose ${DC_RUN_ARGS} ps 43 | 44 | build: ## Build images 45 | docker compose ${DC_RUN_ARGS} build 46 | 47 | update: ## Update containers 48 | docker compose ${DC_RUN_ARGS} up -d --no-deps --build --remove-orphans 49 | 50 | restart: ## Restart all containers 51 | docker compose ${DC_RUN_ARGS} restart 52 | 53 | down-up: down up ## Down all containers, then up 54 | 55 | images\:list: ## Sort Docker images by size 56 | docker images --format "{{.ID}}\t{{.Size}}\t{{.Repository}}" | sort -k 2 -h 57 | 58 | images\:clean: ## Remove all dangling images and images not referenced by any container 59 | docker image prune -a 60 | 61 | containers\:health: ## Check all containers health 62 | docker compose ${DC_RUN_ARGS} ps --format "table {{.Name}}\t{{.Service}}\t{{.Status}}" 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Laravel Docker Setup

3 |
4 |
5 | License 6 | GitHub release (latest by date) 7 | GitHub closed pull requests 8 |
9 | GitHub Workflow Status 10 | GitHub Workflow Status 11 | GitHub Workflow Status 12 | GitHub Workflow Status 13 |
14 |
15 | 16 | A finely tuned engine for deploying blazing-fast **Laravel Octane** applications. It combines the raw power of Octane with the streamlined efficiency of Docker and Docker Compose to create a **production-ready** environment that's ready to launch. 17 | 18 | ## Key Features 19 | 20 | * **Octane-Optimized**: Built specifically to harness the performance gains of Laravel Octane, whether you prefer **FrankenPHP**, **Swoole** or **RoadRunner**. 21 | * **Production-Ready Docker Compose:** A comprehensive Docker Compose file orchestrates a full stack, including: 22 | * **Traefik:** Intelligent reverse proxy for routing, load balancing, and secure access. 23 | * **PostgreSQL:** Robust and reliable database backend. 24 | * **Redis:** Lightning-fast caching for improved response times. 25 | * **Minio:** Scalable object storage for your application's assets. 26 | * **Typesense:** Powerful search engine to enhance user experience. 27 | * **pgAdmin & pghero:** Tools for database management and performance monitoring. 28 | * **Backup Service:** Automated backups to protect your valuable data. 29 | * **System Monitoring:** Glances and Netdata provide real-time insights into your infrastructure. 30 | * **Security Hardened:** Includes best practices for security, such as user authentication for exposed services and restricted container privileges. 31 | * **PHP Powerhouse:** Uses official PHP images (Debian or Alpine based) with pre-configured PHP runtime, JIT compiler, and OPcache for maximum performance. 32 | 33 | 34 | ## Laravel Container modes 35 | 36 | Easily launch your container in different modes to handle specific tasks: 37 | 38 | 39 | | Mode | `CONTAINER_MODE` value | Description 40 | | --------------------- | ---------------- | ---------------- | 41 | | HTTP Server (default) | `http` | Runs your Laravel Octane application. | 42 | | Horizon | `horizon` | Manages your queued jobs efficiently. | 43 | | Scheduler | `scheduler` | Executes scheduled tasks at defined intervals. | 44 | | Worker | `worker` | A dedicated worker for background processing. | 45 | | Reverb | `reverb` | Facilitates real-time communication with Laravel Echo. | 46 | 47 | ## Prerequisites 48 | 49 | - Docker installed on your system 50 | - Docker Compose installed on your system 51 | - Setup Laravel Octane, Laravel Horizon and Laravel Reverb 52 | 53 | ## Usage 54 | 55 | ### Building Docker image 56 | 57 | 1. Clone the repository: 58 | ``` 59 | git clone --depth 1 git@github.com:exaco/laravel-octane-dockerfile.git 60 | ``` 61 | 2. Copy the contents of the cloned directory, including the following items, into your Laravel project powered by Octane: 62 | - `deployment` directory 63 | - `.Dockerfile` 64 | - `.dockerignore` 65 | 66 | 1. Change the directory to your Laravel project 67 | 2. Build your image: 68 | ``` 69 | docker build -t : -f .Dockerfile . 70 | ``` 71 | 72 | ### Running Docker container 73 | 74 | ```bash 75 | # HTTP mode 76 | docker run -p :8000 --rm : 77 | 78 | # Horizon mode 79 | docker run -e CONTAINER_MODE=horizon --rm : 80 | 81 | # Scheduler mode 82 | docker run -e CONTAINER_MODE=scheduler --rm : 83 | 84 | # Reverb mode 85 | docker run -e CONTAINER_MODE=reverb --rm : 86 | 87 | # HTTP mode with Horizon 88 | docker run -e WITH_HORIZON=true -p :8000 --rm : 89 | 90 | # HTTP mode with Scheduler 91 | docker run -e WITH_SCHEDULER=true -p :8000 --rm : 92 | 93 | # HTTP mode with Scheduler and Horizon 94 | docker run \ 95 | -e WITH_SCHEDULER=true \ 96 | -e WITH_HORIZON=true \ 97 | -p :8000 \ 98 | --rm : 99 | 100 | # HTTP mode with Scheduler, Horizon and Reverb 101 | docker run \ 102 | -e WITH_SCHEDULER=true \ 103 | -e WITH_HORIZON=true \ 104 | -e WITH_REVERB=true \ 105 | -p :8000 \ 106 | --rm : 107 | 108 | # Worker mode 109 | docker run \ 110 | -e CONTAINER_MODE=worker \ 111 | -e WORKER_COMMAND="php /var/www/html/artisan foo:bar" \ 112 | --rm : 113 | 114 | # Running a single command 115 | docker run --rm : php artisan about 116 | ``` 117 | 118 | ### Docker Compose 119 | 120 | To deploy your application stack with Docker Compose: 121 | 1. Copy the following items to your code base: 122 | - `docker-compose.production.yml` 123 | - `.env.production` 124 | - `Makefile` 125 | 2. Edit `.env.production` and populate it with the appropriate values for your production environment variables (e.g., database credentials, API keys). 126 | 3. Run the following command in your project root directory to prevent permission issues: 127 | ```bash 128 | sudo mkdir -p storage/framework/{sessions,views,cache,testing} storage/logs && sudo chmod -R a+rw storage 129 | ``` 130 | 4. Run the command `make up` to start the containers. 131 | 132 | > [!NOTE] 133 | > The included `Makefile` offers a range of additional commands for managing your deployment, including options for rebuilding, stopping, and restarting services. 134 | 135 | > [!CAUTION] 136 | > Do not forget to edit `.env.production`! 137 | 138 | ## Configuration and Customization 139 | 140 | * You can use the `APP_ENV` build argument to specify a different environment file. 141 | 142 | ### Recommended `Swoole` options in `octane.php` 143 | 144 | ```php 145 | // config/octane.php 146 | 147 | return [ 148 | 'swoole' => [ 149 | 'options' => [ 150 | 'http_compression' => true, 151 | 'http_compression_level' => 6, // 1 - 9 152 | 'compression_min_length' => 20, 153 | 'package_max_length' => 2 * 1024 * 1024, // 2MB 154 | 'upload_max_filesize' => 20 * 1024 * 1024, // 20MB 155 | 'open_http2_protocol' => true, 156 | 'document_root' => public_path(), 157 | 'enable_static_handler' => true, 158 | ] 159 | ] 160 | ]; 161 | ``` 162 | 163 | ## Essential Notes 164 | 165 | * Some configurations are highly opinionated, so please make sure they align with your needs. 166 | * Laravel Octane logs request information only in the `local` environment. 167 | * Be mindful of the contents of the `.dockerignore` file. 168 | 169 | ## ToDo 170 | - [x] Add Docker Compose 171 | - [x] Add support for PHP 8.4 172 | - [x] Add support for worker mode 173 | - [x] Build assets with Bun 174 | - [x] Install more Caddy modules 175 | - [x] Create standalone and self-executable app 176 | - [x] Add support for Horizon 177 | - [x] Add support for RoadRunner 178 | - [x] Add support for FrankenPHP 179 | - [x] Add support for Laravel Reverb 180 | - [x] Add support for the full-stack apps (Front-end assets) 181 | - [ ] Add support `testing` environment and CI 182 | - [x] Add support for the Laravel scheduler 183 | - [ ] Add support for Laravel Dusk 184 | - [x] Support more PHP extensions 185 | - [x] Add tests 186 | - [x] Add Alpine-based images 187 | 188 | ## Contributing 189 | 190 | Thank you for considering contributing! If you find an issue, or have a better way to do something, feel free to open an 191 | issue, or a PR. 192 | 193 | ## Credits 194 | 195 | - [SMortexa](https://github.com/smortexa) 196 | - [All contributors](https://github.com/exaco/laravel-octane-dockerfile/graphs/contributors) 197 | 198 | ## License 199 | 200 | This repository is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 201 | -------------------------------------------------------------------------------- /RoadRunner.Alpine.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.4 2 | ARG COMPOSER_VERSION=2.8 3 | ARG BUN_VERSION="latest" 4 | ARG APP_ENV 5 | 6 | FROM composer:${COMPOSER_VERSION} AS vendor 7 | 8 | FROM php:${PHP_VERSION}-cli-alpine AS base 9 | 10 | LABEL maintainer="SMortexa " 11 | LABEL org.opencontainers.image.title="Laravel Octane Dockerfile" 12 | LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane" 13 | LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile 14 | LABEL org.opencontainers.image.licenses=MIT 15 | 16 | ARG WWWUSER=1000 17 | ARG WWWGROUP=1000 18 | ARG TZ=UTC 19 | ARG APP_ENV 20 | 21 | ENV TERM=xterm-color \ 22 | WITH_HORIZON=false \ 23 | WITH_SCHEDULER=false \ 24 | OCTANE_SERVER=roadrunner \ 25 | TZ=${TZ} \ 26 | USER=octane \ 27 | APP_ENV=${APP_ENV} \ 28 | ROOT=/var/www/html \ 29 | COMPOSER_FUND=0 \ 30 | COMPOSER_MAX_PARALLEL_HTTP=24 31 | 32 | WORKDIR ${ROOT} 33 | 34 | SHELL ["/bin/sh", "-eou", "pipefail", "-c"] 35 | 36 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ 37 | && echo ${TZ} > /etc/timezone 38 | 39 | ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ 40 | 41 | RUN apk update; \ 42 | apk upgrade; \ 43 | apk add --no-cache \ 44 | curl \ 45 | wget \ 46 | vim \ 47 | tzdata \ 48 | git \ 49 | ncdu \ 50 | procps \ 51 | unzip \ 52 | ca-certificates \ 53 | supervisor \ 54 | libsodium-dev \ 55 | brotli \ 56 | # Install PHP extensions 57 | && install-php-extensions \ 58 | bz2 \ 59 | pcntl \ 60 | mbstring \ 61 | bcmath \ 62 | sockets \ 63 | pgsql \ 64 | pdo_pgsql \ 65 | opcache \ 66 | exif \ 67 | pdo_mysql \ 68 | zip \ 69 | uv \ 70 | vips \ 71 | intl \ 72 | gd \ 73 | redis \ 74 | rdkafka \ 75 | memcached \ 76 | igbinary \ 77 | ldap \ 78 | && docker-php-source delete \ 79 | && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* 80 | 81 | RUN arch="$(apk --print-arch)" \ 82 | && case "$arch" in \ 83 | armhf) _cronic_fname='supercronic-linux-arm' ;; \ 84 | aarch64) _cronic_fname='supercronic-linux-arm64' ;; \ 85 | x86_64) _cronic_fname='supercronic-linux-amd64' ;; \ 86 | x86) _cronic_fname='supercronic-linux-386' ;; \ 87 | *) echo >&2 "error: unsupported architecture: $arch"; exit 1 ;; \ 88 | esac \ 89 | && wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/${_cronic_fname}" \ 90 | -O /usr/bin/supercronic \ 91 | && chmod +x /usr/bin/supercronic \ 92 | && mkdir -p /etc/supercronic \ 93 | && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel 94 | 95 | RUN addgroup -g ${WWWGROUP} ${USER} \ 96 | && adduser -D -h ${ROOT} -G ${USER} -u ${WWWUSER} -s /bin/sh ${USER} 97 | 98 | RUN mkdir -p /var/log/supervisor /var/run/supervisor \ 99 | && chown -R ${USER}:${USER} ${ROOT} /var/log /var/run \ 100 | && chmod -R a+rw ${ROOT} /var/log /var/run 101 | 102 | RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini 103 | 104 | USER ${USER} 105 | 106 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=vendor /usr/bin/composer /usr/bin/composer 107 | 108 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.conf /etc/ 109 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/RoadRunner/supervisord.roadrunner.conf /etc/supervisor/conf.d/ 110 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.*.conf /etc/supervisor/conf.d/ 111 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini 112 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/RoadRunner/.rr.prod.yaml ./.rr.yaml 113 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/start-container /usr/local/bin/start-container 114 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/healthcheck /usr/local/bin/healthcheck 115 | 116 | RUN chmod +x /usr/local/bin/start-container /usr/local/bin/healthcheck 117 | 118 | ########################################### 119 | 120 | FROM base AS common 121 | 122 | USER ${USER} 123 | 124 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 125 | 126 | RUN composer install \ 127 | --no-dev \ 128 | --no-interaction \ 129 | --no-autoloader \ 130 | --no-ansi \ 131 | --no-scripts \ 132 | --audit 133 | 134 | ########################################### 135 | # Build frontend assets with Bun 136 | ########################################### 137 | 138 | FROM oven/bun:${BUN_VERSION} AS build 139 | 140 | ARG APP_ENV 141 | 142 | ENV ROOT=/var/www/html \ 143 | APP_ENV=${APP_ENV} \ 144 | NODE_ENV=${APP_ENV:-production} 145 | 146 | WORKDIR ${ROOT} 147 | 148 | COPY --link package.json bun.lock* ./ 149 | 150 | RUN bun install --frozen-lockfile 151 | 152 | COPY --link . . 153 | COPY --link --from=common ${ROOT}/vendor vendor 154 | 155 | RUN bun run build 156 | 157 | ########################################### 158 | 159 | FROM common AS runner 160 | 161 | USER ${USER} 162 | 163 | ENV WITH_HORIZON=false \ 164 | WITH_SCHEDULER=false \ 165 | WITH_REVERB=false 166 | 167 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 168 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public 169 | 170 | RUN mkdir -p \ 171 | storage/framework/sessions \ 172 | storage/framework/views \ 173 | storage/framework/cache \ 174 | storage/framework/testing \ 175 | storage/logs \ 176 | bootstrap/cache && chmod -R a+rw storage 177 | 178 | RUN composer install \ 179 | --classmap-authoritative \ 180 | --no-interaction \ 181 | --no-ansi \ 182 | --no-dev \ 183 | && composer clear-cache 184 | 185 | RUN if composer show | grep spiral/roadrunner-cli >/dev/null; then \ 186 | ./vendor/bin/rr get-binary --quiet; else \ 187 | echo "`spiral/roadrunner-cli` package is not installed. Exiting..."; exit 1; \ 188 | fi 189 | 190 | RUN chmod +x rr 191 | 192 | EXPOSE 8000 193 | EXPOSE 6001 194 | EXPOSE 8080 195 | 196 | ENTRYPOINT ["start-container"] 197 | 198 | HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD healthcheck || exit 1 -------------------------------------------------------------------------------- /RoadRunner.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.4 2 | ARG COMPOSER_VERSION=2.8 3 | ARG BUN_VERSION="latest" 4 | ARG APP_ENV 5 | 6 | FROM composer:${COMPOSER_VERSION} AS vendor 7 | 8 | FROM php:${PHP_VERSION}-cli-bookworm AS base 9 | 10 | LABEL maintainer="SMortexa " 11 | LABEL org.opencontainers.image.title="Laravel Octane Dockerfile" 12 | LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane" 13 | LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile 14 | LABEL org.opencontainers.image.licenses=MIT 15 | 16 | ARG WWWUSER=1000 17 | ARG WWWGROUP=1000 18 | ARG TZ=UTC 19 | ARG APP_ENV 20 | 21 | ENV DEBIAN_FRONTEND=noninteractive \ 22 | TERM=xterm-color \ 23 | OCTANE_SERVER=roadrunner \ 24 | TZ=${TZ} \ 25 | USER=octane \ 26 | APP_ENV=${APP_ENV} \ 27 | ROOT=/var/www/html \ 28 | COMPOSER_FUND=0 \ 29 | COMPOSER_MAX_PARALLEL_HTTP=24 30 | 31 | WORKDIR ${ROOT} 32 | 33 | SHELL ["/bin/bash", "-eou", "pipefail", "-c"] 34 | 35 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ 36 | && echo ${TZ} > /etc/timezone 37 | 38 | ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ 39 | 40 | RUN apt-get update; \ 41 | apt-get upgrade -yqq; \ 42 | apt-get install -yqq --no-install-recommends --show-progress \ 43 | apt-utils \ 44 | curl \ 45 | wget \ 46 | vim \ 47 | git \ 48 | ncdu \ 49 | procps \ 50 | unzip \ 51 | ca-certificates \ 52 | supervisor \ 53 | libsodium-dev \ 54 | libbrotli-dev \ 55 | # Install PHP extensions 56 | && install-php-extensions \ 57 | bz2 \ 58 | pcntl \ 59 | mbstring \ 60 | bcmath \ 61 | sockets \ 62 | pgsql \ 63 | pdo_pgsql \ 64 | opcache \ 65 | exif \ 66 | pdo_mysql \ 67 | zip \ 68 | uv \ 69 | vips \ 70 | intl \ 71 | gd \ 72 | redis \ 73 | rdkafka \ 74 | memcached \ 75 | igbinary \ 76 | ldap \ 77 | && apt-get -y autoremove \ 78 | && apt-get clean \ 79 | && docker-php-source delete \ 80 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ 81 | && rm /var/log/lastlog /var/log/faillog 82 | 83 | RUN arch="$(uname -m)" \ 84 | && case "$arch" in \ 85 | armhf) _cronic_fname='supercronic-linux-arm' ;; \ 86 | aarch64) _cronic_fname='supercronic-linux-arm64' ;; \ 87 | x86_64) _cronic_fname='supercronic-linux-amd64' ;; \ 88 | x86) _cronic_fname='supercronic-linux-386' ;; \ 89 | *) echo >&2 "error: unsupported architecture: $arch"; exit 1 ;; \ 90 | esac \ 91 | && wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/${_cronic_fname}" \ 92 | -O /usr/bin/supercronic \ 93 | && chmod +x /usr/bin/supercronic \ 94 | && mkdir -p /etc/supercronic \ 95 | && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel 96 | 97 | RUN userdel --remove --force www-data \ 98 | && groupadd --force -g ${WWWGROUP} ${USER} \ 99 | && useradd -ms /bin/bash --no-log-init --no-user-group -g ${WWWGROUP} -u ${WWWUSER} ${USER} 100 | 101 | RUN chown -R ${USER}:${USER} ${ROOT} /var/{log,run} \ 102 | && chmod -R a+rw ${ROOT} /var/{log,run} 103 | 104 | RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini 105 | 106 | USER ${USER} 107 | 108 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=vendor /usr/bin/composer /usr/bin/composer 109 | 110 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.conf /etc/ 111 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/RoadRunner/supervisord.roadrunner.conf /etc/supervisor/conf.d/ 112 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.*.conf /etc/supervisor/conf.d/ 113 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini 114 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/RoadRunner/.rr.prod.yaml ./.rr.yaml 115 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/start-container /usr/local/bin/start-container 116 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/healthcheck /usr/local/bin/healthcheck 117 | 118 | RUN chmod +x /usr/local/bin/start-container /usr/local/bin/healthcheck 119 | 120 | ########################################### 121 | 122 | FROM base AS common 123 | 124 | USER ${USER} 125 | 126 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 127 | 128 | RUN composer install \ 129 | --no-dev \ 130 | --no-interaction \ 131 | --no-autoloader \ 132 | --no-ansi \ 133 | --no-scripts \ 134 | --audit 135 | 136 | ########################################### 137 | # Build frontend assets with Bun 138 | ########################################### 139 | 140 | FROM oven/bun:${BUN_VERSION} AS build 141 | 142 | ARG APP_ENV 143 | 144 | ENV ROOT=/var/www/html \ 145 | APP_ENV=${APP_ENV} \ 146 | NODE_ENV=${APP_ENV:-production} 147 | 148 | WORKDIR ${ROOT} 149 | 150 | COPY --link package.json bun.lock* ./ 151 | 152 | RUN bun install --frozen-lockfile 153 | 154 | COPY --link . . 155 | COPY --link --from=common ${ROOT}/vendor vendor 156 | 157 | RUN bun run build 158 | 159 | ########################################### 160 | 161 | FROM common AS runner 162 | 163 | USER ${USER} 164 | 165 | ENV WITH_HORIZON=false \ 166 | WITH_SCHEDULER=false \ 167 | WITH_REVERB=false 168 | 169 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 170 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public 171 | 172 | RUN mkdir -p \ 173 | storage/framework/{sessions,views,cache,testing} \ 174 | storage/logs \ 175 | bootstrap/cache && chmod -R a+rw storage 176 | 177 | RUN composer install \ 178 | --classmap-authoritative \ 179 | --no-interaction \ 180 | --no-ansi \ 181 | --no-dev \ 182 | && composer clear-cache 183 | 184 | RUN if composer show | grep spiral/roadrunner-cli >/dev/null; then \ 185 | ./vendor/bin/rr get-binary --quiet; else \ 186 | echo "`spiral/roadrunner-cli` package is not installed. Exiting..."; exit 1; \ 187 | fi 188 | 189 | RUN chmod +x rr 190 | 191 | EXPOSE 8000 192 | EXPOSE 6001 193 | EXPOSE 8080 194 | 195 | ENTRYPOINT ["start-container"] 196 | 197 | HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD healthcheck || exit 1 198 | -------------------------------------------------------------------------------- /Swoole.Alpine.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.4 2 | ARG COMPOSER_VERSION=2.8 3 | ARG BUN_VERSION="latest" 4 | ARG APP_ENV 5 | 6 | FROM composer:${COMPOSER_VERSION} AS vendor 7 | 8 | FROM php:${PHP_VERSION}-cli-alpine AS base 9 | 10 | LABEL maintainer="SMortexa " 11 | LABEL org.opencontainers.image.title="Laravel Octane Dockerfile" 12 | LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane" 13 | LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile 14 | LABEL org.opencontainers.image.licenses=MIT 15 | 16 | ARG WWWUSER=1000 17 | ARG WWWGROUP=1000 18 | ARG TZ=UTC 19 | ARG APP_ENV 20 | 21 | ENV TERM=xterm-color \ 22 | WITH_HORIZON=false \ 23 | WITH_SCHEDULER=false \ 24 | OCTANE_SERVER=swoole \ 25 | TZ=${TZ} \ 26 | USER=octane \ 27 | APP_ENV=${APP_ENV} \ 28 | ROOT=/var/www/html \ 29 | COMPOSER_FUND=0 \ 30 | COMPOSER_MAX_PARALLEL_HTTP=24 31 | 32 | WORKDIR ${ROOT} 33 | 34 | SHELL ["/bin/sh", "-eou", "pipefail", "-c"] 35 | 36 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ 37 | && echo ${TZ} > /etc/timezone 38 | 39 | ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ 40 | 41 | RUN apk update; \ 42 | apk upgrade; \ 43 | apk add --no-cache \ 44 | curl \ 45 | wget \ 46 | vim \ 47 | tzdata \ 48 | git \ 49 | ncdu \ 50 | procps \ 51 | unzip \ 52 | ca-certificates \ 53 | supervisor \ 54 | libsodium-dev \ 55 | brotli \ 56 | # Install PHP extensions 57 | && install-php-extensions \ 58 | bz2 \ 59 | pcntl \ 60 | mbstring \ 61 | bcmath \ 62 | sockets \ 63 | pgsql \ 64 | pdo_pgsql \ 65 | opcache \ 66 | exif \ 67 | pdo_mysql \ 68 | zip \ 69 | uv \ 70 | vips \ 71 | intl \ 72 | gd \ 73 | redis \ 74 | rdkafka \ 75 | memcached \ 76 | igbinary \ 77 | ldap \ 78 | swoole \ 79 | && docker-php-source delete \ 80 | && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* 81 | 82 | RUN arch="$(apk --print-arch)" \ 83 | && case "$arch" in \ 84 | armhf) _cronic_fname='supercronic-linux-arm' ;; \ 85 | aarch64) _cronic_fname='supercronic-linux-arm64' ;; \ 86 | x86_64) _cronic_fname='supercronic-linux-amd64' ;; \ 87 | x86) _cronic_fname='supercronic-linux-386' ;; \ 88 | *) echo >&2 "error: unsupported architecture: $arch"; exit 1 ;; \ 89 | esac \ 90 | && wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/${_cronic_fname}" \ 91 | -O /usr/bin/supercronic \ 92 | && chmod +x /usr/bin/supercronic \ 93 | && mkdir -p /etc/supercronic \ 94 | && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel 95 | 96 | RUN addgroup -g ${WWWGROUP} ${USER} \ 97 | && adduser -D -h ${ROOT} -G ${USER} -u ${WWWUSER} -s /bin/sh ${USER} 98 | 99 | RUN mkdir -p /var/log/supervisor /var/run/supervisor \ 100 | && chown -R ${USER}:${USER} ${ROOT} /var/log /var/run \ 101 | && chmod -R a+rw ${ROOT} /var/log /var/run 102 | 103 | RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini 104 | 105 | USER ${USER} 106 | 107 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=vendor /usr/bin/composer /usr/bin/composer 108 | 109 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.conf /etc/ 110 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/Swoole/supervisord.swoole.conf /etc/supervisor/conf.d/ 111 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.*.conf /etc/supervisor/conf.d/ 112 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini 113 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/start-container /usr/local/bin/start-container 114 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/healthcheck /usr/local/bin/healthcheck 115 | 116 | RUN chmod +x /usr/local/bin/start-container /usr/local/bin/healthcheck 117 | 118 | ########################################### 119 | 120 | FROM base AS common 121 | 122 | USER ${USER} 123 | 124 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 125 | 126 | RUN composer install \ 127 | --no-dev \ 128 | --no-interaction \ 129 | --no-autoloader \ 130 | --no-ansi \ 131 | --no-scripts \ 132 | --audit 133 | 134 | ########################################### 135 | # Build frontend assets with Bun 136 | ########################################### 137 | 138 | FROM oven/bun:${BUN_VERSION} AS build 139 | 140 | ARG APP_ENV 141 | 142 | ENV ROOT=/var/www/html \ 143 | APP_ENV=${APP_ENV} \ 144 | NODE_ENV=${APP_ENV:-production} 145 | 146 | WORKDIR ${ROOT} 147 | 148 | COPY --link package.json bun.lock* ./ 149 | 150 | RUN bun install --frozen-lockfile 151 | 152 | COPY --link . . 153 | COPY --link --from=common ${ROOT}/vendor vendor 154 | 155 | RUN bun run build 156 | 157 | ########################################### 158 | 159 | FROM common AS runner 160 | 161 | USER ${USER} 162 | 163 | ENV WITH_HORIZON=false \ 164 | WITH_SCHEDULER=false \ 165 | WITH_REVERB=false 166 | 167 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 168 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public 169 | 170 | RUN mkdir -p \ 171 | storage/framework/sessions \ 172 | storage/framework/views \ 173 | storage/framework/cache \ 174 | storage/framework/testing \ 175 | storage/logs \ 176 | bootstrap/cache && chmod -R a+rw storage 177 | 178 | RUN composer install \ 179 | --classmap-authoritative \ 180 | --no-interaction \ 181 | --no-ansi \ 182 | --no-dev \ 183 | && composer clear-cache 184 | 185 | EXPOSE 8000 186 | EXPOSE 8080 187 | 188 | ENTRYPOINT ["start-container"] 189 | 190 | HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD healthcheck || exit 1 -------------------------------------------------------------------------------- /Swoole.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.4 2 | ARG COMPOSER_VERSION=2.8 3 | ARG BUN_VERSION="latest" 4 | ARG APP_ENV 5 | 6 | FROM composer:${COMPOSER_VERSION} AS vendor 7 | 8 | FROM php:${PHP_VERSION}-cli-bookworm AS base 9 | 10 | LABEL maintainer="SMortexa " 11 | LABEL org.opencontainers.image.title="Laravel Octane Dockerfile" 12 | LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane" 13 | LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile 14 | LABEL org.opencontainers.image.licenses=MIT 15 | 16 | ARG WWWUSER=1000 17 | ARG WWWGROUP=1000 18 | ARG TZ=UTC 19 | ARG APP_ENV 20 | 21 | ENV DEBIAN_FRONTEND=noninteractive \ 22 | TERM=xterm-color \ 23 | OCTANE_SERVER=swoole \ 24 | TZ=${TZ} \ 25 | USER=octane \ 26 | APP_ENV=${APP_ENV} \ 27 | ROOT=/var/www/html \ 28 | COMPOSER_FUND=0 \ 29 | COMPOSER_MAX_PARALLEL_HTTP=24 30 | 31 | WORKDIR ${ROOT} 32 | 33 | SHELL ["/bin/bash", "-eou", "pipefail", "-c"] 34 | 35 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ 36 | && echo ${TZ} > /etc/timezone 37 | 38 | ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ 39 | 40 | RUN apt-get update; \ 41 | apt-get upgrade -yqq; \ 42 | apt-get install -yqq --no-install-recommends --show-progress \ 43 | apt-utils \ 44 | curl \ 45 | wget \ 46 | vim \ 47 | git \ 48 | ncdu \ 49 | procps \ 50 | unzip \ 51 | ca-certificates \ 52 | supervisor \ 53 | libsodium-dev \ 54 | libbrotli-dev \ 55 | # Install PHP extensions 56 | && install-php-extensions \ 57 | bz2 \ 58 | pcntl \ 59 | mbstring \ 60 | bcmath \ 61 | sockets \ 62 | pgsql \ 63 | pdo_pgsql \ 64 | opcache \ 65 | exif \ 66 | pdo_mysql \ 67 | zip \ 68 | uv \ 69 | vips \ 70 | intl \ 71 | gd \ 72 | redis \ 73 | rdkafka \ 74 | memcached \ 75 | igbinary \ 76 | ldap \ 77 | swoole \ 78 | && apt-get -y autoremove \ 79 | && apt-get clean \ 80 | && docker-php-source delete \ 81 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ 82 | && rm /var/log/lastlog /var/log/faillog 83 | 84 | RUN arch="$(uname -m)" \ 85 | && case "$arch" in \ 86 | armhf) _cronic_fname='supercronic-linux-arm' ;; \ 87 | aarch64) _cronic_fname='supercronic-linux-arm64' ;; \ 88 | x86_64) _cronic_fname='supercronic-linux-amd64' ;; \ 89 | x86) _cronic_fname='supercronic-linux-386' ;; \ 90 | *) echo >&2 "error: unsupported architecture: $arch"; exit 1 ;; \ 91 | esac \ 92 | && wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/${_cronic_fname}" \ 93 | -O /usr/bin/supercronic \ 94 | && chmod +x /usr/bin/supercronic \ 95 | && mkdir -p /etc/supercronic \ 96 | && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel 97 | 98 | RUN userdel --remove --force www-data \ 99 | && groupadd --force -g ${WWWGROUP} ${USER} \ 100 | && useradd -ms /bin/bash --no-log-init --no-user-group -g ${WWWGROUP} -u ${WWWUSER} ${USER} 101 | 102 | RUN chown -R ${USER}:${USER} ${ROOT} /var/{log,run} \ 103 | && chmod -R a+rw ${ROOT} /var/{log,run} 104 | 105 | RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini 106 | 107 | USER ${USER} 108 | 109 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=vendor /usr/bin/composer /usr/bin/composer 110 | 111 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.conf /etc/ 112 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/Swoole/supervisord.swoole.conf /etc/supervisor/conf.d/ 113 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.*.conf /etc/supervisor/conf.d/ 114 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini 115 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/start-container /usr/local/bin/start-container 116 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/healthcheck /usr/local/bin/healthcheck 117 | 118 | RUN chmod +x /usr/local/bin/start-container /usr/local/bin/healthcheck 119 | 120 | ########################################### 121 | 122 | FROM base AS common 123 | 124 | USER ${USER} 125 | 126 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 127 | 128 | RUN composer install \ 129 | --no-dev \ 130 | --no-interaction \ 131 | --no-autoloader \ 132 | --no-ansi \ 133 | --no-scripts \ 134 | --audit 135 | 136 | ########################################### 137 | # Build frontend assets with Bun 138 | ########################################### 139 | 140 | FROM oven/bun:${BUN_VERSION} AS build 141 | 142 | ARG APP_ENV 143 | 144 | ENV ROOT=/var/www/html \ 145 | APP_ENV=${APP_ENV} \ 146 | NODE_ENV=${APP_ENV:-production} 147 | 148 | WORKDIR ${ROOT} 149 | 150 | COPY --link package.json bun.lock* ./ 151 | 152 | RUN bun install --frozen-lockfile 153 | 154 | COPY --link . . 155 | COPY --link --from=common ${ROOT}/vendor vendor 156 | 157 | RUN bun run build 158 | 159 | ########################################### 160 | 161 | FROM common AS runner 162 | 163 | USER ${USER} 164 | 165 | ENV WITH_HORIZON=false \ 166 | WITH_SCHEDULER=false \ 167 | WITH_REVERB=false 168 | 169 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 170 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public 171 | 172 | RUN mkdir -p \ 173 | storage/framework/{sessions,views,cache,testing} \ 174 | storage/logs \ 175 | bootstrap/cache && chmod -R a+rw storage 176 | 177 | RUN composer install \ 178 | --classmap-authoritative \ 179 | --no-interaction \ 180 | --no-ansi \ 181 | --no-dev \ 182 | && composer clear-cache 183 | 184 | EXPOSE 8000 185 | EXPOSE 8080 186 | 187 | ENTRYPOINT ["start-container"] 188 | 189 | HEALTHCHECK --start-period=5s --interval=2s --timeout=5s --retries=8 CMD healthcheck || exit 1 -------------------------------------------------------------------------------- /deployment/healthcheck: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | container_mode=${CONTAINER_MODE:-"http"} 6 | 7 | if [ "${container_mode}" = "http" ]; then 8 | php "${ROOT}/artisan" octane:status 9 | elif [ "${container_mode}" = "horizon" ]; then 10 | php "${ROOT}/artisan" horizon:status 11 | elif [ "${container_mode}" = "scheduler" ]; then 12 | if [ "$(supervisorctl status scheduler:scheduler_0 | awk '{print tolower($2)}')" = "running" ]; then 13 | exit 0 14 | else 15 | echo "Healthcheck failed." 16 | exit 1 17 | fi 18 | elif [ "${container_mode}" = "reverb" ]; then 19 | if [ "$(supervisorctl status reverb:reverb_0 | awk '{print tolower($2)}')" = "running" ]; then 20 | exit 0 21 | else 22 | echo "Healthcheck failed." 23 | exit 1 24 | fi 25 | elif [ "${container_mode}" = "worker" ]; then 26 | if [ "$(supervisorctl status worker:worker_0 | awk '{print tolower($2)}')" = "running" ]; then 27 | exit 0 28 | else 29 | echo "Healthcheck failed." 30 | exit 1 31 | fi 32 | else 33 | echo "Container mode mismatched." 34 | exit 1 35 | fi 36 | -------------------------------------------------------------------------------- /deployment/octane/FrankenPHP/Caddyfile: -------------------------------------------------------------------------------- 1 | { 2 | {$CADDY_GLOBAL_OPTIONS} 3 | 4 | admin {$CADDY_SERVER_ADMIN_HOST}:{$CADDY_SERVER_ADMIN_PORT} 5 | 6 | frankenphp { 7 | worker "{$APP_PUBLIC_PATH}/frankenphp-worker.php" {$CADDY_SERVER_WORKER_COUNT} 8 | } 9 | 10 | metrics { 11 | per_host 12 | } 13 | 14 | servers { 15 | protocols h1 16 | } 17 | } 18 | 19 | {$CADDY_EXTRA_CONFIG} 20 | 21 | {$CADDY_SERVER_SERVER_NAME} { 22 | log { 23 | level WARN 24 | 25 | format filter { 26 | wrap {$CADDY_SERVER_LOGGER} 27 | fields { 28 | uri query { 29 | replace authorization REDACTED 30 | } 31 | } 32 | } 33 | } 34 | 35 | route { 36 | root * "{$APP_PUBLIC_PATH}" 37 | encode zstd br gzip 38 | 39 | {$CADDY_SERVER_EXTRA_DIRECTIVES} 40 | 41 | request_body { 42 | max_size 500MB 43 | } 44 | 45 | @static { 46 | file 47 | path *.js *.css *.jpg *.jpeg *.webp *.weba *.webm *.gif *.png *.ico *.cur *.gz *.svg *.svgz *.mp4 *.mp3 *.ogg *.ogv *.htc *.woff2 *.woff 48 | } 49 | 50 | @staticshort { 51 | file 52 | path *.json *.xml *.rss 53 | } 54 | 55 | header @static Cache-Control "public, immutable, stale-while-revalidate, max-age=31536000" 56 | 57 | header @staticshort Cache-Control "no-cache, max-age=3600" 58 | 59 | @rejected `path('*.bak', '*.conf', '*.dist', '*.fla', '*.ini', '*.inc', '*.inci', '*.log', '*.orig', '*.psd', '*.sh', '*.sql', '*.swo', '*.swp', '*.swop', '*/.*') && !path('*/.well-known/')` 60 | error @rejected 401 61 | 62 | php_server { 63 | index frankenphp-worker.php 64 | try_files {path} frankenphp-worker.php 65 | resolve_root_symlink 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /deployment/octane/FrankenPHP/supervisord.frankenphp.conf: -------------------------------------------------------------------------------- 1 | [program:octane] 2 | process_name = %(program_name)s_%(process_num)s 3 | command = php %(ENV_ROOT)s/artisan octane:frankenphp --host=0.0.0.0 --port=8000 --admin-port=2019 --caddyfile=%(ENV_ROOT)s/deployment/octane/FrankenPHP/Caddyfile 4 | user = %(ENV_USER)s 5 | priority = 1 6 | autostart = true 7 | autorestart = true 8 | environment = LARAVEL_OCTANE = "1" 9 | stdout_logfile = /dev/stdout 10 | stdout_logfile_maxbytes = 0 11 | stderr_logfile = /dev/stderr 12 | stderr_logfile_maxbytes = 0 13 | 14 | [program:horizon] 15 | process_name = %(program_name)s_%(process_num)s 16 | command = php %(ENV_ROOT)s/artisan horizon 17 | user = %(ENV_USER)s 18 | priority = 3 19 | autostart = %(ENV_WITH_HORIZON)s 20 | autorestart = true 21 | stdout_logfile = %(ENV_ROOT)s/storage/logs/horizon.log 22 | stdout_logfile_maxbytes = 200MB 23 | stderr_logfile = %(ENV_ROOT)s/storage/logs/horizon.log 24 | stderr_logfile_maxbytes = 200MB 25 | stopwaitsecs = 3600 26 | 27 | [program:scheduler] 28 | process_name = %(program_name)s_%(process_num)s 29 | command = supercronic -overlapping /etc/supercronic/laravel 30 | user = %(ENV_USER)s 31 | autostart = %(ENV_WITH_SCHEDULER)s 32 | autorestart = true 33 | stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 34 | stdout_logfile_maxbytes = 200MB 35 | stderr_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 36 | stderr_logfile_maxbytes = 200MB 37 | 38 | [program:clear-scheduler-cache] 39 | process_name = %(program_name)s_%(process_num)s 40 | command = php %(ENV_ROOT)s/artisan schedule:clear-cache 41 | user = %(ENV_USER)s 42 | autostart = %(ENV_WITH_SCHEDULER)s 43 | autorestart = false 44 | startsecs = 0 45 | startretries = 1 46 | stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 47 | stdout_logfile_maxbytes = 200MB 48 | stderr_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 49 | stderr_logfile_maxbytes = 200MB 50 | 51 | [program:reverb] 52 | process_name = %(program_name)s_%(process_num)s 53 | command = php %(ENV_ROOT)s/artisan reverb:start 54 | user = %(ENV_USER)s 55 | priority = 2 56 | autostart = %(ENV_WITH_REVERB)s 57 | autorestart = true 58 | stdout_logfile = %(ENV_ROOT)s/storage/logs/reverb.log 59 | stdout_logfile_maxbytes = 200MB 60 | stderr_logfile = %(ENV_ROOT)s/storage/logs/reverb.log 61 | stderr_logfile_maxbytes = 200MB 62 | minfds = 10000 63 | 64 | [include] 65 | files = /etc/supervisord.conf -------------------------------------------------------------------------------- /deployment/octane/RoadRunner/.rr.prod.yaml: -------------------------------------------------------------------------------- 1 | version: '2.7' 2 | rpc: 3 | listen: 'tcp://127.0.0.1:6001' 4 | server: 5 | relay: pipes 6 | http: 7 | middleware: [ "static", "gzip", "headers" ] 8 | max_request_size: 20 9 | static: 10 | dir: "public" 11 | forbid: [ ".php", ".htaccess" ] 12 | uploads: 13 | forbid: [".php", ".exe", ".bat", ".sh"] 14 | pool: 15 | allocate_timeout: 10s 16 | destroy_timeout: 10s 17 | supervisor: 18 | max_worker_memory: 128 19 | exec_ttl: 60s 20 | logs: 21 | mode: production 22 | level: debug 23 | encoding: json 24 | status: 25 | address: localhost:2114 26 | -------------------------------------------------------------------------------- /deployment/octane/RoadRunner/supervisord.roadrunner.conf: -------------------------------------------------------------------------------- 1 | [program:octane] 2 | process_name = %(program_name)s_%(process_num)s 3 | command = php %(ENV_ROOT)s/artisan octane:roadrunner --host=0.0.0.0 --port=8000 --rpc-port=6001 --rr-config=%(ENV_ROOT)s/.rr.yaml 4 | user = %(ENV_USER)s 5 | priority = 1 6 | autostart = true 7 | autorestart = true 8 | environment = LARAVEL_OCTANE = "1" 9 | stdout_logfile = /dev/stdout 10 | stdout_logfile_maxbytes = 0 11 | stderr_logfile = /dev/stderr 12 | stderr_logfile_maxbytes = 0 13 | 14 | [program:horizon] 15 | process_name = %(program_name)s_%(process_num)s 16 | command = php %(ENV_ROOT)s/artisan horizon 17 | user = %(ENV_USER)s 18 | priority = 3 19 | autostart = %(ENV_WITH_HORIZON)s 20 | autorestart = true 21 | stdout_logfile = %(ENV_ROOT)s/storage/logs/horizon.log 22 | stdout_logfile_maxbytes = 200MB 23 | stderr_logfile = %(ENV_ROOT)s/storage/logs/horizon.log 24 | stderr_logfile_maxbytes = 200MB 25 | stopwaitsecs = 3600 26 | 27 | [program:scheduler] 28 | process_name = %(program_name)s_%(process_num)s 29 | command = supercronic -overlapping /etc/supercronic/laravel 30 | user = %(ENV_USER)s 31 | autostart = %(ENV_WITH_SCHEDULER)s 32 | autorestart = true 33 | stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 34 | stdout_logfile_maxbytes = 200MB 35 | stderr_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 36 | stderr_logfile_maxbytes = 200MB 37 | 38 | [program:clear-scheduler-cache] 39 | process_name = %(program_name)s_%(process_num)s 40 | command = php %(ENV_ROOT)s/artisan schedule:clear-cache 41 | user = %(ENV_USER)s 42 | autostart = %(ENV_WITH_SCHEDULER)s 43 | autorestart = false 44 | startsecs = 0 45 | startretries = 1 46 | stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 47 | stdout_logfile_maxbytes = 200MB 48 | stderr_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 49 | stderr_logfile_maxbytes = 200MB 50 | 51 | [program:reverb] 52 | process_name = %(program_name)s_%(process_num)s 53 | command = php %(ENV_ROOT)s/artisan reverb:start 54 | user = %(ENV_USER)s 55 | priority = 2 56 | autostart = %(ENV_WITH_REVERB)s 57 | autorestart = true 58 | stdout_logfile = %(ENV_ROOT)s/storage/logs/reverb.log 59 | stdout_logfile_maxbytes = 200MB 60 | stderr_logfile = %(ENV_ROOT)s/storage/logs/reverb.log 61 | stderr_logfile_maxbytes = 200MB 62 | minfds = 10000 63 | 64 | [include] 65 | files = /etc/supervisord.conf -------------------------------------------------------------------------------- /deployment/octane/Swoole/supervisord.swoole.conf: -------------------------------------------------------------------------------- 1 | [program:octane] 2 | process_name = %(program_name)s_%(process_num)s 3 | command = php %(ENV_ROOT)s/artisan octane:swoole --host=0.0.0.0 --port=8000 4 | user = %(ENV_USER)s 5 | priority = 1 6 | autostart = true 7 | autorestart = true 8 | environment = LARAVEL_OCTANE = "1" 9 | stdout_logfile = /dev/stdout 10 | stdout_logfile_maxbytes = 0 11 | stderr_logfile = /dev/stderr 12 | stderr_logfile_maxbytes = 0 13 | 14 | [program:horizon] 15 | process_name = %(program_name)s_%(process_num)s 16 | command = php %(ENV_ROOT)s/artisan horizon 17 | user = %(ENV_USER)s 18 | priority = 3 19 | autostart = %(ENV_WITH_HORIZON)s 20 | autorestart = true 21 | stdout_logfile = %(ENV_ROOT)s/storage/logs/horizon.log 22 | stdout_logfile_maxbytes = 200MB 23 | stderr_logfile = %(ENV_ROOT)s/storage/logs/horizon.log 24 | stderr_logfile_maxbytes = 200MB 25 | stopwaitsecs = 3600 26 | 27 | [program:scheduler] 28 | process_name = %(program_name)s_%(process_num)s 29 | command = supercronic -overlapping /etc/supercronic/laravel 30 | user = %(ENV_USER)s 31 | autostart = %(ENV_WITH_SCHEDULER)s 32 | autorestart = true 33 | stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 34 | stdout_logfile_maxbytes = 200MB 35 | stderr_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 36 | stderr_logfile_maxbytes = 200MB 37 | 38 | [program:clear-scheduler-cache] 39 | process_name = %(program_name)s_%(process_num)s 40 | command = php %(ENV_ROOT)s/artisan schedule:clear-cache 41 | user = %(ENV_USER)s 42 | autostart = %(ENV_WITH_SCHEDULER)s 43 | autorestart = false 44 | startsecs = 0 45 | startretries = 1 46 | stdout_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 47 | stdout_logfile_maxbytes = 200MB 48 | stderr_logfile = %(ENV_ROOT)s/storage/logs/scheduler.log 49 | stderr_logfile_maxbytes = 200MB 50 | 51 | [program:reverb] 52 | process_name = %(program_name)s_%(process_num)s 53 | command = php %(ENV_ROOT)s/artisan reverb:start 54 | user = %(ENV_USER)s 55 | priority = 2 56 | autostart = %(ENV_WITH_REVERB)s 57 | autorestart = true 58 | stdout_logfile = %(ENV_ROOT)s/storage/logs/reverb.log 59 | stdout_logfile_maxbytes = 200MB 60 | stderr_logfile = %(ENV_ROOT)s/storage/logs/reverb.log 61 | stderr_logfile_maxbytes = 200MB 62 | minfds = 10000 63 | 64 | [include] 65 | files = /etc/supervisord.conf -------------------------------------------------------------------------------- /deployment/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | post_max_size = 100M 3 | upload_max_filesize = 100M 4 | expose_php = 0 5 | realpath_cache_size = 16M 6 | realpath_cache_ttl = 360 7 | max_input_time = 5 8 | register_argc_argv = 0 9 | date.timezone = ${TZ:-UTC} 10 | 11 | [Opcache] 12 | opcache.enable = 1 13 | opcache.enable_cli = 1 14 | opcache.memory_consumption = 256M 15 | opcache.use_cwd = 0 16 | opcache.max_file_size = 0 17 | opcache.max_accelerated_files = 32531 18 | opcache.validate_timestamps = 0 19 | opcache.file_update_protection = 0 20 | opcache.interned_strings_buffer = 16 21 | 22 | [JIT] 23 | opcache.jit_buffer_size = 128M 24 | opcache.jit = function 25 | opcache.jit_prof_threshold = 0.001 26 | opcache.jit_max_root_traces = 2048 27 | opcache.jit_max_side_traces = 256 28 | 29 | [zlib] 30 | zlib.output_compression = On 31 | zlib.output_compression_level = 9 32 | -------------------------------------------------------------------------------- /deployment/start-container: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -e 3 | 4 | container_mode=${CONTAINER_MODE:-"http"} 5 | octane_server=${OCTANE_SERVER} 6 | running_migrations_and_seeders=${RUNNING_MIGRATIONS_AND_SEEDERS:-"false"} 7 | 8 | initialStuff() { 9 | echo "Container mode: $container_mode" 10 | 11 | if [ "${running_migrations_and_seeders}" = "true" ]; then 12 | echo "Running migrations and seeding database ..." 13 | php artisan migrate --isolated --seed --force || php artisan migrate --seed --force 14 | fi 15 | 16 | php artisan storage:link; \ 17 | php artisan optimize:clear; \ 18 | php artisan optimize; 19 | } 20 | 21 | if [ "$1" != "" ]; then 22 | exec "$@" 23 | elif [ "${container_mode}" = "http" ]; then 24 | initialStuff 25 | echo "Octane Server: $octane_server" 26 | if [ "${octane_server}" = "frankenphp" ]; then 27 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.frankenphp.conf 28 | elif [ "${octane_server}" = "swoole" ]; then 29 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.swoole.conf 30 | elif [ "${octane_server}" = "roadrunner" ]; then 31 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.roadrunner.conf 32 | else 33 | echo "Invalid Octane server supplied." 34 | exit 1 35 | fi 36 | elif [ "${container_mode}" = "horizon" ]; then 37 | initialStuff 38 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.horizon.conf 39 | elif [ "${container_mode}" = "reverb" ]; then 40 | initialStuff 41 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.reverb.conf 42 | elif [ "${container_mode}" = "scheduler" ]; then 43 | initialStuff 44 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.scheduler.conf 45 | elif [ "${container_mode}" = "worker" ]; then 46 | if [ -z "${WORKER_COMMAND}" ]; then 47 | echo "WORKER_COMMAND is undefined." 48 | exit 1 49 | fi 50 | initialStuff 51 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.worker.conf 52 | else 53 | echo "Container mode mismatched." 54 | exit 1 55 | fi 56 | -------------------------------------------------------------------------------- /deployment/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon = true 3 | user = %(ENV_USER)s 4 | logfile = /var/log/supervisor/supervisord.log 5 | pidfile = /var/run/supervisord.pid 6 | 7 | [supervisorctl] 8 | 9 | [inet_http_server] 10 | port = 127.0.0.1:9001 11 | 12 | [rpcinterface:supervisor] 13 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface -------------------------------------------------------------------------------- /deployment/supervisord.horizon.conf: -------------------------------------------------------------------------------- 1 | [program:horizon] 2 | process_name = %(program_name)s_%(process_num)s 3 | command = php %(ENV_ROOT)s/artisan horizon 4 | user = %(ENV_USER)s 5 | autostart = true 6 | autorestart = true 7 | stdout_logfile = /dev/stdout 8 | stdout_logfile_maxbytes = 0 9 | stderr_logfile = /dev/stderr 10 | stderr_logfile_maxbytes = 0 11 | stopwaitsecs = 3600 12 | 13 | [include] 14 | files = /etc/supervisord.conf -------------------------------------------------------------------------------- /deployment/supervisord.reverb.conf: -------------------------------------------------------------------------------- 1 | [program:reverb] 2 | process_name = %(program_name)s_%(process_num)s 3 | command = php %(ENV_ROOT)s/artisan reverb:start 4 | user = %(ENV_USER)s 5 | autostart = true 6 | autorestart = true 7 | stdout_logfile = /dev/stdout 8 | stdout_logfile_maxbytes = 0 9 | stderr_logfile = /dev/stderr 10 | stderr_logfile_maxbytes = 0 11 | minfds = 10000 12 | 13 | [include] 14 | files = /etc/supervisord.conf -------------------------------------------------------------------------------- /deployment/supervisord.scheduler.conf: -------------------------------------------------------------------------------- 1 | [program:scheduler] 2 | process_name = %(program_name)s_%(process_num)s 3 | command = supercronic -overlapping /etc/supercronic/laravel 4 | user = %(ENV_USER)s 5 | autostart = true 6 | autorestart = true 7 | stdout_logfile = /dev/stdout 8 | stdout_logfile_maxbytes = 0 9 | stderr_logfile = /dev/stderr 10 | stderr_logfile_maxbytes = 0 11 | 12 | [program:clear-scheduler-cache] 13 | process_name = %(program_name)s_%(process_num)s 14 | command = php %(ENV_ROOT)s/artisan schedule:clear-cache 15 | user = %(ENV_USER)s 16 | autostart = true 17 | autorestart = false 18 | startsecs = 0 19 | startretries = 1 20 | stdout_logfile = /dev/stdout 21 | stdout_logfile_maxbytes = 0 22 | stderr_logfile = /dev/stderr 23 | stderr_logfile_maxbytes = 0 24 | 25 | [include] 26 | files = /etc/supervisord.conf -------------------------------------------------------------------------------- /deployment/supervisord.worker.conf: -------------------------------------------------------------------------------- 1 | [program:worker] 2 | process_name = %(program_name)s_%(process_num)s 3 | command = %(ENV_WORKER_COMMAND)s 4 | user = %(ENV_USER)s 5 | autostart = true 6 | autorestart = true 7 | stdout_logfile = /dev/stdout 8 | stdout_logfile_maxbytes = 0 9 | stderr_logfile = /dev/stderr 10 | stderr_logfile_maxbytes = 0 11 | 12 | [include] 13 | files = /etc/supervisord.conf -------------------------------------------------------------------------------- /docker-compose.production.yml: -------------------------------------------------------------------------------- 1 | x-logging: &default-logging 2 | driver: 'json-file' 3 | options: 4 | max-size: "50m" 5 | max-file: 6 6 | 7 | x-base: &base 8 | profiles: [app] 9 | depends_on: 10 | pgsql: 11 | condition: service_healthy 12 | redis: 13 | condition: service_healthy 14 | minio: 15 | condition: service_healthy 16 | build: 17 | context: . 18 | dockerfile: FrankenPHP.Dockerfile 19 | args: 20 | APP_ENV: 'production' # to load .env.production 21 | APP_HOST: '${APP_HOST}' 22 | WWWUSER: ${HOST_UID:-1000} 23 | WWWGROUP: ${HOST_GID:-1000} 24 | image: laravel/app 25 | user: "${HOST_UID:-1000}:${HOST_GID:-1000}" 26 | ulimits: 27 | nofile: 28 | soft: 20000 29 | hard: 40000 30 | security_opt: 31 | - no-new-privileges:true 32 | networks: 33 | - stack 34 | volumes: 35 | - './storage/app/public:/var/www/html/storage/app/public' 36 | - './storage/logs:/var/www/html/storage/logs' 37 | logging: *default-logging 38 | restart: always 39 | 40 | services: 41 | traefik: 42 | profiles: [app] 43 | image: traefik:3.2 44 | restart: always 45 | ulimits: 46 | nofile: 47 | soft: 20000 48 | hard: 40000 49 | security_opt: 50 | - no-new-privileges:true 51 | command: 52 | - "--log.level=WARN" 53 | - "--ping=true" 54 | - "--api=true" 55 | - "--accesslog=true" 56 | - "--api.dashboard=true" 57 | - "--providers.docker=true" 58 | - "--providers.docker.exposedByDefault=false" 59 | - "--entryPoints.traefik.address=:8190" 60 | - "--entryPoints.app.address=:80" 61 | - "--entryPoints.app.http.redirections.entryPoint.to=app-secure" 62 | - "--entryPoints.app.http.redirections.entryPoint.scheme=https" 63 | - "--entryPoints.app-secure.address=:443" 64 | - "--entryPoints.app-secure.http3=true" 65 | - "--entryPoints.reverb.address=:8080" 66 | - "--entryPoints.reverb.http3=true" 67 | - "--entryPoints.pgadmin.address=:6053" 68 | - "--entryPoints.pghero.address=:6660" 69 | - "--entryPoints.minio.address=:9000" 70 | - "--entryPoints.minio-console.address=:8900" 71 | - "--entryPoints.glances.address=:61208" 72 | - "--entryPoints.netdata.address=:19999" 73 | ports: 74 | - "127.0.0.1:8190:8190" # Traefik 75 | - "80:80" # HTTP 76 | - "443:443" # HTTPS 77 | - "443:443/udp" # HTTP/3 78 | - "8080:8080" # Reverb 79 | - "127.0.0.1:6053:6053" # pgAdmin 80 | - "127.0.0.1:6660:6660" # PgHero 81 | - "127.0.0.1:61208:61208" # Glances 82 | - "9000:9000" # MinIO 83 | - "127.0.0.1:8900:8900" # MinIO console 84 | - "127.0.0.1:19999:19999" # NetData 85 | networks: 86 | - stack 87 | volumes: 88 | - /var/run/docker.sock:/var/run/docker.sock:ro 89 | logging: *default-logging 90 | labels: 91 | traefik.enable: true 92 | traefik.http.routers.traefik.rule: Host(`localhost`) || Host(`${APP_HOST}`) 93 | traefik.http.routers.traefik.service: api@internal 94 | traefik.http.routers.traefik.entryPoints: traefik 95 | traefik.http.routers.traefik.middlewares: "traefik-auth,traefik-retry" 96 | 97 | traefik.http.middlewares.traefik-retry.retry.attempts: 4 98 | traefik.http.middlewares.traefik-retry.retry.initialinterval: 100ms 99 | traefik.http.middlewares.traefik-auth.basicauth.removeheader: true 100 | traefik.http.middlewares.traefik-auth.basicauth.users: "user:$$2y$$05$$8zbpsdxg9wDiiKdqxiB0zeAlxZtG68P1SDBOvCN4IooLFewLx70Gm" # user:123456 101 | app: 102 | <<: *base 103 | healthcheck: 104 | test: [ "CMD", "curl", "--fail", "localhost:8000/up" ] 105 | interval: 3s 106 | retries: 12 107 | timeout: 5s 108 | labels: 109 | traefik.enable: true 110 | 111 | traefik.http.routers.app.rule: Host(`${APP_HOST}`) || Host(`localhost`) 112 | traefik.http.routers.app.entryPoints: app 113 | traefik.http.routers.app.service: app-service 114 | traefik.http.routers.app.middlewares: "redirect-scheme,app-retry,app-compress,security-headers" 115 | 116 | traefik.http.routers.app-secure.rule: Host(`${APP_HOST}`) || Host(`localhost`) 117 | traefik.http.routers.app-secure.entryPoints: app-secure 118 | traefik.http.routers.app-secure.service: app-service 119 | traefik.http.routers.app-secure.tls: true 120 | traefik.http.routers.app-secure.middlewares: "redirect-scheme,app-retry,app-compress,security-headers" 121 | 122 | traefik.http.services.app-service.loadbalancer.server.port: 8000 # Because container exposes multiple ports 123 | traefik.http.services.app-service.loadbalancer.healthCheck.path: "/up" 124 | traefik.http.services.app-service.loadbalancer.healthCheck.hostname: "localhost" 125 | traefik.http.services.app-service.loadbalancer.healthCheck.port: 8000 126 | traefik.http.services.app-service.loadbalancer.healthCheck.interval: 3s 127 | traefik.http.services.app-service.loadbalancer.healthCheck.timeout: 5s 128 | 129 | traefik.http.middlewares.limit.buffering.maxRequestBodyBytes: 460000000 # 460mb 130 | traefik.http.middlewares.redirect-scheme.redirectscheme.scheme: https 131 | traefik.http.middlewares.redirect-scheme.redirectscheme.permanent: true 132 | traefik.http.middlewares.app-retry.retry.attempts: 4 133 | traefik.http.middlewares.app-retry.retry.initialinterval: 100ms 134 | traefik.http.middlewares.app-compress.compress: true 135 | traefik.http.middlewares.security-headers.headers.accesscontrolmaxage: 100 136 | traefik.http.middlewares.security-headers.headers.addvaryheader: true # Vary: Origin 137 | traefik.http.middlewares.security-headers.headers.hostsproxyheaders: X-Forwarded-Host 138 | traefik.http.middlewares.security-headers.headers.stsseconds: 63072000 # Strict-Transport-Security: max-age=63072000; includeSubDomains; preload 139 | traefik.http.middlewares.security-headers.headers.stsincludesubdomains: true 140 | traefik.http.middlewares.security-headers.headers.stspreload: true 141 | traefik.http.middlewares.security-headers.headers.forcestsheader: true 142 | traefik.http.middlewares.security-headers.headers.customFrameOptionsValue: SAMEORIGIN # X-Frame-Options: same-origin 143 | traefik.http.middlewares.security-headers.headers.contenttypenosniff: true # X-Content-Type-Options: nosniff 144 | traefik.http.middlewares.security-headers.headers.browserxssfilter: true # X-XSS-Protection: 1; mode=block 145 | traefik.http.middlewares.security-headers.headers.referrerpolicy: no-referrer-when-downgrade # Referrer-Policy: no-referrer-when-downgrade 146 | traefik.http.middlewares.security-headers.headers.permissionspolicy: camera=(), geolocation=(), microphone=(), payment=(), usb=(), interest-cohort=(), gyroscope=() 147 | horizon: 148 | <<: *base 149 | environment: 150 | CONTAINER_MODE: horizon 151 | labels: 152 | traefik.enable: false 153 | scheduler: 154 | <<: *base 155 | environment: 156 | CONTAINER_MODE: scheduler 157 | labels: 158 | traefik.enable: false 159 | reverb: 160 | <<: *base 161 | environment: 162 | CONTAINER_MODE: reverb 163 | labels: 164 | traefik.enable: true 165 | traefik.http.routers.reverb.rule: Host(`${APP_HOST}`) || Host(`localhost`) 166 | traefik.http.routers.reverb.entryPoints: reverb 167 | traefik.http.routers.reverb.tls: true 168 | traefik.http.routers.reverb.middlewares: "reverb-retry" 169 | traefik.http.middlewares.reverb-retry.retry.attempts: 4 170 | traefik.http.middlewares.reverb-retry.retry.initialinterval: 100ms 171 | traefik.http.routers.reverb.service: reverb-service 172 | traefik.http.services.reverb-service.loadbalancer.server.port: 8080 173 | redis: 174 | profiles: [app] 175 | image: 'redis:alpine' 176 | ulimits: 177 | nofile: 178 | soft: 20000 179 | hard: 40000 180 | command: [ "redis-server", "--requirepass", "${REDIS_PASSWORD}", "--maxmemory", "2gb" ] 181 | security_opt: 182 | - no-new-privileges:true 183 | volumes: 184 | - 'stack-redis:/data' 185 | networks: 186 | - stack 187 | logging: *default-logging 188 | healthcheck: 189 | test: [ "CMD", "redis-cli", "ping" ] 190 | retries: 3 191 | timeout: 5s 192 | restart: always 193 | labels: 194 | traefik.enable: false 195 | pgsql: 196 | profiles: [app] 197 | image: 'postgres:17-bookworm' 198 | ulimits: 199 | nofile: 200 | soft: 20000 201 | hard: 40000 202 | # command: ["-c", "config_file=/etc/postgresql/postgresql.conf"] 203 | security_opt: 204 | - no-new-privileges:true 205 | environment: 206 | PGPASSWORD: '${DB_PASSWORD}' 207 | POSTGRES_DB: '${DB_DATABASE}' 208 | POSTGRES_USER: '${DB_USERNAME}' 209 | POSTGRES_PASSWORD: '${DB_PASSWORD}' 210 | volumes: 211 | # - './postgresql.conf:/etc/postgresql/postgresql.conf' 212 | - 'stack-pgsql:/var/lib/postgresql/data' 213 | - '../backup:/backup' 214 | networks: 215 | - stack 216 | healthcheck: 217 | test: [ "CMD", "pg_isready", "-q", "-d", "${DB_DATABASE}", "-U", "${DB_USERNAME}"] 218 | interval: 15s 219 | retries: 12 220 | timeout: 20s 221 | restart: always 222 | logging: *default-logging 223 | labels: 224 | traefik.enable: false 225 | docker-volume-backup.stop-during-backup: true 226 | docker-volume-backup.archive-pre: /bin/sh -c 'pg_dump -U ${DB_USERNAME} -F t ${DB_DATABASE} > /backup/${DB_DATABASE}-database.tar' 227 | pgadmin: 228 | profiles: [administration] 229 | image: 'dpage/pgadmin4:latest' 230 | security_opt: 231 | - no-new-privileges:true 232 | depends_on: 233 | pgsql: 234 | condition: service_healthy 235 | environment: 236 | PGADMIN_DEFAULT_EMAIL: '${PGADMIN_DEFAULT_EMAIL}' 237 | PGADMIN_DEFAULT_PASSWORD: '${PGADMIN_DEFAULT_PASSWORD}' 238 | PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: 'True' 239 | volumes: 240 | - 'stack-pgadmin:/var/lib/pgadmin' 241 | networks: 242 | - stack 243 | restart: always 244 | logging: *default-logging 245 | labels: 246 | traefik.enable: true 247 | traefik.http.routers.pgadmin.rule: Host(`localhost`) 248 | traefik.http.routers.pgadmin.entryPoints: pgadmin 249 | traefik.http.routers.pgadmin.middlewares: "pgadmin-auth,pgadmin-retry" 250 | traefik.http.middlewares.pgadmin-retry.retry.attempts: 4 251 | traefik.http.middlewares.pgadmin-retry.retry.initialinterval: 100ms 252 | traefik.http.middlewares.pgadmin-auth.basicauth.removeheader: true 253 | traefik.http.middlewares.pgadmin-auth.basicauth.users: "user:$$2y$$05$$8zbpsdxg9wDiiKdqxiB0zeAlxZtG68P1SDBOvCN4IooLFewLx70Gm" # user:123456 254 | pghero: 255 | profiles: [administration] 256 | image: ankane/pghero 257 | security_opt: 258 | - no-new-privileges:true 259 | depends_on: 260 | pgsql: 261 | condition: service_healthy 262 | environment: 263 | PORT: 6660 264 | DATABASE_URL: postgres://${DB_USERNAME}:${DB_PASSWORD}@pgsql:5432/${DB_DATABASE} 265 | networks: 266 | - stack 267 | restart: always 268 | logging: *default-logging 269 | labels: 270 | traefik.enable: true 271 | traefik.http.routers.pghero.rule: Host(`localhost`) 272 | traefik.http.routers.pghero.entryPoints: pghero 273 | traefik.http.routers.pghero.middlewares: "pghero-auth,pghero-retry" 274 | traefik.http.middlewares.pghero-retry.retry.attempts: 4 275 | traefik.http.middlewares.pghero-retry.retry.initialinterval: 100ms 276 | traefik.http.middlewares.pghero-auth.basicauth.removeheader: true 277 | traefik.http.middlewares.pghero-auth.basicauth.users: "user:$$2y$$05$$8zbpsdxg9wDiiKdqxiB0zeAlxZtG68P1SDBOvCN4IooLFewLx70Gm" # user:123456 278 | traefik.http.routers.pghero.service: pghero-service 279 | traefik.http.services.pghero-service.loadbalancer.server.port: 6660 280 | typesense: 281 | profiles: [app] 282 | image: 'typesense/typesense:0.25.2' 283 | security_opt: 284 | - no-new-privileges:true 285 | environment: 286 | TYPESENSE_DATA_DIR: '${TYPESENSE_DATA_DIR:-/typesense-data}' 287 | TYPESENSE_API_KEY: '${TYPESENSE_API_KEY}' 288 | TYPESENSE_ENABLE_CORS: '${TYPESENSE_ENABLE_CORS:-true}' 289 | volumes: 290 | - 'stack-typesense:/typesense-data' 291 | networks: 292 | - stack 293 | healthcheck: 294 | test: [ "CMD", "wget", "--no-verbose", "--spider", "http://localhost:8108/health" ] 295 | interval: 3s 296 | retries: 12 297 | timeout: 5s 298 | restart: always 299 | logging: *default-logging 300 | labels: 301 | traefik.enable: false 302 | backup: 303 | profiles: [administration] 304 | image: offen/docker-volume-backup:v2 305 | security_opt: 306 | - no-new-privileges:true 307 | environment: 308 | BACKUP_FILENAME: backup-%Y-%m-%dT%H-%M-%S.tar.gz 309 | BACKUP_PRUNING_PREFIX: backup- 310 | BACKUP_CRON_EXPRESSION: "0 2 * * *" # run every day at 2am 311 | BACKUP_RETENTION_DAYS: '7' 312 | restart: always 313 | depends_on: 314 | pgsql: 315 | condition: service_healthy 316 | logging: *default-logging 317 | volumes: 318 | - stack-pgsql:/backup/pgsql:ro 319 | - ../backup/volumes:/archive 320 | - /var/run/docker.sock:/var/run/docker.sock:ro 321 | - /etc/timezone:/etc/timezone:ro 322 | - /etc/localtime:/etc/localtime:ro 323 | labels: 324 | traefik.enable: false 325 | minio: 326 | profiles: [app] 327 | image: 'minio/minio:latest' 328 | security_opt: 329 | - no-new-privileges:true 330 | environment: 331 | MINIO_ROOT_USER: '${MINIO_ROOT_USER}' 332 | MINIO_ROOT_PASSWORD: '${MINIO_ROOT_PASSWORD}' 333 | volumes: 334 | - 'stack-minio:/data/minio' 335 | networks: 336 | - stack 337 | command: 'minio server /data/minio --console-address ":8900"' 338 | restart: always 339 | logging: *default-logging 340 | labels: 341 | traefik.enable: true 342 | 343 | traefik.http.routers.minio-console.rule: Host(`localhost`) 344 | traefik.http.routers.minio-console.entryPoints: minio-console 345 | traefik.http.routers.minio-console.service: minio-console-service 346 | traefik.http.routers.minio-console.middlewares: "minio-auth,minio-retry" 347 | traefik.http.services.minio-console-service.loadbalancer.server.port: 8900 348 | 349 | traefik.http.routers.minio.rule: Host(`${APP_HOST}`) || Host(`localhost`) 350 | traefik.http.routers.minio.entryPoints: minio 351 | traefik.http.routers.minio.service: minio-service 352 | traefik.http.routers.minio.middlewares: "minio-retry,minio-compress" 353 | traefik.http.services.minio-service.loadbalancer.server.port: 9000 354 | 355 | traefik.http.middlewares.minio-compress.compress: true 356 | traefik.http.middlewares.minio-retry.retry.attempts: 4 357 | traefik.http.middlewares.minio-retry.retry.initialinterval: 100ms 358 | traefik.http.middlewares.minio-auth.basicauth.removeheader: true 359 | traefik.http.middlewares.minio-auth.basicauth.users: "user:$$2y$$05$$8zbpsdxg9wDiiKdqxiB0zeAlxZtG68P1SDBOvCN4IooLFewLx70Gm" # user:123456 360 | healthcheck: 361 | test: [ "CMD", "mc", "ready", "local" ] 362 | retries: 3 363 | timeout: 5s 364 | glances: 365 | profiles: [administration] 366 | image: nicolargo/glances:latest-full 367 | pid: host 368 | security_opt: 369 | - no-new-privileges:true 370 | volumes: 371 | - /var/run/docker.sock:/var/run/docker.sock:ro 372 | - /etc/os-release:/etc/os-release:ro 373 | environment: 374 | - "GLANCES_OPT=-w" 375 | - TZ="${APP_TIMEZONE}" 376 | networks: 377 | - stack 378 | logging: *default-logging 379 | restart: always 380 | labels: 381 | traefik.enable: true 382 | traefik.http.routers.glances.rule: Host(`localhost`) 383 | traefik.http.routers.glances.entryPoints: glances 384 | traefik.http.routers.glances.middlewares: "glances-auth,glances-retry" 385 | traefik.http.middlewares.glances-retry.retry.attempts: 4 386 | traefik.http.middlewares.glances-retry.retry.initialinterval: 100ms 387 | traefik.http.middlewares.glances-auth.basicauth.removeheader: true 388 | traefik.http.middlewares.glances-auth.basicauth.users: "user:$$2y$$05$$8zbpsdxg9wDiiKdqxiB0zeAlxZtG68P1SDBOvCN4IooLFewLx70Gm" # user:123456 389 | netdata: 390 | profiles: [administration] 391 | image: netdata/netdata 392 | restart: unless-stopped 393 | cap_add: 394 | - SYS_PTRACE 395 | - SYS_ADMIN 396 | security_opt: 397 | - apparmor:unconfined 398 | logging: *default-logging 399 | networks: 400 | - stack 401 | volumes: 402 | - stack-netdataconfig:/etc/netdata 403 | - stack-netdatalib:/var/lib/netdata 404 | - stack-netdatacache:/var/cache/netdata 405 | - /:/host/root:ro,rslave 406 | - /etc/passwd:/host/etc/passwd:ro 407 | - /etc/group:/host/etc/group:ro 408 | - /etc/localtime:/etc/localtime:ro 409 | - /proc:/host/proc:ro 410 | - /sys:/host/sys:ro 411 | - /etc/os-release:/host/etc/os-release:ro 412 | - /var/log:/host/var/log:ro 413 | - /var/run/docker.sock:/var/run/docker.sock:ro 414 | labels: 415 | traefik.enable: true 416 | traefik.http.routers.netdata.rule: Host(`localhost`) 417 | traefik.http.routers.netdata.entryPoints: netdata 418 | traefik.http.routers.netdata.middlewares: "netdata-auth" 419 | traefik.http.middlewares.netdata-auth.basicauth.removeheader: true 420 | traefik.http.middlewares.netdata-auth.basicauth.users: "user:$$2y$$05$$8zbpsdxg9wDiiKdqxiB0zeAlxZtG68P1SDBOvCN4IooLFewLx70Gm" # user:123456 421 | networks: 422 | stack: 423 | driver: bridge 424 | volumes: 425 | stack-pgsql: 426 | driver: local 427 | stack-redis: 428 | driver: local 429 | stack-pgadmin: 430 | driver: local 431 | stack-minio: 432 | driver: local 433 | stack-typesense: 434 | driver: local 435 | stack-netdataconfig: 436 | driver: local 437 | stack-netdatalib: 438 | driver: local 439 | stack-netdatacache: 440 | driver: local 441 | 442 | -------------------------------------------------------------------------------- /static-build.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.4 2 | ARG FRANKENPHP_VERSION=1.6 3 | ARG COMPOSER_VERSION=2.8 4 | ARG BUN_VERSION="latest" 5 | ARG APP_ENV 6 | 7 | FROM composer:${COMPOSER_VERSION} AS vendor 8 | 9 | FROM dunglas/frankenphp:${FRANKENPHP_VERSION}-builder-php${PHP_VERSION} AS upstream 10 | 11 | COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy 12 | 13 | RUN CGO_ENABLED=1 \ 14 | XCADDY_SETCAP=1 \ 15 | XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ 16 | CGO_CFLAGS=$(php-config --includes) \ 17 | CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ 18 | xcaddy build \ 19 | --output /usr/local/bin/frankenphp \ 20 | --with github.com/dunglas/frankenphp=./ \ 21 | --with github.com/dunglas/frankenphp/caddy=./caddy/ \ 22 | --with github.com/dunglas/caddy-cbrotli 23 | 24 | FROM dunglas/frankenphp:${FRANKENPHP_VERSION}-php${PHP_VERSION} AS base 25 | 26 | COPY --from=upstream /usr/local/bin/frankenphp /usr/local/bin/frankenphp 27 | 28 | LABEL maintainer="SMortexa " 29 | LABEL org.opencontainers.image.title="Laravel Octane Dockerfile" 30 | LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane" 31 | LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile 32 | LABEL org.opencontainers.image.licenses=MIT 33 | 34 | ARG WWWUSER=1000 35 | ARG WWWGROUP=1000 36 | ARG TZ=UTC 37 | ARG APP_DIR=/var/www/html 38 | ARG APP_ENV 39 | ARG APP_HOST 40 | 41 | ENV DEBIAN_FRONTEND=noninteractive \ 42 | TERM=xterm-color \ 43 | OCTANE_SERVER=frankenphp \ 44 | TZ=${TZ} \ 45 | USER=octane \ 46 | ROOT=${APP_DIR} \ 47 | APP_ENV=${APP_ENV} \ 48 | COMPOSER_FUND=0 \ 49 | COMPOSER_MAX_PARALLEL_HTTP=24 \ 50 | XDG_CONFIG_HOME=${APP_DIR}/.config \ 51 | XDG_DATA_HOME=${APP_DIR}/.data \ 52 | SERVER_NAME=${APP_HOST} 53 | 54 | WORKDIR ${ROOT} 55 | 56 | SHELL ["/bin/bash", "-eou", "pipefail", "-c"] 57 | 58 | RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \ 59 | && echo ${TZ} > /etc/timezone 60 | 61 | RUN apt-get update; \ 62 | apt-get upgrade -yqq; \ 63 | apt-get install -yqq --no-install-recommends --show-progress \ 64 | apt-utils \ 65 | curl \ 66 | wget \ 67 | vim \ 68 | git \ 69 | ncdu \ 70 | procps \ 71 | unzip \ 72 | ca-certificates \ 73 | supervisor \ 74 | libsodium-dev \ 75 | libbrotli-dev \ 76 | # Install PHP extensions (included with dunglas/frankenphp) 77 | && install-php-extensions \ 78 | bz2 \ 79 | pcntl \ 80 | mbstring \ 81 | bcmath \ 82 | sockets \ 83 | pgsql \ 84 | pdo_pgsql \ 85 | opcache \ 86 | exif \ 87 | pdo_mysql \ 88 | zip \ 89 | uv \ 90 | vips \ 91 | intl \ 92 | gd \ 93 | redis \ 94 | rdkafka \ 95 | memcached \ 96 | igbinary \ 97 | ldap \ 98 | && apt-get -y autoremove \ 99 | && apt-get clean \ 100 | && docker-php-source delete \ 101 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ 102 | && rm /var/log/lastlog /var/log/faillog 103 | 104 | RUN arch="$(uname -m)" \ 105 | && case "$arch" in \ 106 | armhf) _cronic_fname='supercronic-linux-arm' ;; \ 107 | aarch64) _cronic_fname='supercronic-linux-arm64' ;; \ 108 | x86_64) _cronic_fname='supercronic-linux-amd64' ;; \ 109 | x86) _cronic_fname='supercronic-linux-386' ;; \ 110 | *) echo >&2 "error: unsupported architecture: $arch"; exit 1 ;; \ 111 | esac \ 112 | && wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/${_cronic_fname}" \ 113 | -O /usr/bin/supercronic \ 114 | && chmod +x /usr/bin/supercronic \ 115 | && mkdir -p /etc/supercronic \ 116 | && echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel 117 | 118 | RUN userdel --remove --force www-data \ 119 | && groupadd --force -g ${WWWGROUP} ${USER} \ 120 | && useradd -ms /bin/bash --no-log-init --no-user-group -g ${WWWGROUP} -u ${WWWUSER} ${USER} \ 121 | && setcap -r /usr/local/bin/frankenphp 122 | 123 | RUN chown -R ${USER}:${USER} ${ROOT} /var/{log,run} \ 124 | && chmod -R a+rw ${ROOT} /var/{log,run} 125 | 126 | RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini 127 | 128 | USER ${USER} 129 | 130 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=vendor /usr/bin/composer /usr/bin/composer 131 | 132 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.conf /etc/ 133 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/octane/FrankenPHP/supervisord.frankenphp.conf /etc/supervisor/conf.d/ 134 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/supervisord.*.conf /etc/supervisor/conf.d/ 135 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/start-container /usr/local/bin/start-container 136 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/healthcheck /usr/local/bin/healthcheck 137 | COPY --link --chown=${WWWUSER}:${WWWUSER} deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini 138 | 139 | RUN chmod +x /usr/local/bin/start-container /usr/local/bin/healthcheck 140 | 141 | ########################################### 142 | 143 | FROM base AS common 144 | 145 | USER ${USER} 146 | 147 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 148 | 149 | RUN composer install \ 150 | --no-dev \ 151 | --no-interaction \ 152 | --no-autoloader \ 153 | --no-ansi \ 154 | --no-scripts \ 155 | --audit 156 | 157 | ########################################### 158 | # Build frontend assets with Bun 159 | ########################################### 160 | 161 | FROM oven/bun:${BUN_VERSION} AS build 162 | 163 | ARG APP_ENV 164 | 165 | ENV ROOT=/var/www/html \ 166 | APP_ENV=${APP_ENV} \ 167 | NODE_ENV=${APP_ENV:-production} 168 | 169 | WORKDIR ${ROOT} 170 | 171 | COPY --link package.json bun.lock* ./ 172 | 173 | RUN bun install --frozen-lockfile 174 | 175 | COPY --link . . 176 | COPY --link --from=common ${ROOT}/vendor vendor 177 | 178 | RUN bun run build 179 | 180 | ########################################### 181 | 182 | FROM common AS runner 183 | 184 | USER ${USER} 185 | 186 | ENV WITH_HORIZON=false \ 187 | WITH_SCHEDULER=false \ 188 | WITH_REVERB=false 189 | 190 | COPY --link --chown=${WWWUSER}:${WWWUSER} . . 191 | COPY --link --chown=${WWWUSER}:${WWWUSER} --from=build ${ROOT}/public public 192 | 193 | RUN mkdir -p \ 194 | storage/framework/{sessions,views,cache,testing} \ 195 | storage/logs \ 196 | bootstrap/cache && chmod -R a+rw storage 197 | 198 | RUN composer install \ 199 | --classmap-authoritative \ 200 | --no-interaction \ 201 | --no-ansi \ 202 | --no-dev \ 203 | && composer clear-cache 204 | 205 | ########################################### 206 | 207 | FROM --platform=linux/amd64 dunglas/frankenphp:static-builder-${FRANKENPHP_VERSION} AS static 208 | 209 | WORKDIR /go/src/app/dist/app 210 | 211 | COPY --link --from=runner . . 212 | 213 | RUN rm -Rf tests/ 214 | 215 | ENV NO_COMPRESS=true 216 | 217 | WORKDIR /go/src/app/ 218 | 219 | RUN EMBED=dist/app/ ./build-static.sh 220 | --------------------------------------------------------------------------------