Laravel Octane

번역일: 2026년 6월 25일

Laravel Octane

소개

Laravel OctaneFrankenPHP, Open Swoole, Swoole, RoadRunner 같은 고성능 애플리케이션 서버를 활용해 Laravel 애플리케이션의 성능을 획기적으로 끌어올립니다. Octane은 애플리케이션을 최초 한 번만 부팅한 뒤 메모리에 상주시키고, 이후 요청을 매우 빠른 속도로 처리합니다.

일반적인 PHP-FPM 방식은 요청마다 프레임워크를 새로 초기화하지만, Octane은 서버 프로세스가 살아 있는 동안 애플리케이션 인스턴스를 재사용합니다. 이 차이가 큰 성능 향상의 핵심입니다.

설치

Composer로 Octane을 설치합니다:

composer require laravel/octane

설치 후 octane:install Artisan 명령어를 실행하면 Octane 설정 파일이 애플리케이션에 추가됩니다:

php artisan octane:install

서버 사전 요구사항

FrankenPHP

FrankenPHP는 Go로 작성된 PHP 애플리케이션 서버로, early hints, Brotli 압축, Zstandard 압축 같은 최신 웹 기능을 지원합니다. Octane 설치 시 서버로 FrankenPHP를 선택하면 Octane이 FrankenPHP 바이너리를 자동으로 다운로드하고 설치합니다.

Laravel Sail에서 FrankenPHP 사용

Laravel Sail로 개발 환경을 구성하는 경우 아래 명령어로 Octane과 FrankenPHP를 설치하세요:

./vendor/bin/sail up./vendor/bin/sail composer require laravel/octane

그 다음, octane:install 명령어로 FrankenPHP 바이너리를 설치합니다:

./vendor/bin/sail artisan octane:install --server=frankenphp

이후 docker-compose.yml 파일의 laravel.test 서비스 정의에 SUPERVISOR_PHP_COMMAND 환경 변수를 추가합니다. 이 변수에 지정된 명령어로 Sail이 기존 PHP 개발 서버 대신 Octane을 사용해 애플리케이션을 서빙합니다:

services:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'" #
XDG_CONFIG_HOME: /var/www/html/config #
XDG_DATA_HOME: /var/www/html/data #

HTTPS, HTTP/2, HTTP/3를 활성화하려면 다음과 같이 설정합니다:

services:
laravel.test:
ports:
- '${APP_PORT:-80}:80'
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
- '443:443' #
- '443:443/udp' #
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --host=localhost --port=443 --admin-port=2019 --https" #
XDG_CONFIG_HOME: /var/www/html/config #
XDG_DATA_HOME: /var/www/html/data #

FrankenPHP Sail 애플리케이션은 https://localhost로 접속하는 것을 권장합니다. https://127.0.0.1을 사용하면 추가 설정이 필요하며 권장되지 않습니다.

FrankenPHP를 Docker로 사용

FrankenPHP 공식 Docker 이미지를 사용하면 정적 설치 방식보다 성능이 향상되고 추가 PHP 확장도 활용할 수 있습니다. Windows처럼 FrankenPHP가 기본 지원하지 않는 플랫폼에서도 사용할 수 있으며, 로컬 개발과 프로덕션 모두에 적합합니다.

다음 Dockerfile을 FrankenPHP 기반 Laravel 애플리케이션의 시작점으로 활용하세요:

FROM dunglas/frankenphp RUN install-php-extensions \ pcntl # 필요한 PHP 확장을 여기에 추가하세요... COPY . /app ENTRYPOINT ["php", "artisan", "octane:frankenphp"]

개발 시에는 아래 Docker Compose 파일을 사용할 수 있습니다:

# compose.yaml services: frankenphp: build: context: . entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1 ports: - "8000:8000" volumes: - .:/app

php artisan octane:start 명령어에 --log-level 옵션을 명시적으로 전달하면, Octane이 FrankenPHP의 네이티브 로거를 사용하며 별도 설정이 없는 경우 JSON 구조화 로그를 출력합니다.

Docker에서 FrankenPHP를 실행하는 자세한 방법은 공식 FrankenPHP 문서를 참고하세요.

커스텀 Caddyfile 설정

