[{"data":1,"prerenderedAt":3303},["ShallowReactive",2],{"navigation":3,"/docs/framework-guides/laravel/octane":229,"/docs/framework-guides/laravel/octane-surround":3298},[4],{"title":5,"path":6,"stem":7,"children":8},"Docs","/docs","docs",[9,12,56,85,132,153,186,203,216],{"title":10,"path":6,"stem":11},"","docs/index",{"title":13,"path":14,"stem":15,"children":16,"icon":55},"Getting Started","/docs/getting-started","docs/1.getting-started/1.index",[17,19,23,27,31,35,39,43,47,51],{"title":18,"path":14,"stem":15},"Introduction",{"title":20,"path":21,"stem":22},"Container Basics","/docs/getting-started/container-basics","docs/1.getting-started/2.container-basics",{"title":24,"path":25,"stem":26},"Installation","/docs/getting-started/installation","docs/1.getting-started/3.installation",{"title":28,"path":29,"stem":30},"These Images vs Others","/docs/getting-started/these-images-vs-others","docs/1.getting-started/4.these-images-vs-others",{"title":32,"path":33,"stem":34},"Choosing an Image","/docs/getting-started/choosing-an-image","docs/1.getting-started/5.choosing-an-image",{"title":36,"path":37,"stem":38},"Default Configurations","/docs/getting-started/default-configurations","docs/1.getting-started/6.default-configurations",{"title":40,"path":41,"stem":42},"Upgrade Guide","/docs/getting-started/upgrade-guide","docs/1.getting-started/7.upgrade-guide",{"title":44,"path":45,"stem":46},"Changelog","/docs/getting-started/changelog","docs/1.getting-started/8.changelog",{"title":48,"path":49,"stem":50},"About","/docs/getting-started/about","docs/1.getting-started/9.about",{"title":52,"path":53,"stem":54},"Contributing","/docs/getting-started/contributing","docs/1.getting-started/99.contributing",false,{"title":57,"path":58,"stem":59,"children":60,"page":55},"Image Variations","/docs/image-variations","docs/2.image-variations",[61,65,69,73,77,81],{"title":62,"path":63,"stem":64},"CLI","/docs/image-variations/cli","docs/2.image-variations/cli",{"title":66,"path":67,"stem":68},"FPM","/docs/image-variations/fpm","docs/2.image-variations/fpm",{"title":70,"path":71,"stem":72},"FPM-Apache","/docs/image-variations/fpm-apache","docs/2.image-variations/fpm-apache",{"title":74,"path":75,"stem":76},"FPM-NGINX","/docs/image-variations/fpm-nginx","docs/2.image-variations/fpm-nginx",{"title":78,"path":79,"stem":80},"FrankenPHP","/docs/image-variations/frankenphp","docs/2.image-variations/frankenphp",{"title":82,"path":83,"stem":84},"Unit (Deprecated)","/docs/image-variations/unit","docs/2.image-variations/unit",{"title":86,"path":87,"stem":88,"children":89,"page":55},"Framework Guides","/docs/framework-guides","docs/3.framework-guides",[90,123],{"title":91,"icon":55,"defaultOpen":55,"path":92,"stem":93,"children":94,"page":55},"Laravel","/docs/framework-guides/laravel","docs/3.framework-guides/1.laravel",[95,99,103,107,111,115,119],{"title":96,"path":97,"stem":98},"Automations","/docs/framework-guides/laravel/automations","docs/3.framework-guides/1.laravel/1.automations",{"title":100,"path":101,"stem":102},"Task Scheduler","/docs/framework-guides/laravel/task-scheduler","docs/3.framework-guides/1.laravel/2.task-scheduler",{"title":104,"path":105,"stem":106},"Queue","/docs/framework-guides/laravel/queue","docs/3.framework-guides/1.laravel/3.queue",{"title":108,"path":109,"stem":110},"Horizon","/docs/framework-guides/laravel/horizon","docs/3.framework-guides/1.laravel/4.horizon",{"title":112,"path":113,"stem":114},"Reverb","/docs/framework-guides/laravel/reverb","docs/3.framework-guides/1.laravel/4.reverb",{"title":116,"path":117,"stem":118},"Nightwatch","/docs/framework-guides/laravel/nightwatch","docs/3.framework-guides/1.laravel/5.nightwatch",{"title":120,"path":121,"stem":122},"Octane","/docs/framework-guides/laravel/octane","docs/3.framework-guides/1.laravel/octane",{"title":124,"icon":55,"defaultOpen":55,"path":125,"stem":126,"children":127,"page":55},"WordPress","/docs/framework-guides/wordpress","docs/3.framework-guides/2.wordpress",[128],{"title":129,"path":130,"stem":131},"Using Docker with WordPress","/docs/framework-guides/wordpress/using-wordpress-with-docker","docs/3.framework-guides/2.wordpress/4.using-wordpress-with-docker",{"title":133,"path":134,"stem":135,"children":136,"page":55},"Deployment And Production","/docs/deployment-and-production","docs/4.deployment-and-production",[137,141,145,149],{"title":138,"path":139,"stem":140},"Development to Production","/docs/deployment-and-production/development-to-production","docs/4.deployment-and-production/2.development-to-production",{"title":142,"path":143,"stem":144},"Packaging Your App for Deployment","/docs/deployment-and-production/packaging-your-app-for-deployment","docs/4.deployment-and-production/3.packaging-your-app-for-deployment",{"title":146,"path":147,"stem":148},"Configuring SSL","/docs/deployment-and-production/configuring-ssl","docs/4.deployment-and-production/4.configuring-ssl",{"title":150,"path":151,"stem":152},"Choosing a Host","/docs/deployment-and-production/choosing-a-host","docs/4.deployment-and-production/5.choosing-a-host",{"title":154,"icon":55,"defaultOpen":55,"path":155,"stem":156,"children":157,"page":55},"Advanced Guides","/docs/guide","docs/5.guide",[158,162,166,170,174,178,182],{"title":159,"path":160,"stem":161},"Migrating from official PHP images","/docs/guide/migrating-from-official-php-images","docs/5.guide/1.migrating-from-official-php-images",{"title":163,"path":164,"stem":165},"Using Healthchecks With Laravel","/docs/guide/using-healthchecks-with-laravel","docs/5.guide/2.using-healthchecks-with-laravel",{"title":167,"path":168,"stem":169},"Using S6 Overlay","/docs/guide/using-s6-overlay","docs/5.guide/2.using-s6-overlay",{"title":171,"path":172,"stem":173},"Understanding File Permissions","/docs/guide/understanding-file-permissions","docs/5.guide/3.understanding-file-permissions",{"title":175,"path":176,"stem":177},"Configuring Trusted Proxies","/docs/guide/configuring-trusted-proxies","docs/5.guide/4.configuring-trusted-proxies",{"title":179,"path":180,"stem":181},"Major version migrations","/docs/guide/major-version-migrations","docs/5.guide/5.major-version-migrations",{"title":183,"path":184,"stem":185},"Production performance tuning","/docs/guide/production-performance-tuning","docs/5.guide/6.production-performance-tuning",{"title":187,"icon":55,"defaultOpen":55,"path":188,"stem":189,"children":190,"page":55},"Customization","/docs/customizing-the-image","docs/6.customizing-the-image",[191,195,199],{"title":192,"path":193,"stem":194},"Changing php.ini settings","/docs/customizing-the-image/changing-common-php-settings","docs/6.customizing-the-image/1.changing-common-php-settings",{"title":196,"path":197,"stem":198},"Installing PHP extensions","/docs/customizing-the-image/installing-additional-php-extensions","docs/6.customizing-the-image/2.installing-additional-php-extensions",{"title":200,"path":201,"stem":202},"Adding Start Up Scripts","/docs/customizing-the-image/adding-your-own-start-up-scripts","docs/6.customizing-the-image/3.adding-your-own-start-up-scripts",{"title":204,"path":205,"stem":206,"children":207,"page":55},"Troubleshooting","/docs/troubleshooting","docs/7.troubleshooting",[208,212],{"title":209,"path":210,"stem":211},"Common Issues","/docs/troubleshooting/common-issues","docs/7.troubleshooting/1.common-issues",{"title":213,"path":214,"stem":215},"Getting Help","/docs/troubleshooting/getting-help","docs/7.troubleshooting/2.getting-help",{"title":217,"path":218,"stem":219,"children":220,"page":55},"Reference","/docs/reference","docs/8.reference",[221,225],{"title":222,"path":223,"stem":224},"Environment Variable Specification","/docs/reference/environment-variable-specification","docs/8.reference/1.environment-variable-specification",{"title":226,"path":227,"stem":228},"Command Reference","/docs/reference/command-reference","docs/8.reference/2.command-reference",{"id":230,"title":120,"body":231,"description":3291,"extension":2962,"links":3292,"meta":3293,"navigation":674,"path":121,"redirect":3292,"seo":3296,"stem":122,"__hash__":3297},"docs/docs/3.framework-guides/1.laravel/octane.md",{"type":232,"value":233,"toc":3260},"minimark",[234,241,246,249,256,262,266,269,379,382,412,462,466,469,480,485,488,564,567,571,580,634,653,775,800,804,807,906,909,970,996,1011,1015,1018,1032,1039,1043,1050,1065,1121,1125,1153,1177,1180,1205,1242,1256,1269,1273,1299,1312,1316,1319,1538,1554,1564,1616,1625,1696,1715,1717,1733,1752,1755,1779,1787,1790,1799,1802,1988,2053,2069,2073,2079,2121,2127,2130,2170,2173,2304,2316,2330,2334,2344,2462,2497,2500,2525,2657,2662,2680,2765,2781,2785,2791,2934,2948,2966,2978,2982,2991,3039,3048,3052,3064,3082,3086,3102,3121,3130,3134,3137,3180,3184,3187,3191,3200,3204,3213,3217,3231,3235,3256],[235,236,237],"lead-p",{},[238,239,240],"p",{},"Laravel Octane supercharges your application's performance by keeping it loaded in memory and serving requests at incredible speeds. The FrankenPHP variation of our images provides native Octane support with worker mode built in, and you can run Octane's other servers from our CLI image.",[242,243,245],"h2",{"id":244},"what-is-laravel-octane","What is Laravel Octane?",[238,247,248],{},"Laravel Octane boots your Laravel application once and keeps it in memory, then processes thousands of requests without reloading. This dramatically improves performance compared to traditional PHP execution.",[238,250,251,255],{},[252,253,254],"strong",{},"Traditional PHP:","\nBootstrap → Handle Request → Teardown → Repeat for every request",[238,257,258,261],{},[252,259,260],{},"With Octane:","\nBootstrap once → Handle unlimited requests",[242,263,265],{"id":264},"choosing-an-octane-server","Choosing an Octane Server",[238,267,268],{},"Octane runs on four different application servers. One of them is built into our images, and the rest need some assembly:",[270,271,272,288],"table",{},[273,274,275],"thead",{},[276,277,278,282,285],"tr",{},[279,280,281],"th",{},"Server",[279,283,284],{},"How to run it with our images",[279,286,287],{},"What you get",[289,290,291,316,337,356],"tbody",{},[276,292,293,298,309],{},[294,295,296],"td",{},[252,297,78],{},[294,299,300,301,308],{},"Built into the ",[302,303,304],"a",{"href":79},[305,306,307],"code",{},"frankenphp"," variation",[294,310,311,312,315],{},"Nothing to install. Worker mode, our production Caddyfile, and every ",[305,313,314],{},"CADDY_*"," variable work out of the box.",[276,317,318,323,334],{},[294,319,320],{},[252,321,322],{},"Swoole",[294,324,325,330,331],{},[302,326,327],{"href":63},[305,328,329],{},"cli"," + ",[305,332,333],{},"install-php-extensions swoole",[294,335,336],{},"Octane's concurrent tasks, ticks, cache driver, and tables. You supply the HTTP hardening yourself.",[276,338,339,344,353],{},[294,340,341],{},[252,342,343],{},"Open Swoole",[294,345,346,330,350],{},[302,347,348],{"href":63},[305,349,329],{},[305,351,352],{},"install-php-extensions openswoole",[294,354,355],{},"A fork of Swoole with the same Octane feature set.",[276,357,358,363,372],{},[294,359,360],{},[252,361,362],{},"RoadRunner",[294,364,365,330,369],{},[302,366,367],{"href":63},[305,368,329],{},[305,370,371],{},"spiral/roadrunner-cli",[294,373,374,375,378],{},"A Go binary with its own ",[305,376,377],{},".rr.yaml"," configuration.",[238,380,381],{},"We recommend FrankenPHP for almost everyone. It is the only server where the image does the hard part for you: the Caddyfile we ship handles asset caching, security headers, trusted proxies, SSL, the health check endpoint, and log redaction, and it switches itself into worker mode when Octane starts it. Everywhere else, those concerns become your job.",[383,384,385],"warning",{},[238,386,387,388,391,392,395,396,399,400,403,404,406,407,411],{},"Octane ",[252,389,390],{},"replaces"," PHP-FPM, so you cannot run it inside the ",[305,393,394],{},"fpm",", ",[305,397,398],{},"fpm-nginx",", or ",[305,401,402],{},"fpm-apache"," variations. The NGINX and Apache in those images are PHP-FPM front ends, not reverse proxies. If you want Octane without FrankenPHP, start from the ",[305,405,329],{}," image instead. See ",[302,408,410],{"href":409},"#running-octane-without-frankenphp","Running Octane Without FrankenPHP",".",[413,414,415,418,459],"tip",{},[238,416,417],{},"There is one genuinely good reason to choose Swoole over FrankenPHP: four Octane features only exist there.",[419,420,421,435,443,451],"ul",{},[422,423,424,431,432],"li",{},[302,425,430],{"href":426,"rel":427,"target":429},"https://laravel.com/docs/13.x/octane#concurrent-tasks",[428],"nofollow","_blank","Concurrent tasks"," via ",[305,433,434],{},"Octane::concurrently()",[422,436,437,442],{},[302,438,441],{"href":439,"rel":440,"target":429},"https://laravel.com/docs/13.x/octane#ticks-and-intervals",[428],"Ticks and intervals"," for running a callback every N seconds",[422,444,445,450],{},[302,446,449],{"href":447,"rel":448,"target":429},"https://laravel.com/docs/13.x/octane#the-octane-cache",[428],"The Octane cache driver",", backed by Swoole tables",[422,452,453,458],{},[302,454,457],{"href":455,"rel":456,"target":429},"https://laravel.com/docs/13.x/octane#tables",[428],"Tables"," for sharing data between workers",[238,460,461],{},"If you need any of these, Swoole is the right call. If you don't, FrankenPHP will serve you better.",[242,463,465],{"id":464},"quick-start-with-frankenphp","Quick Start with FrankenPHP",[238,467,468],{},"Let's use this example project to get started.",[383,470,472],{"target":429,"to":471},"https://serversideup.net/open-source/spin/docs",[238,473,474,475,479],{},"This example assumes you already have a Laravel application installed. If you need help installing a new Laravel project with Docker, check out ",[302,476,478],{"href":471,"rel":477,"target":429},[428],"Spin"," for a simple way to get started.",[481,482,484],"h3",{"id":483},"classic-mode","Classic Mode",[238,486,487],{},"By default, FrankenPHP runs in classic mode. Your compose file might look something like this:",[489,490,495],"pre",{"className":491,"code":492,"filename":493,"language":494,"meta":10,"style":10},"language-yml shiki shiki-themes github-dark","services:\n  php:\n    image: serversideup/php:8.5-frankenphp\n    ports:\n      - \"80:8080\"\n    volumes:\n      - .:/var/www/html/\n","compose.yml","yml",[305,496,497,510,518,531,539,548,556],{"__ignoreMap":10},[498,499,502,506],"span",{"class":500,"line":501},"line",1,[498,503,505],{"class":504},"s4JwU","services",[498,507,509],{"class":508},"s95oV",":\n",[498,511,513,516],{"class":500,"line":512},2,[498,514,515],{"class":504},"  php",[498,517,509],{"class":508},[498,519,521,524,527],{"class":500,"line":520},3,[498,522,523],{"class":504},"    image",[498,525,526],{"class":508},": ",[498,528,530],{"class":529},"sU2Wk","serversideup/php:8.5-frankenphp\n",[498,532,534,537],{"class":500,"line":533},4,[498,535,536],{"class":504},"    ports",[498,538,509],{"class":508},[498,540,542,545],{"class":500,"line":541},5,[498,543,544],{"class":508},"      - ",[498,546,547],{"class":529},"\"80:8080\"\n",[498,549,551,554],{"class":500,"line":550},6,[498,552,553],{"class":504},"    volumes",[498,555,509],{"class":508},[498,557,559,561],{"class":500,"line":558},7,[498,560,544],{"class":508},[498,562,563],{"class":529},".:/var/www/html/\n",[238,565,566],{},"We'll expand upon this classic mode file and modify it to run Laravel Octane (which uses FrankenPHP's worker mode).",[481,568,570],{"id":569},"install-laravel-octane","Install Laravel Octane",[238,572,573,574,579],{},"First, install Octane in your Laravel application and tell it to use FrankenPHP. These are the same steps from the ",[302,575,578],{"href":576,"rel":577,"target":429},"https://laravel.com/docs/13.x/octane#installation",[428],"official Laravel documentation",":",[489,581,586],{"className":582,"code":583,"filename":584,"language":585,"meta":10,"style":10},"language-bash shiki shiki-themes github-dark","docker compose run php composer require laravel/octane\ndocker compose run php php artisan octane:install --server=frankenphp\n","Terminal","bash",[305,587,588,612],{"__ignoreMap":10},[498,589,590,594,597,600,603,606,609],{"class":500,"line":501},[498,591,593],{"class":592},"svObZ","docker",[498,595,596],{"class":529}," compose",[498,598,599],{"class":529}," run",[498,601,602],{"class":529}," php",[498,604,605],{"class":529}," composer",[498,607,608],{"class":529}," require",[498,610,611],{"class":529}," laravel/octane\n",[498,613,614,616,618,620,622,624,627,630],{"class":500,"line":512},[498,615,593],{"class":592},[498,617,596],{"class":529},[498,619,599],{"class":529},[498,621,602],{"class":529},[498,623,602],{"class":529},[498,625,626],{"class":529}," artisan",[498,628,629],{"class":529}," octane:install",[498,631,633],{"class":632},"sDLfK"," --server=frankenphp\n",[238,635,636,637,640,641,644,645,648,649,652],{},"The install command publishes ",[305,638,639],{},"config/octane.php",", sets ",[305,642,643],{},"OCTANE_SERVER=frankenphp"," in your ",[305,646,647],{},".env"," file, and creates a PHP file in your ",[305,650,651],{},"/public"," directory. This is the script FrankenPHP keeps running in memory, and Octane needs it to work.",[489,654,659],{"className":655,"code":656,"filename":657,"language":658,"meta":10,"style":10},"language-php shiki shiki-themes github-dark","\u003C?php\n\n// Set a default for the application base path and public path if they are missing...\n$_SERVER['APP_BASE_PATH'] = $_ENV['APP_BASE_PATH'] ?? $_SERVER['APP_BASE_PATH'] ?? __DIR__.'/..';\n$_SERVER['APP_PUBLIC_PATH'] = $_ENV['APP_PUBLIC_PATH'] ?? $_SERVER['APP_PUBLIC_PATH'] ?? __DIR__;\n\nrequire __DIR__.'/../vendor/laravel/octane/bin/frankenphp-worker.php';\n\n","public/frankenphp-worker.php","php",[305,660,661,670,676,682,726,757,761],{"__ignoreMap":10},[498,662,663,667],{"class":500,"line":501},[498,664,666],{"class":665},"snl16","\u003C?",[498,668,669],{"class":632},"php\n",[498,671,672],{"class":500,"line":512},[498,673,675],{"emptyLinePlaceholder":674},true,"\n",[498,677,678],{"class":500,"line":520},[498,679,681],{"class":680},"sAwPA","// Set a default for the application base path and public path if they are missing...\n",[498,683,684,687,690,693,696,699,701,703,706,709,711,713,715,718,720,723],{"class":500,"line":533},[498,685,686],{"class":508},"$_SERVER[",[498,688,689],{"class":529},"'APP_BASE_PATH'",[498,691,692],{"class":508},"] ",[498,694,695],{"class":665},"=",[498,697,698],{"class":508}," $_ENV[",[498,700,689],{"class":529},[498,702,692],{"class":508},[498,704,705],{"class":665},"??",[498,707,708],{"class":508}," $_SERVER[",[498,710,689],{"class":529},[498,712,692],{"class":508},[498,714,705],{"class":665},[498,716,717],{"class":632}," __DIR__",[498,719,411],{"class":665},[498,721,722],{"class":529},"'/..'",[498,724,725],{"class":508},";\n",[498,727,728,730,733,735,737,739,741,743,745,747,749,751,753,755],{"class":500,"line":541},[498,729,686],{"class":508},[498,731,732],{"class":529},"'APP_PUBLIC_PATH'",[498,734,692],{"class":508},[498,736,695],{"class":665},[498,738,698],{"class":508},[498,740,732],{"class":529},[498,742,692],{"class":508},[498,744,705],{"class":665},[498,746,708],{"class":508},[498,748,732],{"class":529},[498,750,692],{"class":508},[498,752,705],{"class":665},[498,754,717],{"class":632},[498,756,725],{"class":508},[498,758,759],{"class":500,"line":550},[498,760,675],{"emptyLinePlaceholder":674},[498,762,763,766,768,770,773],{"class":500,"line":558},[498,764,765],{"class":665},"require",[498,767,717],{"class":632},[498,769,411],{"class":665},[498,771,772],{"class":529},"'/../vendor/laravel/octane/bin/frankenphp-worker.php'",[498,774,725],{"class":508},[776,777,778],"note",{},[238,779,780,783,784,787,788,791,792,795,796,799],{},[305,781,782],{},"octane:install"," also adds ",[305,785,786],{},"frankenphp-worker.php"," to your ",[305,789,790],{},".gitignore",". We recommend removing that line and committing the file, so it is present in every build of your image. If you leave it ignored, ",[305,793,794],{},"octane:start"," recreates the file when the container starts, but only if the ",[305,797,798],{},"public"," directory is writable.",[481,801,803],{"id":802},"configure-worker-mode","Configure Worker Mode",[238,805,806],{},"We now want to update the compose file to start Octane instead of FrankenPHP's classic mode.",[489,808,813],{"className":491,"code":809,"filename":493,"highlights":810,"language":494,"meta":10,"style":10},"services:\n  php:\n    image: serversideup/php:8.5-frankenphp\n    ports:\n      - \"80:8080\"\n    volumes:\n      - .:/var/www/html/\n    # Start Octane in worker mode with our production-ready Caddyfile\n    command: [\"php\", \"artisan\", \"octane:start\", \"--server=frankenphp\", \"--port=8080\", \"--caddyfile=/etc/frankenphp/Caddyfile\"]\n",[811,812],8,9,[305,814,815,821,827,835,841,847,853,859,866],{"__ignoreMap":10},[498,816,817,819],{"class":500,"line":501},[498,818,505],{"class":504},[498,820,509],{"class":508},[498,822,823,825],{"class":500,"line":512},[498,824,515],{"class":504},[498,826,509],{"class":508},[498,828,829,831,833],{"class":500,"line":520},[498,830,523],{"class":504},[498,832,526],{"class":508},[498,834,530],{"class":529},[498,836,837,839],{"class":500,"line":533},[498,838,536],{"class":504},[498,840,509],{"class":508},[498,842,843,845],{"class":500,"line":541},[498,844,544],{"class":508},[498,846,547],{"class":529},[498,848,849,851],{"class":500,"line":550},[498,850,553],{"class":504},[498,852,509],{"class":508},[498,854,855,857],{"class":500,"line":558},[498,856,544],{"class":508},[498,858,563],{"class":529},[498,860,863],{"class":861,"line":811},[500,862],"highlight",[498,864,865],{"class":680},"    # Start Octane in worker mode with our production-ready Caddyfile\n",[498,867,869,872,875,878,880,883,885,888,890,893,895,898,900,903],{"class":868,"line":812},[500,862],[498,870,871],{"class":504},"    command",[498,873,874],{"class":508},": [",[498,876,877],{"class":529},"\"php\"",[498,879,395],{"class":508},[498,881,882],{"class":529},"\"artisan\"",[498,884,395],{"class":508},[498,886,887],{"class":529},"\"octane:start\"",[498,889,395],{"class":508},[498,891,892],{"class":529},"\"--server=frankenphp\"",[498,894,395],{"class":508},[498,896,897],{"class":529},"\"--port=8080\"",[498,899,395],{"class":508},[498,901,902],{"class":529},"\"--caddyfile=/etc/frankenphp/Caddyfile\"",[498,904,905],{"class":508},"]\n",[238,907,908],{},"The command does three things:",[910,911,912,925,953],"ol",{},[422,913,914,919,920,924],{},[252,915,916],{},[305,917,918],{},"octane:start --server=frankenphp"," starts FrankenPHP in worker mode through Octane, exactly as the ",[302,921,578],{"href":922,"rel":923,"target":429},"https://laravel.com/docs/13.x/octane#serving-your-application",[428]," describes.",[422,926,927,932,933,936,937,940,941,943,944,947,948,411],{},[252,928,929],{},[305,930,931],{},"--port=8080"," matches ",[305,934,935],{},"CADDY_HTTP_PORT",", which defaults to ",[305,938,939],{},"8080",". Our Caddyfile listens on ",[305,942,935],{},", so if you change that variable, change ",[305,945,946],{},"--port"," to match. The port is written out because Docker's exec form (the JSON array syntax) ",[302,949,952],{"href":950,"rel":951,"target":429},"https://docs.docker.com/reference/dockerfile/#exec-form",[428],"does not perform variable substitution",[422,954,955,960,961,966,967,969],{},[252,956,957],{},[305,958,959],{},"--caddyfile=/etc/frankenphp/Caddyfile"," tells Octane to use the Caddyfile that ships with this image instead of its own. Laravel documents this option under ",[302,962,965],{"href":963,"rel":964,"target":429},"https://laravel.com/docs/13.x/octane#frankenphp-caddyfile",[428],"Custom Caddyfile Configuration",". Octane's own Caddyfile is a minimal configuration, so without this flag you lose our trusted proxy support, security headers, asset caching, SSL modes, health check endpoint, and most of the ",[305,968,314],{}," environment variables.",[238,971,972,973,976,977,979,980,983,984,987,988,991,992,995],{},"Octane sets ",[305,974,975],{},"LARAVEL_OCTANE=1"," in FrankenPHP's environment when it starts the server. Our Caddyfile detects this and switches on the settings Octane expects: the worker script, routing every request to ",[305,978,786],{}," instead of ",[305,981,982],{},"index.php",", and the Caddy admin API that ",[305,985,986],{},"octane:status"," and ",[305,989,990],{},"octane:reload"," rely on. Everything else in the Caddyfile stays the same as classic mode, including logs on ",[305,993,994],{},"stderr",", which is the only stream Octane relays.",[776,997,998],{},[238,999,1000,1001,1004,1005,1010],{},"Octane always uses its own Caddyfile unless you pass ",[305,1002,1003],{},"--caddyfile",". The ",[302,1006,1009],{"href":1007,"rel":1008,"target":429},"https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile",[428],"Octane Caddyfile"," is a good reference for the variables Octane controls, but we recommend passing our Caddyfile so you get the same production hardening as classic mode.",[481,1012,1014],{"id":1013},"testing-locally","Testing Locally",[238,1016,1017],{},"Run your application locally to test Octane:",[489,1019,1021],{"className":582,"code":1020,"filename":584,"language":585,"meta":10,"style":10},"docker compose up\n",[305,1022,1023],{"__ignoreMap":10},[498,1024,1025,1027,1029],{"class":500,"line":501},[498,1026,593],{"class":592},[498,1028,596],{"class":529},[498,1030,1031],{"class":529}," up\n",[238,1033,1034,1035,1038],{},"Your Laravel application will be available at ",[305,1036,1037],{},"http://localhost"," with Octane running in worker mode.",[242,1040,1042],{"id":1041},"health-checks","Health Checks",[238,1044,1045,1046,1049],{},"The image's built-in health check keeps working in Octane mode, because our Caddyfile still serves the ",[305,1047,1048],{},"/healthcheck"," endpoint. No changes are required.",[238,1051,1052,1053,1056,1057,1064],{},"If you would rather use Octane's own status check, the image also ships a ",[305,1054,1055],{},"healthcheck-octane"," command. It runs ",[302,1058,1061],{"href":1059,"rel":1060,"target":429},"https://laravel.com/docs/13.x/octane#checking-the-server-status",[428],[305,1062,1063],{},"php artisan octane:status",", which asks the Caddy admin API whether the server Octane started is still running:",[489,1066,1068],{"className":491,"code":1067,"filename":493,"language":494,"meta":10,"style":10},"services:\n  php:\n    # ...\n    healthcheck:\n      test: [\"CMD\", \"healthcheck-octane\"]\n      start_period: 10s\n",[305,1069,1070,1076,1082,1087,1094,1111],{"__ignoreMap":10},[498,1071,1072,1074],{"class":500,"line":501},[498,1073,505],{"class":504},[498,1075,509],{"class":508},[498,1077,1078,1080],{"class":500,"line":512},[498,1079,515],{"class":504},[498,1081,509],{"class":508},[498,1083,1084],{"class":500,"line":520},[498,1085,1086],{"class":680},"    # ...\n",[498,1088,1089,1092],{"class":500,"line":533},[498,1090,1091],{"class":504},"    healthcheck",[498,1093,509],{"class":508},[498,1095,1096,1099,1101,1104,1106,1109],{"class":500,"line":541},[498,1097,1098],{"class":504},"      test",[498,1100,874],{"class":508},[498,1102,1103],{"class":529},"\"CMD\"",[498,1105,395],{"class":508},[498,1107,1108],{"class":529},"\"healthcheck-octane\"",[498,1110,905],{"class":508},[498,1112,1113,1116,1118],{"class":500,"line":550},[498,1114,1115],{"class":504},"      start_period",[498,1117,526],{"class":508},[498,1119,1120],{"class":529},"10s\n",[242,1122,1124],{"id":1123},"logging","Logging",[238,1126,1127,1128,1130,1131,1133,1134,1139,1140,1148,1149,411],{},"Octane relays FrankenPHP's log output to your console, but it only reads ",[305,1129,994],{}," and it only understands JSON. Our Caddyfile satisfies both without any settings. The FrankenPHP variation writes to ",[305,1132,994],{}," by default, which is ",[302,1135,1138],{"href":1136,"rel":1137,"target":429},"https://caddyserver.com/docs/caddyfile/directives/log#output",[428],"Caddy's default",", and Caddy ",[302,1141,1144,1145,1147],{"href":1142,"rel":1143,"target":429},"https://caddyserver.com/docs/caddyfile/directives/log#format",[428],"writes JSON whenever ",[305,1146,994],{}," is not a terminal",", which is always the case when Octane starts FrankenPHP. Read more about ",[302,1150,1152],{"href":1151},"/docs/getting-started/default-configurations#logging","how we approach logging",[383,1154,1155],{},[238,1156,1157,1158,987,1161,1164,1165,1168,1169,1172,1173,1176],{},"Leave ",[305,1159,1160],{},"CADDY_LOG_OUTPUT",[305,1162,1163],{},"CADDY_LOG_FORMAT"," at their defaults when you run Octane. With ",[305,1166,1167],{},"CADDY_LOG_OUTPUT=stdout",", Octane discards the output and your logs disappear. With ",[305,1170,1171],{},"CADDY_LOG_FORMAT=console",", Octane cannot parse the lines and wraps each one in an ",[305,1174,1175],{},"INFO"," message, including Caddy's warnings and errors.",[238,1178,1179],{},"Here is what you will see:",[419,1181,1182,1189,1192],{},[422,1183,1184,1185,1188],{},"In the ",[305,1186,1187],{},"local"," environment, Octane prints one line per request with the method, path, status code, and duration. This is the same output you get with Octane's own Caddyfile.",[422,1190,1191],{},"In every other environment, Octane prints Caddy's warnings and errors and drops the request lines.",[422,1193,1194,1195,1198,1199,1204],{},"With ",[305,1196,1197],{},"--log-level",", Octane passes Caddy's JSON through untouched. Laravel ",[302,1200,1203],{"href":1201,"rel":1202,"target":429},"https://laravel.com/docs/13.x/octane#frankenphp-via-docker",[428],"documents this option"," as the switch to FrankenPHP's native logger that \"will produce structured JSON logs\", and that is exactly what you get.",[238,1206,1207,1208,1211,1212,1214,1215,1218,1219,1221,1222,1225,1226,1229,1230,1238,1239,1241],{},"Octane also decides the log level. It sets ",[305,1209,1210],{},"CADDY_SERVER_LOG_LEVEL"," to ",[305,1213,1175],{}," when ",[305,1216,1217],{},"APP_ENV"," is ",[305,1220,1187],{}," and to ",[305,1223,1224],{},"WARN"," everywhere else, which takes precedence over ",[305,1227,1228],{},"LOG_OUTPUT_LEVEL",". Caddy ",[302,1231,1234,1235,1237],{"href":1232,"rel":1233,"target":429},"https://caddyserver.com/docs/caddyfile/directives/log#level",[428],"writes request logs at the ",[305,1236,1175],{}," level",", so at ",[305,1240,1224],{}," successful requests no longer appear in your logs.",[776,1243,1244],{},[238,1245,1246,1247,1211,1250,1252,1253,1255],{},"If you want request logs in production, pass ",[305,1248,1249],{},"--log-level=INFO",[305,1251,794],{},". You get one JSON object per request on ",[305,1254,994],{},", ready for your log collector.",[238,1257,1258,1259,1262,1263,1268],{},"The request log redacts the ",[305,1260,1261],{},"authorization"," query parameter, so the JWT that Mercure 0.x subscribers pass in the URL never lands in your logs. This is the same filter that ",[302,1264,1267],{"href":1265,"rel":1266,"target":429},"https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile",[428],"FrankenPHP's own Caddyfile"," recommends.",[242,1270,1272],{"id":1271},"php-settings-still-apply","PHP Settings Still Apply",[238,1274,1275,1276,1279,1280,1283,1284,1287,1288,987,1291,1294,1295,1298],{},"Octane does not change how PHP loads its configuration. FrankenPHP reads the same ",[305,1277,1278],{},"php.ini"," files from ",[305,1281,1282],{},"/usr/local/etc/php/conf.d/"," in every mode, so all of the ",[305,1285,1286],{},"PHP_*"," environment variables (like ",[305,1289,1290],{},"PHP_MEMORY_LIMIT",[305,1292,1293],{},"PHP_OPCACHE_ENABLE",") work exactly as they do in classic mode. Any custom ",[305,1296,1297],{},".ini"," files you mount into that directory apply as well.",[413,1300,1301],{},[238,1302,1303,1304,1307,1308,1311],{},"Enable OPcache with ",[305,1305,1306],{},"PHP_OPCACHE_ENABLE=1"," when you run Octane in production. See ",[302,1309,1310],{"href":193},"Changing common PHP settings"," for the full list of options.",[242,1313,1315],{"id":1314},"octane-options","Octane Options",[238,1317,1318],{},"Octane passes its command line options to FrankenPHP through environment variables. Here is how they behave with our Caddyfile:",[270,1320,1321,1331],{},[273,1322,1323],{},[276,1324,1325,1328],{},[279,1326,1327],{},"Option",[279,1329,1330],{},"Behavior with our Caddyfile",[289,1332,1333,1357,1367,1382,1401,1427,1454,1496,1514],{},[276,1334,1335,1339],{},[294,1336,1337],{},[305,1338,946],{},[294,1340,1341,1342,1344,1345,1347,1348,1350,1351,1353,1354,411],{},"Keep this the same as ",[305,1343,935],{}," (default ",[305,1346,939],{},"). Our Caddyfile listens on ",[305,1349,935],{},". Octane also derives its admin port from this value, so ",[305,1352,931],{}," puts the admin API on port ",[305,1355,1356],{},"2099",[276,1358,1359,1364],{},[294,1360,1361],{},[305,1362,1363],{},"--host",[294,1365,1366],{},"Not used. The container listens on all interfaces, like classic mode.",[276,1368,1369,1374],{},[294,1370,1371],{},[305,1372,1373],{},"--workers",[294,1375,1376,1377,411],{},"Works. Sets the number of PHP threads FrankenPHP starts for the Octane worker. Leave it out and FrankenPHP ",[302,1378,1381],{"href":1379,"rel":1380,"target":429},"https://frankenphp.dev/docs/config/#caddyfile-config",[428],"defaults to twice the number of CPUs",[276,1383,1384,1389],{},[294,1385,1386],{},[305,1387,1388],{},"--max-requests",[294,1390,1391,1392,1397,1398,411],{},"Works. The worker exits after this many requests and FrankenPHP starts a fresh one, which ",[302,1393,1396],{"href":1394,"rel":1395,"target":429},"https://laravel.com/docs/13.x/octane#specifying-the-max-request-count",[428],"limits the impact of memory leaks",". Defaults to ",[305,1399,1400],{},"500",[276,1402,1403,1408],{},[294,1404,1405],{},[305,1406,1407],{},"--watch",[294,1409,1410,1411,1414,1415,1211,1417,1422,1423,1426],{},"Works. Passes the ",[305,1412,1413],{},"watch"," paths from ",[305,1416,639],{},[302,1418,1421],{"href":1419,"rel":1420,"target":429},"https://frankenphp.dev/docs/config/#watching-for-file-changes",[428],"FrankenPHP's file watcher",", which is built into our image. Node and Chokidar are not needed. ",[305,1424,1425],{},"--poll"," has no effect with FrankenPHP.",[276,1428,1429,1434],{},[294,1430,1431],{},[305,1432,1433],{},"--admin-port",[294,1435,1436,1437,395,1439,1441,1442,1445,1446,1449,1450,1453],{},"Works. ",[305,1438,986],{},[305,1440,990],{},", and ",[305,1443,1444],{},"octane:stop"," use this port, and ",[305,1447,1448],{},"CADDY_ADMIN"," is ignored. The admin API only listens on ",[305,1451,1452],{},"localhost"," inside the container.",[276,1455,1456,1461],{},[294,1457,1458],{},[305,1459,1460],{},"--admin-host",[294,1462,1463,1464,1466,1467,1469,1470,1473,1474,1479,1480,1483,1484,1487,1488,1491,1492,1495],{},"Not available on ",[305,1465,794],{},", which always binds the admin API to ",[305,1468,1452],{},". The hidden ",[305,1471,1472],{},"octane:frankenphp"," command accepts it, but leave it alone. The ",[302,1475,1478],{"href":1476,"rel":1477,"target":429},"https://caddyserver.com/docs/api",[428],"admin API has no authentication"," and can reconfigure the web server, so binding it to ",[305,1481,1482],{},"0.0.0.0"," hands that control to anything that can reach the container. The image also lists port ",[305,1485,1486],{},"2019"," in ",[305,1489,1490],{},"EXPOSE",", so ",[305,1493,1494],{},"docker run -P"," would publish it to the host.",[276,1497,1498,1502],{},[294,1499,1500],{},[305,1501,1197],{},[294,1503,1504,1505,1507,1508,1510,1511,411],{},"Works. This sets ",[305,1506,1210],{},", which takes precedence over ",[305,1509,1228],{},", and switches Octane to relaying Caddy's JSON lines untouched. See ",[302,1512,1124],{"href":1513},"#logging",[276,1515,1516,1524],{},[294,1517,1518,987,1521],{},[305,1519,1520],{},"--https",[305,1522,1523],{},"--http-redirect",[294,1525,1526,1527,987,1530,1533,1534,411],{},"Not used. Configure HTTPS with ",[305,1528,1529],{},"SSL_MODE",[305,1531,1532],{},"CADDY_AUTO_HTTPS"," instead, the same as classic mode. See ",[302,1535,1537],{"href":1536},"/docs/image-variations/frankenphp#automatic-https","Automatic HTTPS",[238,1539,1540,1541,1544,1545,1547,1548,1553],{},"The ",[305,1542,1543],{},"max_execution_time"," setting in ",[305,1546,639],{}," works as ",[302,1549,1552],{"href":1550,"rel":1551,"target":429},"https://laravel.com/docs/13.x/octane#specifying-the-max-execution-time",[428],"documented by Laravel",", because Octane passes it to the worker script rather than to the Caddyfile.",[238,1555,1556,1557,987,1560,1563],{},"Octane also sets ",[305,1558,1559],{},"CADDY_GLOBAL_OPTIONS",[305,1561,1562],{},"CADDY_SERVER_EXTRA_DIRECTIVES"," for its own use, so any value you set for those variables is replaced when Octane starts FrankenPHP:",[419,1565,1566,1599],{},[422,1567,1568,1570,1571,1574,1575,1577,1578,1581,1582,1586,1587,1590,1591,1594,1595,411],{},[305,1569,1562],{}," carries the ",[305,1572,1573],{},"mercure"," array from ",[305,1576,639],{},". Use the ",[305,1579,1580],{},"MERCURE_*"," variables instead. See ",[302,1583,1585],{"href":1584},"#mercure","Mercure",". For your own directives, like headers or redirects, mount a ",[305,1588,1589],{},".caddyfile"," into ",[305,1592,1593],{},"/etc/frankenphp/caddyfile-server.d/",". See ",[302,1596,1598],{"href":1597},"/docs/image-variations/frankenphp#adding-your-own-caddyfile-rules","Adding your own Caddyfile rules",[422,1600,1601,1603,1604,1607,1608,1610,1611,1590,1613,411],{},[305,1602,1559],{}," is not applied in Octane mode, because Octane sets it to ",[305,1605,1606],{},"auto_https disable_redirects",", which would conflict with ",[305,1609,1532],{},". If you need additional global options, mount a ",[305,1612,1589],{},[305,1614,1615],{},"/etc/frankenphp/caddyfile-global.d/",[238,1617,1618,1619,1622,1623,411],{},"Octane's own Caddyfile asks for JSON logs through ",[305,1620,1621],{},"CADDY_SERVER_LOGGER",". Our Caddyfile does not read that variable, because Caddy already writes JSON when Octane starts it. See ",[302,1624,1124],{"href":1513},[413,1626,1627,1639,1687],{},[238,1628,1629,1630,1633,1634,1636,1637,579],{},"Octane 2.14 and newer let you override any of the variables Octane sets through the ",[305,1631,1632],{},"caddy.env"," option in ",[305,1635,639],{},". For example, this enables request logs in every environment without passing ",[305,1638,1197],{},[489,1640,1642],{"className":655,"code":1641,"filename":639,"language":658,"meta":10,"style":10},"'caddy' => [\n    'env' => [\n        'CADDY_SERVER_LOG_LEVEL' => 'INFO',\n    ],\n],\n",[305,1643,1644,1655,1664,1677,1682],{"__ignoreMap":10},[498,1645,1646,1649,1652],{"class":500,"line":501},[498,1647,1648],{"class":529},"'caddy'",[498,1650,1651],{"class":665}," =>",[498,1653,1654],{"class":508}," [\n",[498,1656,1657,1660,1662],{"class":500,"line":512},[498,1658,1659],{"class":529},"    'env'",[498,1661,1651],{"class":665},[498,1663,1654],{"class":508},[498,1665,1666,1669,1671,1674],{"class":500,"line":520},[498,1667,1668],{"class":529},"        'CADDY_SERVER_LOG_LEVEL'",[498,1670,1651],{"class":665},[498,1672,1673],{"class":529}," 'INFO'",[498,1675,1676],{"class":508},",\n",[498,1678,1679],{"class":500,"line":533},[498,1680,1681],{"class":508},"    ],\n",[498,1683,1684],{"class":500,"line":541},[498,1685,1686],{"class":508},"],\n",[238,1688,1689,1690,1695],{},"See the ",[302,1691,1694],{"href":1692,"rel":1693,"target":429},"https://github.com/laravel/octane/releases/tag/v2.14.0",[428],"Octane 2.14.0 release notes"," for details.",[238,1697,972,1698,1701,1702,1487,1704,1707,1708,1711,1712,1714],{},[305,1699,1700],{},"APP_PUBLIC_PATH"," to your application's public directory, but our Caddyfile looks for ",[305,1703,786],{},[305,1705,1706],{},"CADDY_SERVER_ROOT"," instead. This keeps the worker script and the document root in the same place. If you changed ",[305,1709,1710],{},"APP_BASE_DIR",", set ",[305,1713,1706],{}," to match, just like classic mode.",[242,1716,1585],{"id":1573},[238,1718,1719,1720,1722,1723,1725,1726,1728,1729],{},"Turn on FrankenPHP's Mercure hub with the ",[305,1721,1580],{}," environment variables, the same as classic mode, and leave the ",[305,1724,1573],{}," array out of ",[305,1727,639],{},". ",[302,1730,1732],{"href":1731},"/docs/image-variations/frankenphp#mercure","Read how to set up Mercure →",[238,1734,1735,1740,1741,1743,1744,1747,1748,1751],{},[302,1736,1739],{"href":1737,"rel":1738,"target":429},"https://frankenphp.dev/docs/laravel/#mercure-support",[428],"FrankenPHP's Laravel docs"," configure the hub through that array instead. Octane passes it to our Caddyfile in ",[305,1742,1562],{},", which lands in every site block. With ",[305,1745,1746],{},"SSL_MODE=mixed"," or ",[305,1749,1750],{},"full"," your app is served from more than one site block, so the array creates more than one hub, and Mercure 1.0 refuses to start with more than one unnamed hub. The environment variables name the hub, so every site shares it.",[242,1753,410],{"id":1754},"running-octane-without-frankenphp",[238,1756,1757,1758,1762,1763,1766,1767,1770,1771,1774,1775,1778],{},"Swoole, Open Swoole, and RoadRunner all run from the ",[302,1759,1760],{"href":63},[305,1761,329],{}," image. It already ships what Octane needs on the PHP side: the ",[305,1764,1765],{},"pcntl"," extension, Composer, ",[305,1768,1769],{},"install-php-extensions",", our entrypoint scripts (including the ",[302,1772,1773],{"href":97},"Laravel automations","), and an unprivileged ",[305,1776,1777],{},"www-data"," user.",[238,1780,1781,1782,1786],{},"What it does not ship is a web server, and that is the whole tradeoff. Read ",[302,1783,1785],{"href":1784},"#what-you-give-up","What You Give Up"," first.",[481,1788,1785],{"id":1789},"what-you-give-up",[238,1791,1792,1793,1795,1796,1798],{},"Our ",[305,1794,307],{}," variation hardens your application in the Caddyfile that ships inside the image. Swoole has no equivalent: its HTTP server is the PHP process itself, configured from your application's ",[305,1797,639],{},", with no place to express caching rules, header policies, or path matching. RoadRunner has a configuration file, but nothing in our images populates it for you.",[238,1800,1801],{},"Here is what moves from the image back onto your plate:",[270,1803,1804,1824],{},[273,1805,1806],{},[276,1807,1808,1811,1816,1821],{},[279,1809,1810],{},"Protection",[279,1812,1814,308],{"align":1813},"center",[305,1815,307],{},[279,1817,1818,1820],{"align":1813},[305,1819,329],{}," + Swoole or RoadRunner",[279,1822,1823],{},"Where it has to live instead",[289,1825,1826,1858,1872,1886,1906,1923,1942,1955,1974],{},[276,1827,1828,1849,1852,1855],{},[294,1829,1830,1831,395,1833,395,1836,395,1839,395,1842,395,1845,1848],{},"Blocking ",[305,1832,647],{},[305,1834,1835],{},".git/",[305,1837,1838],{},"*.bak",[305,1840,1841],{},"*.log",[305,1843,1844],{},"*.sql",[305,1846,1847],{},"*.ini",", and dotfiles",[294,1850,1851],{"align":1813},"✅",[294,1853,1854],{"align":1813},"❌",[294,1856,1857],{},"Reverse proxy",[276,1859,1860,1866,1868,1870],{},[294,1861,1862,1865],{},[305,1863,1864],{},"Cache-Control: immutable"," on static assets",[294,1867,1851],{"align":1813},[294,1869,1854],{"align":1813},[294,1871,1857],{},[276,1873,1874,1880,1882,1884],{},[294,1875,1876,1879],{},[305,1877,1878],{},"Access-Control-Allow-Origin"," on fonts",[294,1881,1851],{"align":1813},[294,1883,1854],{"align":1813},[294,1885,1857],{},[276,1887,1888,1899,1901,1903],{},[294,1889,1890,395,1893,395,1896],{},[305,1891,1892],{},"X-Frame-Options",[305,1894,1895],{},"X-Content-Type-Options",[305,1897,1898],{},"Referrer-Policy",[294,1900,1851],{"align":1813},[294,1902,1854],{"align":1813},[294,1904,1905],{},"Laravel middleware or reverse proxy",[276,1907,1908,1917,1919,1921],{},[294,1909,1910,1911,987,1913,1916],{},"Removing the ",[305,1912,281],{},[305,1914,1915],{},"X-Powered-By"," headers",[294,1918,1851],{"align":1813},[294,1920,1854],{"align":1813},[294,1922,1857],{},[276,1924,1925,1931,1933,1935],{},[294,1926,1927,1928],{},"Trusted proxies via ",[305,1929,1930],{},"TRUSTED_PROXY",[294,1932,1851],{"align":1813},[294,1934,1854],{"align":1813},[294,1936,1937,1938,1941],{},"Laravel's ",[305,1939,1940],{},"TrustProxies"," middleware",[276,1943,1944,1949,1951,1953],{},[294,1945,1946,1948],{},[305,1947,1529],{},", automatic HTTPS, and ACME certificates",[294,1950,1851],{"align":1813},[294,1952,1854],{"align":1813},[294,1954,1857],{},[276,1956,1957,1965,1967,1969],{},[294,1958,1540,1959,1961,1962],{},[305,1960,1048],{}," endpoint and ",[305,1963,1964],{},"HEALTHCHECK_PATH",[294,1966,1851],{"align":1813},[294,1968,1854],{"align":1813},[294,1970,1971,1972],{},"A Laravel route, or ",[305,1973,1055],{},[276,1975,1976,1982,1984,1986],{},[294,1977,1978,1979,1981],{},"Access logs with the ",[305,1980,1261],{}," parameter redacted",[294,1983,1851],{"align":1813},[294,1985,1854],{"align":1813},[294,1987,1857],{},[383,1989,1990,1993,2020,2046],{},[238,1991,1992],{},"The first row is the one that bites, and the two servers fail differently.",[238,1994,1995,1997,1998,2000,2001,2004,2005,2007,2008,2011,2012,2015,2016,2019],{},[252,1996,322],{}," serves every non-PHP file in your public directory exactly as it finds it, with no blocklist. A ",[305,1999,647],{},", a ",[305,2002,2003],{},".git"," directory, a database dump, or a stray ",[305,2006,1838],{}," is handed to anyone who asks, with a ",[305,2009,2010],{},"200"," and the file's full contents. It does refuse ",[305,2013,2014],{},".php"," files, so an uploaded PHP file returns ",[305,2017,2018],{},"404"," instead of running.",[238,2021,2022,2024,2025,2028,2029,2033,2034,2036,2037,2040,2041,2045],{},[252,2023,362],{}," is worse out of the box. Octane starts it with ",[305,2026,2027],{},"http.static.dir"," pointed at your public directory and no forbidden extensions, so it serves ",[2030,2031,2032],"em",{},"everything",", including ",[305,2035,2014],{}," files as ",[305,2038,2039],{},"text/plain"," source. See ",[302,2042,2044],{"href":2043},"#locking-down-roadrunners-static-files","Locking down RoadRunner's static files"," for the fix.",[238,2047,2048,2049,2052],{},"Our Caddyfile answers ",[305,2050,2051],{},"403"," for all of these. Put a reverse proxy in front of Octane in production.",[238,2054,2055,2056,2058,2059,2062,2063,2065,2066,2068],{},"Everything below the HTTP layer still works exactly as it does everywhere else. All of the ",[305,2057,1286],{}," environment variables, the ",[305,2060,2061],{},"AUTORUN_*"," ",[302,2064,1773],{"href":97},", the unprivileged user, and the file permission handling are part of the ",[305,2067,329],{}," image, not the web server.",[481,2070,2072],{"id":2071},"running-octane-on-swoole","Running Octane on Swoole",[238,2074,2075,2076,579],{},"Swoole is a PHP extension, so install it in your own ",[305,2077,2078],{},"Dockerfile",[489,2080,2084],{"className":2081,"code":2082,"filename":2078,"language":2083,"meta":10,"style":10},"language-dockerfile shiki shiki-themes github-dark","FROM serversideup/php:8.5-cli\n\nUSER root\nRUN install-php-extensions swoole\nUSER www-data\n","dockerfile",[305,2085,2086,2094,2098,2106,2114],{"__ignoreMap":10},[498,2087,2088,2091],{"class":500,"line":501},[498,2089,2090],{"class":665},"FROM",[498,2092,2093],{"class":508}," serversideup/php:8.5-cli\n",[498,2095,2096],{"class":500,"line":512},[498,2097,675],{"emptyLinePlaceholder":674},[498,2099,2100,2103],{"class":500,"line":520},[498,2101,2102],{"class":665},"USER",[498,2104,2105],{"class":508}," root\n",[498,2107,2108,2111],{"class":500,"line":533},[498,2109,2110],{"class":665},"RUN",[498,2112,2113],{"class":508}," install-php-extensions swoole\n",[498,2115,2116,2118],{"class":500,"line":541},[498,2117,2102],{"class":665},[498,2119,2120],{"class":508}," www-data\n",[238,2122,2123,2124,2126],{},"For Open Swoole, use ",[305,2125,352],{}," instead. Everything else is identical.",[238,2128,2129],{},"Install Octane and point it at Swoole:",[489,2131,2133],{"className":582,"code":2132,"filename":584,"language":585,"meta":10,"style":10},"docker compose run php composer require laravel/octane\ndocker compose run php php artisan octane:install --server=swoole\n",[305,2134,2135,2151],{"__ignoreMap":10},[498,2136,2137,2139,2141,2143,2145,2147,2149],{"class":500,"line":501},[498,2138,593],{"class":592},[498,2140,596],{"class":529},[498,2142,599],{"class":529},[498,2144,602],{"class":529},[498,2146,605],{"class":529},[498,2148,608],{"class":529},[498,2150,611],{"class":529},[498,2152,2153,2155,2157,2159,2161,2163,2165,2167],{"class":500,"line":512},[498,2154,593],{"class":592},[498,2156,596],{"class":529},[498,2158,599],{"class":529},[498,2160,602],{"class":529},[498,2162,602],{"class":529},[498,2164,626],{"class":529},[498,2166,629],{"class":529},[498,2168,2169],{"class":632}," --server=swoole\n",[238,2171,2172],{},"Then start it from your compose file:",[489,2174,2178],{"className":491,"code":2175,"filename":493,"highlights":2176,"language":494,"meta":10,"style":10},"services:\n  php:\n    build:\n      context: .\n    ports:\n      - \"80:8000\"\n    volumes:\n      - .:/var/www/html/\n    # --host=0.0.0.0 is required so the port is reachable outside the container\n    command: [\"php\", \"artisan\", \"octane:start\", \"--server=swoole\", \"--host=0.0.0.0\", \"--port=8000\"]\n    healthcheck:\n      test: [\"CMD\", \"healthcheck-octane\"]\n      start_period: 10s\n",[2177],10,[305,2179,2180,2186,2192,2199,2209,2215,2222,2228,2234,2239,2273,2280,2295],{"__ignoreMap":10},[498,2181,2182,2184],{"class":500,"line":501},[498,2183,505],{"class":504},[498,2185,509],{"class":508},[498,2187,2188,2190],{"class":500,"line":512},[498,2189,515],{"class":504},[498,2191,509],{"class":508},[498,2193,2194,2197],{"class":500,"line":520},[498,2195,2196],{"class":504},"    build",[498,2198,509],{"class":508},[498,2200,2201,2204,2206],{"class":500,"line":533},[498,2202,2203],{"class":504},"      context",[498,2205,526],{"class":508},[498,2207,2208],{"class":632},".\n",[498,2210,2211,2213],{"class":500,"line":541},[498,2212,536],{"class":504},[498,2214,509],{"class":508},[498,2216,2217,2219],{"class":500,"line":550},[498,2218,544],{"class":508},[498,2220,2221],{"class":529},"\"80:8000\"\n",[498,2223,2224,2226],{"class":500,"line":558},[498,2225,553],{"class":504},[498,2227,509],{"class":508},[498,2229,2230,2232],{"class":500,"line":811},[498,2231,544],{"class":508},[498,2233,563],{"class":529},[498,2235,2236],{"class":500,"line":812},[498,2237,2238],{"class":680},"    # --host=0.0.0.0 is required so the port is reachable outside the container\n",[498,2240,2242,2244,2246,2248,2250,2252,2254,2256,2258,2261,2263,2266,2268,2271],{"class":2241,"line":2177},[500,862],[498,2243,871],{"class":504},[498,2245,874],{"class":508},[498,2247,877],{"class":529},[498,2249,395],{"class":508},[498,2251,882],{"class":529},[498,2253,395],{"class":508},[498,2255,887],{"class":529},[498,2257,395],{"class":508},[498,2259,2260],{"class":529},"\"--server=swoole\"",[498,2262,395],{"class":508},[498,2264,2265],{"class":529},"\"--host=0.0.0.0\"",[498,2267,395],{"class":508},[498,2269,2270],{"class":529},"\"--port=8000\"",[498,2272,905],{"class":508},[498,2274,2276,2278],{"class":500,"line":2275},11,[498,2277,1091],{"class":504},[498,2279,509],{"class":508},[498,2281,2283,2285,2287,2289,2291,2293],{"class":500,"line":2282},12,[498,2284,1098],{"class":504},[498,2286,874],{"class":508},[498,2288,1103],{"class":529},[498,2290,395],{"class":508},[498,2292,1108],{"class":529},[498,2294,905],{"class":508},[498,2296,2298,2300,2302],{"class":500,"line":2297},13,[498,2299,1115],{"class":504},[498,2301,526],{"class":508},[498,2303,1120],{"class":529},[383,2305,2306],{},[238,2307,2308,2311,2312,2315],{},[305,2309,2310],{},"--host=0.0.0.0"," is not optional in a container. Octane binds to ",[305,2313,2314],{},"127.0.0.1"," by default, which is only reachable from inside the container, so Docker's published port answers nothing without it.",[238,2317,2318,2319,2322,2323,2326,2327,2329],{},"Port ",[305,2320,2321],{},"8000"," is Octane's default and is above ",[305,2324,2325],{},"1024",", so the unprivileged ",[305,2328,1777],{}," user can bind it without any changes.",[481,2331,2333],{"id":2332},"running-octane-on-roadrunner","Running Octane on RoadRunner",[238,2335,2336,2337,2340,2341,2343],{},"RoadRunner needs the ",[305,2338,2339],{},"sockets"," extension, which is not in the ",[305,2342,329],{}," image, plus the RoadRunner binary itself:",[489,2345,2347],{"className":2081,"code":2346,"filename":2078,"language":2083,"meta":10,"style":10},"FROM serversideup/php:8.5-cli\n\nUSER root\nRUN install-php-extensions sockets\n\n# Install the RoadRunner binary. TARGETARCH is provided by BuildKit,\n# so this works on both amd64 and arm64.\nARG TARGETARCH\nARG ROADRUNNER_VERSION=2025.1.15\nRUN docker-php-serversideup-download \\\n      \"https://github.com/roadrunner-server/roadrunner/releases/download/v${ROADRUNNER_VERSION}/roadrunner-${ROADRUNNER_VERSION}-linux-${TARGETARCH}.tar.gz\" \\\n      /tmp/roadrunner.tar.gz && \\\n    tar -xzf /tmp/roadrunner.tar.gz -C /tmp && \\\n    mv \"/tmp/roadrunner-${ROADRUNNER_VERSION}-linux-${TARGETARCH}/rr\" /usr/local/bin/rr && \\\n    chmod +x /usr/local/bin/rr && \\\n    rm -rf /tmp/roadrunner*\n\nUSER www-data\n",[305,2348,2349,2355,2359,2365,2372,2376,2381,2386,2394,2401,2408,2416,2421,2426,2438,2444,2450,2455],{"__ignoreMap":10},[498,2350,2351,2353],{"class":500,"line":501},[498,2352,2090],{"class":665},[498,2354,2093],{"class":508},[498,2356,2357],{"class":500,"line":512},[498,2358,675],{"emptyLinePlaceholder":674},[498,2360,2361,2363],{"class":500,"line":520},[498,2362,2102],{"class":665},[498,2364,2105],{"class":508},[498,2366,2367,2369],{"class":500,"line":533},[498,2368,2110],{"class":665},[498,2370,2371],{"class":508}," install-php-extensions sockets\n",[498,2373,2374],{"class":500,"line":541},[498,2375,675],{"emptyLinePlaceholder":674},[498,2377,2378],{"class":500,"line":550},[498,2379,2380],{"class":680},"# Install the RoadRunner binary. TARGETARCH is provided by BuildKit,\n",[498,2382,2383],{"class":500,"line":558},[498,2384,2385],{"class":680},"# so this works on both amd64 and arm64.\n",[498,2387,2388,2391],{"class":500,"line":811},[498,2389,2390],{"class":665},"ARG",[498,2392,2393],{"class":508}," TARGETARCH\n",[498,2395,2396,2398],{"class":500,"line":812},[498,2397,2390],{"class":665},[498,2399,2400],{"class":508}," ROADRUNNER_VERSION=2025.1.15\n",[498,2402,2403,2405],{"class":500,"line":2177},[498,2404,2110],{"class":665},[498,2406,2407],{"class":508}," docker-php-serversideup-download \\\n",[498,2409,2410,2413],{"class":500,"line":2275},[498,2411,2412],{"class":529},"      \"https://github.com/roadrunner-server/roadrunner/releases/download/v${ROADRUNNER_VERSION}/roadrunner-${ROADRUNNER_VERSION}-linux-${TARGETARCH}.tar.gz\"",[498,2414,2415],{"class":508}," \\\n",[498,2417,2418],{"class":500,"line":2282},[498,2419,2420],{"class":508},"      /tmp/roadrunner.tar.gz && \\\n",[498,2422,2423],{"class":500,"line":2297},[498,2424,2425],{"class":508},"    tar -xzf /tmp/roadrunner.tar.gz -C /tmp && \\\n",[498,2427,2429,2432,2435],{"class":500,"line":2428},14,[498,2430,2431],{"class":508},"    mv ",[498,2433,2434],{"class":529},"\"/tmp/roadrunner-${ROADRUNNER_VERSION}-linux-${TARGETARCH}/rr\"",[498,2436,2437],{"class":508}," /usr/local/bin/rr && \\\n",[498,2439,2441],{"class":500,"line":2440},15,[498,2442,2443],{"class":508},"    chmod +x /usr/local/bin/rr && \\\n",[498,2445,2447],{"class":500,"line":2446},16,[498,2448,2449],{"class":508},"    rm -rf /tmp/roadrunner*\n",[498,2451,2453],{"class":500,"line":2452},17,[498,2454,675],{"emptyLinePlaceholder":674},[498,2456,2458,2460],{"class":500,"line":2457},18,[498,2459,2102],{"class":665},[498,2461,2120],{"class":508},[383,2463,2464,2485],{},[238,2465,2466,2467,2469,2470,2473,2474,2477,2478,2481,2482,411],{},"Laravel's documentation tells you to install ",[305,2468,371],{}," and run ",[305,2471,2472],{},"./vendor/bin/rr get-binary",". That package requires ",[305,2475,2476],{},"symfony/console"," version 7 or below, and Laravel 13 ships version 8, so Composer cannot install it. ",[305,2479,2480],{},"php artisan octane:install --server=roadrunner"," also fails, because it shells out to ",[305,2483,2484],{},"./vendor/bin/rr",[238,2486,2487,2488,2490,2491,644,2494,2496],{},"Install the binary yourself as shown above, and skip ",[305,2489,782],{}," for RoadRunner. Set ",[305,2492,2493],{},"OCTANE_SERVER=roadrunner",[305,2495,647],{}," file instead.",[238,2498,2499],{},"Then add the PHP side and start the server:",[489,2501,2503],{"className":582,"code":2502,"filename":584,"language":585,"meta":10,"style":10},"docker compose run php composer require laravel/octane spiral/roadrunner-http\n",[305,2504,2505],{"__ignoreMap":10},[498,2506,2507,2509,2511,2513,2515,2517,2519,2522],{"class":500,"line":501},[498,2508,593],{"class":592},[498,2510,596],{"class":529},[498,2512,599],{"class":529},[498,2514,602],{"class":529},[498,2516,605],{"class":529},[498,2518,608],{"class":529},[498,2520,2521],{"class":529}," laravel/octane",[498,2523,2524],{"class":529}," spiral/roadrunner-http\n",[489,2526,2529],{"className":491,"code":2527,"filename":493,"highlights":2528,"language":494,"meta":10,"style":10},"services:\n  php:\n    build:\n      context: .\n    ports:\n      - \"80:8000\"\n    volumes:\n      - .:/var/www/html/\n    # --rr-config points RoadRunner at the hardened config below\n    command: [\"php\", \"artisan\", \"octane:start\", \"--server=roadrunner\", \"--host=0.0.0.0\", \"--port=8000\", \"--rpc-port=6001\", \"--rr-config=/var/www/html/.rr.yaml\"]\n    healthcheck:\n      test: [\"CMD\", \"healthcheck-octane\"]\n      start_period: 10s\n",[812,2177],[305,2530,2531,2537,2543,2549,2557,2563,2569,2575,2581,2587,2629,2635,2649],{"__ignoreMap":10},[498,2532,2533,2535],{"class":500,"line":501},[498,2534,505],{"class":504},[498,2536,509],{"class":508},[498,2538,2539,2541],{"class":500,"line":512},[498,2540,515],{"class":504},[498,2542,509],{"class":508},[498,2544,2545,2547],{"class":500,"line":520},[498,2546,2196],{"class":504},[498,2548,509],{"class":508},[498,2550,2551,2553,2555],{"class":500,"line":533},[498,2552,2203],{"class":504},[498,2554,526],{"class":508},[498,2556,2208],{"class":632},[498,2558,2559,2561],{"class":500,"line":541},[498,2560,536],{"class":504},[498,2562,509],{"class":508},[498,2564,2565,2567],{"class":500,"line":550},[498,2566,544],{"class":508},[498,2568,2221],{"class":529},[498,2570,2571,2573],{"class":500,"line":558},[498,2572,553],{"class":504},[498,2574,509],{"class":508},[498,2576,2577,2579],{"class":500,"line":811},[498,2578,544],{"class":508},[498,2580,563],{"class":529},[498,2582,2584],{"class":2583,"line":812},[500,862],[498,2585,2586],{"class":680},"    # --rr-config points RoadRunner at the hardened config below\n",[498,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606,2609,2611,2613,2615,2617,2619,2622,2624,2627],{"class":2589,"line":2177},[500,862],[498,2591,871],{"class":504},[498,2593,874],{"class":508},[498,2595,877],{"class":529},[498,2597,395],{"class":508},[498,2599,882],{"class":529},[498,2601,395],{"class":508},[498,2603,887],{"class":529},[498,2605,395],{"class":508},[498,2607,2608],{"class":529},"\"--server=roadrunner\"",[498,2610,395],{"class":508},[498,2612,2265],{"class":529},[498,2614,395],{"class":508},[498,2616,2270],{"class":529},[498,2618,395],{"class":508},[498,2620,2621],{"class":529},"\"--rpc-port=6001\"",[498,2623,395],{"class":508},[498,2625,2626],{"class":529},"\"--rr-config=/var/www/html/.rr.yaml\"",[498,2628,905],{"class":508},[498,2630,2631,2633],{"class":500,"line":2275},[498,2632,1091],{"class":504},[498,2634,509],{"class":508},[498,2636,2637,2639,2641,2643,2645,2647],{"class":500,"line":2282},[498,2638,1098],{"class":504},[498,2640,874],{"class":508},[498,2642,1103],{"class":529},[498,2644,395],{"class":508},[498,2646,1108],{"class":529},[498,2648,905],{"class":508},[498,2650,2651,2653,2655],{"class":500,"line":2297},[498,2652,1115],{"class":504},[498,2654,526],{"class":508},[498,2656,1120],{"class":529},[2658,2659,2661],"h4",{"id":2660},"locking-down-roadrunners-static-files","Locking Down RoadRunner's Static Files",[238,2663,2664,2665,2667,2668,2670,2671,2673,2674,2676,2677,579],{},"Octane starts RoadRunner with ",[305,2666,2027],{}," pointed at your public directory and no forbidden extensions. RoadRunner will happily return your ",[305,2669,647],{}," file, and it serves ",[305,2672,2014],{}," files as plain text rather than refusing them. Create a ",[305,2675,377],{}," in your project root and pass it with ",[305,2678,2679],{},"--rr-config",[489,2681,2683],{"className":491,"code":2682,"filename":377,"language":494,"meta":10,"style":10},"version: \"3\"\n\nhttp:\n  static:\n    # RoadRunner serves everything in the public directory by default,\n    # including .php files as plain text. Refuse these outright.\n    forbid: [\".php\", \".env\", \".bak\", \".log\", \".sql\", \".ini\", \".git\"]\n",[305,2684,2685,2695,2699,2706,2713,2718,2723],{"__ignoreMap":10},[498,2686,2687,2690,2692],{"class":500,"line":501},[498,2688,2689],{"class":504},"version",[498,2691,526],{"class":508},[498,2693,2694],{"class":529},"\"3\"\n",[498,2696,2697],{"class":500,"line":512},[498,2698,675],{"emptyLinePlaceholder":674},[498,2700,2701,2704],{"class":500,"line":520},[498,2702,2703],{"class":504},"http",[498,2705,509],{"class":508},[498,2707,2708,2711],{"class":500,"line":533},[498,2709,2710],{"class":504},"  static",[498,2712,509],{"class":508},[498,2714,2715],{"class":500,"line":541},[498,2716,2717],{"class":680},"    # RoadRunner serves everything in the public directory by default,\n",[498,2719,2720],{"class":500,"line":550},[498,2721,2722],{"class":680},"    # including .php files as plain text. Refuse these outright.\n",[498,2724,2725,2728,2730,2733,2735,2738,2740,2743,2745,2748,2750,2753,2755,2758,2760,2763],{"class":500,"line":558},[498,2726,2727],{"class":504},"    forbid",[498,2729,874],{"class":508},[498,2731,2732],{"class":529},"\".php\"",[498,2734,395],{"class":508},[498,2736,2737],{"class":529},"\".env\"",[498,2739,395],{"class":508},[498,2741,2742],{"class":529},"\".bak\"",[498,2744,395],{"class":508},[498,2746,2747],{"class":529},"\".log\"",[498,2749,395],{"class":508},[498,2751,2752],{"class":529},"\".sql\"",[498,2754,395],{"class":508},[498,2756,2757],{"class":529},"\".ini\"",[498,2759,395],{"class":508},[498,2761,2762],{"class":529},"\".git\"",[498,2764,905],{"class":508},[238,2766,2767,2768,395,2771,1441,2774,2777,2778,2780],{},"With this file in place, ",[305,2769,2770],{},"/.env",[305,2772,2773],{},"/uploaded.php",[305,2775,2776],{},"/config.bak"," all return ",[305,2779,2018],{},", while your real assets keep working. This is the RoadRunner equivalent of the rules our Caddyfile applies for you, and it is still narrower than what a reverse proxy gives you.",[481,2782,2784],{"id":2783},"serving-static-assets-and-tls","Serving Static Assets and TLS",[238,2786,2787,2788,2790],{},"Laravel recommends running Octane behind NGINX or Apache in production so the web server can serve your static assets and terminate TLS. That reverse proxy is also where the protections from ",[302,2789,1785],{"href":1784}," belong.",[489,2792,2794],{"className":491,"code":2793,"filename":493,"language":494,"meta":10,"style":10},"services:\n  php:\n    build:\n      context: .\n    volumes:\n      - .:/var/www/html/\n    # --host=0.0.0.0 is required so the port is reachable outside the container\n    command: [\"php\", \"artisan\", \"octane:start\", \"--server=swoole\", \"--host=0.0.0.0\", \"--port=8000\"]\n\n  web:\n    image: nginx:1.29-alpine\n    ports:\n      - \"80:80\"\n    volumes:\n      - .:/var/www/html/:ro\n      - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro\n    depends_on:\n      - php\n",[305,2795,2796,2802,2808,2814,2822,2828,2834,2838,2868,2872,2879,2888,2894,2901,2907,2914,2921,2928],{"__ignoreMap":10},[498,2797,2798,2800],{"class":500,"line":501},[498,2799,505],{"class":504},[498,2801,509],{"class":508},[498,2803,2804,2806],{"class":500,"line":512},[498,2805,515],{"class":504},[498,2807,509],{"class":508},[498,2809,2810,2812],{"class":500,"line":520},[498,2811,2196],{"class":504},[498,2813,509],{"class":508},[498,2815,2816,2818,2820],{"class":500,"line":533},[498,2817,2203],{"class":504},[498,2819,526],{"class":508},[498,2821,2208],{"class":632},[498,2823,2824,2826],{"class":500,"line":541},[498,2825,553],{"class":504},[498,2827,509],{"class":508},[498,2829,2830,2832],{"class":500,"line":550},[498,2831,544],{"class":508},[498,2833,563],{"class":529},[498,2835,2836],{"class":500,"line":558},[498,2837,2238],{"class":680},[498,2839,2840,2842,2844,2846,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866],{"class":500,"line":811},[498,2841,871],{"class":504},[498,2843,874],{"class":508},[498,2845,877],{"class":529},[498,2847,395],{"class":508},[498,2849,882],{"class":529},[498,2851,395],{"class":508},[498,2853,887],{"class":529},[498,2855,395],{"class":508},[498,2857,2260],{"class":529},[498,2859,395],{"class":508},[498,2861,2265],{"class":529},[498,2863,395],{"class":508},[498,2865,2270],{"class":529},[498,2867,905],{"class":508},[498,2869,2870],{"class":500,"line":812},[498,2871,675],{"emptyLinePlaceholder":674},[498,2873,2874,2877],{"class":500,"line":2177},[498,2875,2876],{"class":504},"  web",[498,2878,509],{"class":508},[498,2880,2881,2883,2885],{"class":500,"line":2275},[498,2882,523],{"class":504},[498,2884,526],{"class":508},[498,2886,2887],{"class":529},"nginx:1.29-alpine\n",[498,2889,2890,2892],{"class":500,"line":2282},[498,2891,536],{"class":504},[498,2893,509],{"class":508},[498,2895,2896,2898],{"class":500,"line":2297},[498,2897,544],{"class":508},[498,2899,2900],{"class":529},"\"80:80\"\n",[498,2902,2903,2905],{"class":500,"line":2428},[498,2904,553],{"class":504},[498,2906,509],{"class":508},[498,2908,2909,2911],{"class":500,"line":2440},[498,2910,544],{"class":508},[498,2912,2913],{"class":529},".:/var/www/html/:ro\n",[498,2915,2916,2918],{"class":500,"line":2446},[498,2917,544],{"class":508},[498,2919,2920],{"class":529},"./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro\n",[498,2922,2923,2926],{"class":500,"line":2452},[498,2924,2925],{"class":504},"    depends_on",[498,2927,509],{"class":508},[498,2929,2930,2932],{"class":500,"line":2457},[498,2931,544],{"class":508},[498,2933,669],{"class":529},[238,2935,2936,2937,2940,2941,979,2944,2947],{},"Laravel publishes a reference NGINX configuration for this setup. Point ",[305,2938,2939],{},"proxy_pass"," at ",[305,2942,2943],{},"http://php:8000",[305,2945,2946],{},"http://127.0.0.1:8000",", since Octane runs in a separate container here.",[2949,2950],"u-button",{"ariaLabel":2951,"className":2952,"color":2960,"label":2961,"size":2962,"target":429,"to":2963,"trailing-icon":2964,"variant":2965},"Laravel NGINX configuration for Octane",[2953,2954,2955,2956,2957,2958,2959],"font-bold","ring","ring-inset","ring-blue-600","text-blue-600","hover:ring-blue-500","hover:text-blue-500","primary","View Laravel's NGINX configuration","md","https://laravel.com/docs/13.x/octane#serving-your-application-via-nginx","i-lucide-arrow-right","outline",[383,2967,2968],{},[238,2969,2970,2971,2973,2974,2977],{},"Do not reach for ",[305,2972,398],{}," as the proxy image. The NGINX in that variation is configured to pass requests to PHP-FPM over FastCGI, not to proxy them to an Octane server. Use a plain ",[305,2975,2976],{},"nginx"," image with your own configuration.",[481,2979,2981],{"id":2980},"checking-the-server-status","Checking the Server Status",[238,2983,2984,2985,2987,2988,2990],{},"There is no ",[305,2986,1048],{}," endpoint here, because that endpoint lives in our Caddyfile. Your options are to define a health check route in your Laravel application, or to use the ",[305,2989,1055],{}," command that ships in every one of our images:",[489,2992,2993],{"className":491,"code":1067,"filename":493,"language":494,"meta":10,"style":10},[305,2994,2995,3001,3007,3011,3017,3031],{"__ignoreMap":10},[498,2996,2997,2999],{"class":500,"line":501},[498,2998,505],{"class":504},[498,3000,509],{"class":508},[498,3002,3003,3005],{"class":500,"line":512},[498,3004,515],{"class":504},[498,3006,509],{"class":508},[498,3008,3009],{"class":500,"line":520},[498,3010,1086],{"class":680},[498,3012,3013,3015],{"class":500,"line":533},[498,3014,1091],{"class":504},[498,3016,509],{"class":508},[498,3018,3019,3021,3023,3025,3027,3029],{"class":500,"line":541},[498,3020,1098],{"class":504},[498,3022,874],{"class":508},[498,3024,1103],{"class":529},[498,3026,395],{"class":508},[498,3028,1108],{"class":529},[498,3030,905],{"class":508},[498,3032,3033,3035,3037],{"class":500,"line":550},[498,3034,1115],{"class":504},[498,3036,526],{"class":508},[498,3038,1120],{"class":529},[238,3040,3041,3042,3047],{},"It runs ",[302,3043,3045],{"href":1059,"rel":3044,"target":429},[428],[305,3046,1063],{},", which inspects the server Octane started, whichever server that is.",[481,3049,3051],{"id":3050},"opcache-and-php-settings","OPcache and PHP Settings",[238,3053,3054,3055,3057,3058,3061,3062,411],{},"All of the ",[305,3056,1286],{}," environment variables work the same way they do in every other variation. One detail is different: Octane on Swoole or RoadRunner is a CLI process, so OPcache follows ",[305,3059,3060],{},"PHP_OPCACHE_ENABLE_CLI"," rather than ",[305,3063,1293],{},[413,3065,3066],{},[238,3067,3068,3070,3071,3074,3075,3077,3078,3081],{},[305,3069,3060],{}," already defaults to ",[305,3072,3073],{},"1"," in the ",[305,3076,329],{}," image, so OPcache is on. See ",[302,3079,3080],{"href":184},"Production Performance Tuning"," for the rest of the settings worth reviewing.",[481,3083,3085],{"id":3084},"watching-for-file-changes","Watching for File Changes",[238,3087,1540,3088,3090,3091,987,3096,3101],{},[305,3089,1407],{}," flag needs ",[302,3092,3095],{"href":3093,"rel":3094,"target":429},"https://nodejs.org",[428],"Node",[302,3097,3100],{"href":3098,"rel":3099,"target":429},"https://github.com/paulmillr/chokidar",[428],"Chokidar"," when you are not using FrankenPHP:",[489,3103,3105],{"className":582,"code":3104,"filename":584,"language":585,"meta":10,"style":10},"npm install --save-dev chokidar\n",[305,3106,3107],{"__ignoreMap":10},[498,3108,3109,3112,3115,3118],{"class":500,"line":501},[498,3110,3111],{"class":592},"npm",[498,3113,3114],{"class":529}," install",[498,3116,3117],{"class":632}," --save-dev",[498,3119,3120],{"class":529}," chokidar\n",[238,3122,3123,3124,3126,3127,3129],{},"Neither Node nor Chokidar ships in the ",[305,3125,329],{}," image, so add them to your development image if you want this. FrankenPHP's built-in file watcher is specific to the ",[305,3128,307],{}," variation.",[481,3131,3133],{"id":3132},"swoole-only-octane-features","Swoole-Only Octane Features",[238,3135,3136],{},"These are the features that make Swoole worth the extra work. None of them are available on FrankenPHP or RoadRunner:",[419,3138,3139,3156,3164,3172],{},[422,3140,3141,3146,3147,3149,3150,3153,3154,411],{},[252,3142,3143],{},[302,3144,430],{"href":426,"rel":3145,"target":429},[428]," run operations in parallel with ",[305,3148,434],{},", using Swoole's task workers. Set how many with ",[305,3151,3152],{},"--task-workers"," on ",[305,3155,794],{},[422,3157,3158,3163],{},[252,3159,3160],{},[302,3161,441],{"href":439,"rel":3162,"target":429},[428]," invoke a callback every N seconds without a cron entry.",[422,3165,3166,3171],{},[252,3167,3168],{},[302,3169,449],{"href":447,"rel":3170,"target":429},[428]," stores data in shared memory at extremely high throughput. The data is flushed whenever the server restarts, so treat it as a cache and nothing more.",[422,3173,3174,3179],{},[252,3175,3176],{},[302,3177,457],{"href":455,"rel":3178,"target":429},[428]," share arbitrary structured data between all workers, with the same restart caveat.",[242,3181,3183],{"id":3182},"things-to-watch-out-for","Things to Watch Out For",[238,3185,3186],{},"Since Octane is a whole different way of running Laravel compared to traditional PHP-FPM, there are a few things to watch out for.",[481,3188,3190],{"id":3189},"dependency-injection","Dependency Injection",[238,3192,3193,3194,3199],{},"Be careful with how you inject dependencies into long-lived objects. Injecting the wrong things into constructors can cause requests to \"leak\" between users. Review Laravel's ",[302,3195,3198],{"href":3196,"rel":3197,"target":429},"https://laravel.com/docs/13.x/octane#dependency-injection-and-octane",[428],"Dependency Injection and Octane"," documentation for details.",[481,3201,3203],{"id":3202},"memory-leaks","Memory Leaks",[238,3205,3206,3207,3212],{},"Review Laravel's ",[302,3208,3211],{"href":3209,"rel":3210,"target":429},"https://laravel.com/docs/13.x/octane#managing-memory-leaks",[428],"Octane documentation on memory leaks"," to understand what to avoid.",[481,3214,3216],{"id":3215},"reloading-after-deployments","Reloading After Deployments",[238,3218,3219,3220,3227,3228,3230],{},"Octane keeps your code in memory, so a deployment needs ",[302,3221,3224],{"href":3222,"rel":3223,"target":429},"https://laravel.com/docs/13.x/octane#reloading-the-workers",[428],[305,3225,3226],{},"php artisan octane:reload"," to pick up new code. In a container this usually means replacing the container instead. If you do reload in place, our Caddyfile exposes the admin API Octane needs, so ",[305,3229,990],{}," works as expected.",[242,3232,3234],{"id":3233},"learn-more","Learn More",[419,3236,3237,3242,3249],{},[422,3238,3239],{},[302,3240,3241],{"href":79},"FrankenPHP Variation Documentation",[422,3243,3244],{},[302,3245,3248],{"href":3246,"rel":3247},"https://laravel.com/docs/13.x/octane",[428],"Laravel Octane Documentation",[422,3250,3251],{},[302,3252,3255],{"href":3253,"rel":3254},"https://frankenphp.dev/docs/",[428],"FrankenPHP Documentation",[3257,3258,3259],"style",{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":10,"searchDepth":512,"depth":512,"links":3261},[3262,3263,3264,3270,3271,3272,3273,3274,3275,3285,3290],{"id":244,"depth":512,"text":245},{"id":264,"depth":512,"text":265},{"id":464,"depth":512,"text":465,"children":3265},[3266,3267,3268,3269],{"id":483,"depth":520,"text":484},{"id":569,"depth":520,"text":570},{"id":802,"depth":520,"text":803},{"id":1013,"depth":520,"text":1014},{"id":1041,"depth":512,"text":1042},{"id":1123,"depth":512,"text":1124},{"id":1271,"depth":512,"text":1272},{"id":1314,"depth":512,"text":1315},{"id":1573,"depth":512,"text":1585},{"id":1754,"depth":512,"text":410,"children":3276},[3277,3278,3279,3280,3281,3282,3283,3284],{"id":1789,"depth":520,"text":1785},{"id":2071,"depth":520,"text":2072},{"id":2332,"depth":520,"text":2333},{"id":2783,"depth":520,"text":2784},{"id":2980,"depth":520,"text":2981},{"id":3050,"depth":520,"text":3051},{"id":3084,"depth":520,"text":3085},{"id":3132,"depth":520,"text":3133},{"id":3182,"depth":512,"text":3183,"children":3286},[3287,3288,3289],{"id":3189,"depth":520,"text":3190},{"id":3202,"depth":520,"text":3203},{"id":3215,"depth":520,"text":3216},{"id":3233,"depth":512,"text":3234},"Learn how to configure Laravel Octane with Docker.",null,{"head":3294,"layout":7},{"title":3295},"Laravel Octane with Docker - Docker PHP - Server Side Up",{"title":120,"description":3291},"rybGyRYB7QJyrZyj44EuFNFlNvac0r8FAoIiA2LSb9A",[3299,3301],{"title":116,"path":117,"stem":118,"description":3300,"children":-1},"Learn how to configure Laravel Nightwatch with Docker.",{"title":129,"path":130,"stem":131,"description":3302,"children":-1},"Learn how serversideup/php is optimized for running WordPress in production.",1793279102002]