公開日 2026-03-30

Laravelでスケジューラを動かす(Command + Scheduler 最小構成)

Laravel 13 の fresh app で Artisan Command と Scheduler を最小構成でつなぎ、routes/console.php、schedule:list、schedule:run、ログ確認まで手元で再現する。

目次

  1. 前提環境
  2. 1. ゴールと非対象
  3. 2. 新規デモを作って開始位置をそろえる
  4. 3. scheduler の全体像を先に見る
  5. 4. 最小 Command を作る
  6. コードのポイント
  7. 5. routes/console.php に schedule を登録する
  8. コードのポイント
  9. 6. schedule:run を実行してログを確認する
  10. 7. よくある詰まりどころ
  11. schedule:list に出ない
  12. schedule:run は通るのにログが増えない
  13. Command を直したのに挙動が変わらない
  14. production では何を置けばよいか分からない
  15. 8. まとめ

Laravel 13 の scheduler を最小構成で試す手順です。fresh app から make:command で Command を 1 本作り、routes/console.phpSchedule::command(...)->everyMinute() を書いて、schedule:run で実行確認するところまでを扱います。認証、starter kit、Queue、production の cron 詳説はここでは入れません。

scheduler は、Laravel の Command をいつ実行するかをアプリ側で定義する仕組みです。この記事では cron の本番運用まで広げず、schedule:run で 1 本の Command が評価されるところだけを確認します。

前提環境

  • Windows 11
  • WSL2(Ubuntu)
  • VS Code(Remote - WSL)
  • Docker Desktop(WSL 連携有効)
  • Laravel 13
  • composer:2 コンテナ

コマンド実行は、特記がない限り WSL 側ターミナルです。

1. ゴールと非対象

この記事で着地したいのは次の 4 点です。

  • php artisan make:command で最小 Command を作れる
  • routes/console.php に scheduler を登録できる
  • php artisan schedule:list で登録内容を確認できる
  • php artisan schedule:run 実行後にログファイルへ 1 行追記されることを確認できる

今回は scheduler の最初の 1 本だけを扱います。Queue 連携、withoutOverlapping()onOneServer() などの運用オプション、cron の本番設定、認証付き画面からの起動導線は対象外です。

2. 新規デモを作って開始位置をそろえる

Laravel 13 の公式 installation docs では laravel new が基本導線ですが、この記事ではローカルに PHP を直接入れずに試せるよう、composer:2 コンテナでプロジェクトを作ります。

開始位置は次のディレクトリです。

mkdir -p ~/projects/laravel-scheduler-command-demo
cd ~/projects/laravel-scheduler-command-demo
code .
docker run --rm -u "$(id -u):$(id -g)" -v "$(pwd):/app" -w /app composer:2 create-project laravel/laravel .

compose.yml は次の内容で作成します。

services:
  app:
    image: composer:2
    user: "${LOCAL_UID:-1000}:${LOCAL_GID:-1000}"
    working_dir: /app
    volumes:
      - ./:/app
    ports:
      - "8000:8000"
    command: php artisan serve --host=0.0.0.0 --port=8000

同じシェルで UID / GID を合わせて起動します。

export LOCAL_UID="$(id -u)"
export LOCAL_GID="$(id -g)"
docker compose up -d
docker compose ps

ブラウザで http://localhost:8000 を開き、Laravel の welcome 画面が見えれば準備完了です。ポート 8000 が埋まっている場合は、compose.yml の左側を 8001:8000 のように変えてから docker compose up -d をやり直してください。

起動状態は docker compose ps と welcome 画面で確認できます。

docker compose ps で app コンテナが起動している画面 Laravel の welcome 画面

3. scheduler の全体像を先に見る

Laravel 13 の scheduling docs では、定期実行の定義場所として routes/console.php が案内されています。ここに「何を」「どの頻度で実行するか」を置き、実際に評価するのは php artisan schedule:run です。

全体の流れは次の図です。