FrankenPHP를 사용할 때 --caddyfile 옵션으로 커스텀 Caddyfile 경로를 지정할 수 있습니다:

php artisan octane:start --server=frankenphp --caddyfile=/path/to/your/Caddyfile

이를 통해 커스텀 미들웨어 추가, 고급 라우팅 설정, 커스텀 디렉티브 등 기본 설정을 넘어서는 FrankenPHP 구성이 가능합니다. Caddyfile 문법과 설정 옵션에 대한 자세한 내용은 공식 Caddy 문서를 참고하세요.

RoadRunner

RoadRunner는 Go로 빌드된 RoadRunner 바이너리로 구동됩니다. RoadRunner 기반 Octane 서버를 처음 시작하면 Octane이 RoadRunner 바이너리 다운로드 및 설치를 안내합니다.

Laravel Sail에서 RoadRunner 사용

Laravel Sail로 개발하는 경우 아래 명령어로 Octane과 RoadRunner를 설치하세요:

./vendor/bin/sail up./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http

이후 Sail 쉘에 접속해 rr 실행 파일로 최신 Linux용 RoadRunner 바이너리를 받습니다:

./vendor/bin/sail shell# Sail 쉘 내부에서..../vendor/bin/rr get-binary

그 다음, docker-compose.yml 파일의 laravel.test 서비스 정의에 SUPERVISOR_PHP_COMMAND 환경 변수를 추가합니다:

services:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port='${APP_PORT:-80}'" #

마지막으로 rr 바이너리에 실행 권한을 부여하고 Sail 이미지를 빌드합니다:

chmod +x ./rr./vendor/bin/sail build --no-cache

Swoole

Swoole 애플리케이션 서버를 사용하려면 Swoole PHP 확장을 설치해야 합니다. PECL로 설치하는 것이 일반적입니다:

pecl install swoole

Open Swoole

Open Swoole을 사용하려면 Open Swoole PHP 확장을 설치합니다:

pecl install openswoole

Open Swoole은 Swoole과 동일하게 동시 작업, 틱, 인터벌 기능을 제공합니다.

Laravel Sail에서 Swoole 사용

WARNING

Sail로 Octane 애플리케이션을 서빙하기 전에 Laravel Sail이 최신 버전인지 확인하고, 애플리케이션 루트 디렉터리에서 ./vendor/bin/sail build --no-cache를 실행하세요.

Laravel Sail은 기본적으로 Swoole 확장을 포함하고 있습니다. 단, docker-compose.yml 파일을 아래와 같이 수정해야 합니다.

laravel.test 서비스 정의에 SUPERVISOR_PHP_COMMAND 환경 변수를 추가합니다:

services:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port='${APP_PORT:-80}'" #

이후 Sail 이미지를 빌드합니다:

./vendor/bin/sail build --no-cache

Swoole 설정

필요에 따라 octane 설정 파일에 Swoole 전용 옵션을 추가할 수 있습니다. 아래 옵션들은 수정이 필요한 경우가 드물어 기본 설정 파일에는 포함되어 있지 않습니다:

'swoole' => [ 'options' => [ 'log_file' => storage_path('logs/swoole_http.log'), 'package_max_length' => 10 * 1024 * 1024, ], ],

애플리케이션 서빙

octane:start Artisan 명령어로 Octane 서버를 시작합니다. 기본적으로 octane 설정 파일의 server 옵션에 지정된 서버를 사용합니다:

php artisan octane:start

기본 포트는 8000이며, 브라우저에서 http://localhost:8000으로 접속할 수 있습니다.

프로덕션에서 Octane 유지하기

프로덕션 환경에서는 Supervisor 같은 프로세스 모니터를 사용해 Octane 서버가 항상 실행되도록 관리해야 합니다. 아래는 Octane용 Supervisor 설정 예시입니다:

[program:octane] process_name=%(program_name)s_%(process_num)02d command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000 autostart=true autorestart=true user=forge redirect_stderr=true stdout_logfile=/home/forge/example.com/storage/logs/octane.log stopwaitsecs=3600

HTTPS로 서빙

기본적으로 Octane으로 실행 중인 애플리케이션은 http://로 시작하는 링크를 생성합니다. config/octane.phpOCTANE_HTTPS 환경 변수를 true로 설정하면 Octane이 Laravel에 모든 링크를 https://로 생성하도록 지시합니다:

