본문 바로가기

Laravel Octane

번역일: 2026년 6월 21일

Laravel Octane

소개

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

일반적인 PHP-FPM 방식은 요청마다 애플리케이션을 새로 초기화하는 반면, Octane은 애플리케이션을 메모리에 유지하므로 부팅 비용 없이 요청을 처리할 수 있습니다. 이 차이가 Octane의 핵심 성능 이점입니다.

설치

Composer로 Octane을 설치합니다:

composer require laravel/octane

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

php artisan octane:install

서버 사전 요구사항

WARNING

Laravel Octane은 PHP 8.1 이상을 요구합니다.

FrankenPHP

WARNING

FrankenPHP의 Octane 통합은 현재 베타 단계입니다. 프로덕션 환경에서는 주의하여 사용하세요.

FrankenPHP는 Go로 작성된 PHP 애플리케이션 서버로, early hints, 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.ymllaravel.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=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은 추가 설정이 필요하며 권장되지 않습니다.

Docker에서 FrankenPHP 사용

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

FrankenPHP 기반 Laravel 애플리케이션을 컨테이너화하기 위한 Dockerfile 시작점입니다:

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 --max-requests=1 ports: - "8000:8000" volumes: - .:/app

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

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.ymllaravel.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=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 기반 Octane 애플리케이션을 개발할 수 있습니다. Laravel Sail에는 Swoole 확장 모듈이 기본으로 포함되어 있습니다. 다만 docker-compose.yml 파일을 수정해야 합니다.

docker-compose.ymllaravel.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=80" #

마지막으로 Sail 이미지를 빌드합니다:

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

Swoole 설정

Swoole은 필요에 따라 octane 설정 파일에 추가할 수 있는 몇 가지 추가 옵션을 지원합니다. 변경할 일이 거의 없기 때문에 기본 설정 파일에는 포함되어 있지 않습니다:

'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으로 애플리케이션에 접근할 수 있습니다.

HTTPS로 서비스하기

기본적으로 Octane을 통해 실행되는 애플리케이션은 http://로 시작하는 링크를 생성합니다. HTTPS 환경에서 서비스할 때는 config/octane.php에서 사용하는 OCTANE_HTTPS 환경 변수를 true로 설정하세요. 이 값이 true이면 Octane이 Laravel에 모든 생성 링크에 https:// 접두사를 붙이도록 지시합니다:

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

Nginx를 통한 서비스

NOTE

서버 설정을 직접 관리하거나 Laravel Octane 애플리케이션 운영에 필요한 다양한 서비스 구성이 부담스럽다면 Laravel Forge를 살펴보세요.

프로덕션 환경에서는 Nginx나 Apache 같은 일반 웹 서버 뒤에 Octane을 배치하는 것을 권장합니다. 이렇게 하면 웹 서버가 이미지, 스타일시트 등 정적 파일을 직접 제공하고 SSL 인증서 처리도 담당할 수 있습니다.

아래 Nginx 설정 예시에서는 정적 파일은 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 플래그를 사용하면 파일 변경 시 Octane이 서버를 자동으로 재시작합니다:

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

워커 재시작

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) 컨테이너나 요청을 참조하게 될 수 있습니다.

Octane은 요청 간 프레임워크 자체의 상태를 자동으로 초기화하지만, 애플리케이션 코드가 직접 만든 전역 상태까지는 항상 알 수 없습니다. 따라서 아래에서 소개하는 Octane 친화적인 코드 작성 방법을 숙지하는 것이 중요합니다.

컨테이너 주입

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

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 인스턴스가 애플리케이션 부팅 중에 한 번 생성되면, 그 시점의 컨테이너가 주입됩니다. 이후 요청에서 컨테이너에 새로운 바인딩이 추가되어도 해당 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() 메서드는 항상 최신 애플리케이션 컨테이너를 반환하므로 안전하게 사용할 수 있습니다.

요청 주입

마찬가지로 HTTP 요청 인스턴스를 싱글톤 객체의 생성자에 직접 주입하는 것도 피해야 합니다:

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 인스턴스에 부팅 시점의 요청이 고정되므로, 이후 요청의 헤더, 입력값, 쿼리 스트링 등 모든 요청 데이터가 올바르게 전달되지 않습니다.

해결 방법으로는 싱글톤 등록을 일반 바인딩으로 변경하거나, 요청 인스턴스를 반환하는 클로저를 주입하거나, 가장 권장하는 방법으로 필요한 요청 데이터를 런타임에 메서드 인자로 직접 전달하는 것입니다:

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 타입을 타입힌트로 선언하는 것은 완전히 안전합니다.

설정 저장소 주입

마찬가지로 설정 저장소 인스턴스를 싱글톤 객체의 생성자에 직접 주입하는 것은 피해야 합니다:

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

설정 값이 요청 간에 변경되더라도 이 서비스는 최초에 주입된 저장소 인스턴스를 계속 참조하므로 새로운 값을 인식하지 못합니다.

해결 방법은 싱글톤 대신 일반 바인딩을 사용하거나, 설정 저장소를 반환하는 클로저를 주입하는 것입니다:

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

전역 config 헬퍼는 항상 최신 설정 저장소를 반환하므로 안전하게 사용할 수 있습니다.

메모리 누수 관리

Octane은 요청 간에 애플리케이션을 메모리에 유지하므로, 정적 배열에 데이터를 계속 추가하면 메모리 누수가 발생합니다. 예를 들어 아래 컨트롤러는 요청마다 정적 $data 배열에

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

번역일: 2026년 6월 21일