flowchart LR
    C[cron または schedule:run] --> S[routes/console.php の schedule 定義を評価]
    S --> K[WriteSchedulerHeartbeat Command]
    K --> L[storage/logs/scheduler-demo.log に追記]

確認する観点は次の 3 つです。

  • Command は「実際に何をするか」を持つ
  • Scheduler は「その Command をいつ動かすか」を持つ
  • production では 1 本の cron から php artisan schedule:run を毎分呼ぶ構成が基本になる

この記事では local で 1 回の評価を確かめたいので、cron の代わりに schedule:run を手で実行します。

4. 最小 Command を作る

Laravel アプリのルートで次のコマンドを実行し、Command のひな形を作ります。

docker compose exec app php artisan make:command WriteSchedulerHeartbeat

生成された app/Console/Commands/WriteSchedulerHeartbeat.php を次の内容に更新します。

<?php

namespace App\Console\Commands;

use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

#[Signature('demo:scheduler-heartbeat')]
#[Description('Write one line to the scheduler demo log file')]
class WriteSchedulerHeartbeat extends Command
{
    public function handle(): int
    {
        $logFile = storage_path('logs/scheduler-demo.log');

        File::ensureDirectoryExists(dirname($logFile));
        File::append(
            $logFile,
            sprintf('[%s] scheduler command executed%s', now()->format('Y-m-d H:i:s'), PHP_EOL),
        );

        $this->info('scheduler-demo.log に 1 行追記しました。');

        return self::SUCCESS;
    }
}

Laravel 13 の Artisan docs では、Command クラスに SignatureDescription attribute を付ける例が示されています。今回はその形に合わせ、やることを 1 つに絞った Command にします。

コードのポイント

① Command の役割は「仕事そのもの」を持つこと