'https' => env('OCTANE_HTTPS', false),

Nginx를 통한 서빙

NOTE

서버 설정을 직접 관리하기 어렵거나 견고한 Laravel Octane 애플리케이션 운영에 필요한 각종 서비스 구성이 부담스럽다면, 완전 관리형 Laravel Octane 지원을 제공하는 Laravel Cloud를 검토해 보세요.

프로덕션 환경에서는 Nginx나 Apache 같은 웹 서버 뒤에 Octane을 배치하는 것이 좋습니다. 이렇게 하면 웹 서버가 이미지, CSS 같은 정적 파일을 직접 처리하고 SSL 인증서 종료도 담당합니다.

아래 Nginx 설정 예시는 정적 파일을 직접 서빙하고 나머지 요청을 포트 8000에서 실행 중인 Octane 서버로 프록시합니다:

map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 80; listen [::]:80; server_name domain.com; server_tokens off; root /home/forge/domain.com/public; index index.php; charset utf-8; location /index.php { try_files /not_exists @octane; } location / { try_files $uri $uri/ @octane; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } access_log off; error_log /var/log/nginx/domain.com-error.log error; error_page 404 /index.php; location @octane { set $suffix ""; if ($uri = /index.php) { set $suffix ?$query_string; } proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header Scheme $scheme; proxy_set_header SERVER_PORT $server_port; proxy_set_header REMOTE_ADDR $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_pass http://127.0.0.1:8000$suffix; } }

파일 변경 감지

Octane은 서버 시작 시점에 애플리케이션을 한 번 메모리에 올리기 때문에, 이후 파일을 수정해도 브라우저를 새로 고침해도 변경 내용이 반영되지 않습니다. 예를 들어 routes/web.php에 라우트를 추가해도 서버를 재시작하기 전까지는 적용되지 않습니다. 개발 편의를 위해 --watch 플래그를 사용하면 파일 변경을 감지할 때마다 서버가 자동으로 재시작됩니다:

php artisan octane:start --watch

이 기능을 사용하려면 로컬 개발 환경에 Node가 설치되어 있어야 하며, 파일 감지 라이브러리인 Chokidar도 프로젝트에 설치해야 합니다:

npm install --save-dev chokidar

감시할 디렉터리와 파일은 config/octane.phpwatch 설정 옵션에서 조정할 수 있습니다.

워커 수 지정

기본적으로 Octane은 머신의 CPU 코어 수만큼 요청 처리 워커를 시작합니다. 이 워커들이 들어오는 HTTP 요청을 처리합니다. octane:start 명령어 실행 시 --workers 옵션으로 워커 수를 직접 지정할 수 있습니다:

php artisan octane:start --workers=4

Swoole 서버를 사용하는 경우 동시 작업을 처리할 "태스크 워커" 수도 함께 지정할 수 있습니다:

php artisan octane:start --workers=4 --task-workers=6

최대 요청 수 지정

잠재적인 메모리 누수 방지를 위해 Octane은 워커가 500개의 요청을 처리하면 자동으로 재시작합니다. --max-requests 옵션으로 이 값을 조정할 수 있습니다:

php artisan octane:start --max-requests=250

최대 실행 시간 지정

기본적으로 Laravel Octane은 config/octane.phpmax_execution_time 옵션을 통해 요청당 최대 실행 시간을 30초로 제한합니다:

'max_execution_time' => 30,

이 값은 요청이 종료되기 전까지 허용되는 최대 실행 시간(초)을 정의합니다. 0으로 설정하면 실행 시간 제한이 비활성화됩니다. 파일 업로드, 대용량 데이터 처리, 외부 API 호출처럼 시간이 오래 걸리는 요청을 처리하는 애플리케이션에서 유용한 설정입니다.

WARNING

max_execution_time 설정을 변경한 경우 변경 사항을 적용하려면 Octane 서버를 반드시 재시작해야 합니다.

워커 재시작

octane:reload 명령어로 Octane 서버의 워커를 순차적으로 재시작할 수 있습니다. 새로 배포한 코드를 메모리에 로드하여 이후 요청에 반영하기 위해 배포 후 실행하는 것이 일반적입니다:

php artisan octane:reload

서버 중지

octane:stop Artisan 명령어로 Octane 서버를 중지합니다:

php artisan octane:stop

서버 상태 확인

octane:status Artisan 명령어로 Octane 서버의 현재 상태를 확인할 수 있습니다:

php artisan octane:status

의존성 주입과 Octane

Octane은 애플리케이션을 최초 한 번만 부팅하고 메모리에 유지하면서 요청을 처리하기 때문에 애플리케이션 개발 시 주의해야 할 사항이 있습니다. 예를 들어, 서비스 프로바이더의 registerboot 메서드는 워커가 처음 시작될 때 단 한 번만 실행됩니다. 이후 요청에서는 동일한 애플리케이션 인스턴스가 재사용됩니다.

따라서 애플리케이션 서비스 컨테이너나 Request 인스턴스를 다른 객체의 생성자에 주입할 때는 특히 주의해야 합니다. 이렇게 주입된 객체는 이후 요청에서 오래된(stale) 컨테이너나 Request를 참조할 수 있습니다.

Octane은 요청 간 프레임워크 자체의 상태는 자동으로 초기화하지만, 애플리케이션 코드에서 만든 전역 상태까지는 처리하지 않습니다. 아래에서 Octane 사용 시 흔히 발생하는 문제 상황과 해결 방법을 설명합니다.

컨테이너 주입

일반적으로 애플리케이션 서비스 컨테이너나 HTTP Request 인스턴스를 다른 객체의 생성자에 직접 주입하는 것은 피해야 합니다. 아래 예시는 싱글톤으로 등록된 객체에 전체 서비스 컨테이너를 주입합니다:

use App\Service; use Illuminate\Contracts\Foundation\Application; /** * 애플리케이션 서비스를 등록합니다. */ public function register(): void { $this->app->singleton(Service::class, function (Application $app) { return new Service($app); }); }

이 경우 Service 인스턴스가 부팅 과정 중에 resolve되면 그 시점의 컨테이너가 주입됩니다. 이후 요청에서도 Service 인스턴스는 이 오래된 컨테이너를 계속 참조하게 되며, 이후 부트 사이클이나 다음 요청에서 추가된 바인딩을 찾지 못하는 문제가 생길 수 있습니다.

이를 해결하려면 싱글톤 등록을 중단하거나, 항상 현재 컨테이너 인스턴스를 반환하는 클로저를 주입하세요:

use App\Service; use Illuminate\Container\Container; use Illuminate\Contracts\Foundation\Application; $this->app->bind(Service::class, function (Application $app) { return new Service($app); }); $this->app->singleton(Service::class, function () { return new Service(fn () => Container::getInstance()); });

전역 app() 헬퍼와 Container::getInstance() 메서드는 항상 최신 애플리케이션 컨테이너를 반환합니다.

Request 주입

마찬가지로 HTTP Request 인스턴스를 싱글톤 객체의 생성자에 주입하면 안 됩니다:

use App\Service; use Illuminate\Contracts\Foundation\Application; /** * 애플리케이션 서비스를 등록합니다. */ public function register(): void { $this->app->singleton(Service::class, function (Application $app) { return new Service($app['request']); }); }

이렇게 하면 Service 인스턴스는 부팅 시점의 Request를 계속 참조하게 됩니다. 이후 요청에서는 헤더, 입력값, 쿼리 스트링 등 모든 요청 데이터가 잘못된 값을 가리킵니다.

해결 방법은 싱글톤 등록을 중단하거나, 항상 현재 Request를 반환하는 클로저를 주입하는 것입니다. 또는 가장 권장하는 방법으로, 필요한 요청 데이터만 런타임에 메서드 인자로 전달하세요:

use App\Service; use Illuminate\Contracts\Foundation\Application; $this->app->bind(Service::class, function (Application $app) { return new Service($app['request']); }); $this->app->singleton(Service::class, function (Application $app) { return new Service(fn () => $app['request']); }); // 또는... $service->method($request->input('name'));

전역 request() 헬퍼는 항상 현재 처리 중인 요청을 반환하므로 애플리케이션 어디서나 안전하게 사용할 수 있습니다.

WARNING

컨트롤러 메서드나 라우트 클로저에서 Illuminate\Http\Request를 타입힌트로 사용하는 것은 안전합니다.

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 6월 25일