#[Signature('demo:scheduler-heartbeat')]
#[Description('Write one line to the scheduler demo log file')]
class WriteSchedulerHeartbeat extends Command
{

ここで定義しているのは、scheduler に登録される対象そのものです。頻度はまだ書かず、Command は「何をするか」だけを担います。

② 観測点をログファイルに固定すると挙動を追いやすい

$logFile = storage_path('logs/scheduler-demo.log');

File::ensureDirectoryExists(dirname($logFile));
File::append(
    $logFile,
    sprintf('[%s] scheduler command executed%s', now()->format('Y-m-d H:i:s'), PHP_EOL),
);

DB テーブルや画面は使わず、storage/logs/scheduler-demo.log への追記を成否の観測点にしています。最小構成では、観測点を絞るほうが追いやすいからです。

5. routes/console.php に schedule を登録する

Laravel 13 では app/Console/Commands 配下の Command が自動登録されるため、次は routes/console.php を開いて scheduler 定義を追加します。

更新後の routes/console.php は次のとおりです。

<?php

use App\Console\Commands\WriteSchedulerHeartbeat;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;

Artisan::command('inspire', function () {
    $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

Schedule::command(WriteSchedulerHeartbeat::class)
    ->everyMinute();

ここでは inspire の既定コマンドを残したまま、Schedule::command(...) を 1 本追加しています。scheduler の docs では、Command 名でもクラス名でも登録できますが、今回はクラス名でつなぐほうが、どの Command を呼ぶか追いやすくなります。

設定後の最初の確認は schedule:list です。

docker compose exec app php artisan schedule:list

php artisan demo:scheduler-heartbeat を含む行が表示され、先頭が * * * * * なら everyMinute() で Scheduler に登録できています。右端の Next Due: ... は次回実行予定です。

schedule:list で demo:scheduler-heartbeat が表示されている結果

コードのポイント

routes/console.php は scheduler 定義の入口になる

use App\Console\Commands\WriteSchedulerHeartbeat;
use Illuminate\Support\Facades\Schedule;

routes/console.php は HTTP route ではなく、console 側の入口をまとめる場所です。scheduler の定義もここへ寄せると、Command と実行頻度を同じ文脈で追えます。

② 頻度は scheduler 側にだけ書く

Schedule::command(WriteSchedulerHeartbeat::class)
    ->everyMinute();

everyMinute() のような頻度指定は scheduler 側の責務です。Command クラスへ時間条件を書き込まないため、同じ Command を別頻度で再利用しやすくなります。

6. schedule:run を実行してログを確認する

一覧に出ることを確認したら、次は 1 回だけ評価して動作を見ます。

docker compose exec app php artisan schedule:run
docker compose exec app tail -n 5 storage/logs/scheduler-demo.log

everyMinute() の task は、その時点の minute で実行対象なら schedule:run で評価されます。成功すると、1 つ目のコマンドでは scheduled task の実行ログが表示され、2 つ目のコマンドでは scheduler command executed を含む行が見えます。

ログファイルがまだ無い場合は、Command が一度も動いていないだけです。schedule:list に出ているか、schedule:run の実行に失敗していないかを先に確認してください。

schedule:run で demo:scheduler-heartbeat が実行された結果 scheduler-demo.log に scheduler command executed が追記された結果

開発中に毎分の評価を手動で打ち直したくない場合は、公式 docs にある schedule:work も使えます。

docker compose exec app php artisan schedule:work

これは foreground で scheduler を回し続ける local 向けの確認手段です。この記事の主線は schedule:run にあるため、まずは 1 回の評価が通ることだけ押さえれば足ります。

7. よくある詰まりどころ

schedule:list に出ない

原因の多くは routes/console.php の import 漏れか、Schedule::command(...) 自体を書いていないことです。WriteSchedulerHeartbeat::class を参照しているか、use Illuminate\Support\Facades\Schedule; があるかを見直してください。

schedule:run は通るのにログが増えない

Command 側の handle() が例外で止まっている可能性があります。次を実行して Laravel のログを確認します。

docker compose exec app tail -n 20 storage/logs/laravel.log

Command を直したのに挙動が変わらない

schedule:run はその都度アプリを起動して評価するので、手動確認だけなら再起動不要です。schedule:work を動かしっぱなしにしている場合は、コード変更後にそのプロセスを止めて起動し直してください。

production では何を置けばよいか分からない

基本は 1 行です。

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

これは Laravel 13 の scheduling docs にある基本形です。この記事では local 確認に留めていますが、本番ではこの 1 行から scheduler を呼びます。

8. まとめ

Laravel 13 の scheduler を最小構成で試すなら、押さえる場所は 3 つです。make:command で仕事本体を作ること、routes/console.php で頻度を登録すること、schedule:run で評価して観測点を確認すること。この 3 点だけで、scheduler の輪郭は十分つかめます。

関連して読みたい記事:

シリーズ 7/16

このシリーズ

Laravelの基本を最初から通す

  1. 1. Laravel入門(Route / Controller / View / Model 最小構成)
  2. 2. Laravelで最小CRUDを作る(一覧 / 作成 / 編集 / 削除)
  3. 3. Laravelで品質ゲートを敷く(PHPUnit / Larastan / Pint / GitHub Actions)
  4. 4. Laravelで認証を足す(Breeze 最小導入)
  5. 5. Laravelで認可を入れる(Policyで自分の本だけ編集できるようにする)
  6. 6. LaravelでQueueを始める(database queue + worker 最小構成)
  7. 7. Laravelでスケジューラを動かす(Command + Scheduler 最小構成) 現在の記事
  8. 8. Laravelでファイルアップロードを扱う(Storage + validation)
  9. 9. Laravelでリレーションを扱う(User / Book / Category の基本)
  10. 10. Laravelで検索・並び替え・ページネーション付き一覧を作る
  11. 11. LaravelでLivewireを始める
  12. 12. LaravelでLivewire一覧画面を作る(検索・並び替え・ページネーション)
  13. 13. LaravelでSanctum認証APIを作る
  14. 14. LaravelでBlade / Livewire / Inertia をどう使い分けるか
  15. 15. LaravelでInertia + Vue.js を始める
  16. 16. LaravelでInertia + React を始める