Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra
Laravel on Askr — the recommended setup

Laravel on Askr — the recommended setup

This is the end-to-end guide to running a Laravel application on Askr with the official kwhorne/askr-laravel package. Follow it top to bottom and you get a single-box (or multi-box) stack with no Redis, no Horizon, no separate WebSocket server, and no cron — session, cache, locks, queue and broadcasting all served from the Askr binary.

New to Askr's model? Skim Worker mode first — Laravel boots once and serves many requests (Octane-style), so a few things behave differently from PHP-FPM.


Automatic page caching

Route::get('/products/{product}', ProductController::class)
    ->middleware('askr.cache:300');

No tag list: the response is tagged with the models it read, so a save() clears exactly the pages that showed them. Details in the package README; use askr cache-report to see which routes are safe to cache before you add it.

What the package gives you

.env Replaces Backed by
SESSION_DRIVER=askr Redis / DB / file sessions shared-memory large region
CACHE_STORE=askr Redis cache, counters, rate limiting, Cache::lock() shared-memory cache
QUEUE_CONNECTION=askr Redis / DB queues + Horizon shared-memory job queue + supervised workers
BROADCAST_CONNECTION=askr Redis pub/sub + Reverb/Pusher in-binary pub/sub + Pusher-compatible WS

Each driver is registered automatically by Askr\Laravel\AskrServiceProvider (Laravel package auto-discovery) — no manual wiring. They all call Askr's askr_* builtins, which exist only when the app is served by Askr with the matching shared-memory regions enabled (see Regions & sizing).

Two tiers are available per driver:

  • L1 (default) — shared memory, mapped before fork, shared across every worker on the box. Fast, lock-free-ish, and gone on reboot.
  • L2 (optional) — a durable, replicated backend over SQL Anywhere, selected at runtime with ASKR_*_DB. Same PHP API, only the backend differs. See Durable L2.

Requirements

  • Askr ≥ 0.9.3 (broadcasting driver + the complete Laravel surface).
  • PHP 8.5 (bundled in the Askr release/Docker image).
  • Laravel 11, 12 or 13.
  • A worker-mode deployment (--worker-script). Per-request mode works too, but the session/cache/queue regions and the recommended performance all assume workers.

1. Install

composer require kwhorne/askr-laravel

That's it — the service provider is auto-discovered. Nothing to add to config/app.php.

Want to run this in Docker rather than installing Askr locally? examples/docker/quickstart.yml serves the project you're in with docker compose up — worker mode and a response cache already set.

Versions track the server. The package is released alongside Askr with the same version number, so pair askr-laravel 1.4.x with an Askr 1.4.x server. They're loosely coupled — the drivers talk to whatever regions the running server exposes — but a feature added in a given release needs both halves at that version. Automatic page caching needs ^1.4.


2. Configure .env

SESSION_DRIVER=askr
CACHE_STORE=askr
QUEUE_CONNECTION=askr
BROADCAST_CONNECTION=askr

3. Register the stores/connections

Add the matching definitions so Laravel knows what askr means. (Cache and session usually resolve from the driver alone, but being explicit is clearer and required for queue + broadcasting.)

// config/cache.php  →  'stores'
'askr' => ['driver' => 'askr'],
// config/queue.php  →  'connections'
'askr' => [
    'driver'      => 'askr',
    'queue'       => 'default',
    'retry_after' => 90,
],
// config/broadcasting.php  →  'connections'
'askr' => ['driver' => 'askr'],

Sessions ride on the cache large region, so no extra config is needed beyond SESSION_DRIVER=askr — just make sure Askr runs with --cache-large-slots (below).


4. The runner scripts

Askr serves your app through small runner scripts that boot Laravel once and then handle requests/jobs in a loop. Copy the three bundled examples into your app so they're versioned with your code:

mkdir -p askr
cp /opt/askr/examples/laravel-worker.php   askr/worker.php     # HTTP
cp /opt/askr/examples/askr-queue.php       askr/queue.php      # queue:work
cp /opt/askr/examples/askr-scheduler.php   askr/schedule.php   # schedule:run

(The Docker image ships them under /opt/askr/examples; the release tarball under <prefix>/examples.) They read ASKR_APP_BASE to find your app root and reset per-request state between requests. See Worker mode if you want to customise the reset.


5. Run Askr

Development

ASKR_APP_BASE="$PWD" askr serve \
  --root public \
  --worker-script askr/worker.php \
  --workers 2 \
  --cache-slots 8192 --cache-large-slots 4096 \
  --queue-slots 8192 \
  --pusher \
  --listen 127.0.0.1:8000

Production (the full stack in one command)

ASKR_APP_BASE=/srv/app askr serve \
  --root public \
  --worker-script askr/worker.php \
  --workers auto \
  --max-rss 400 \
  --cache-slots 16384 --cache-large-slots 4096 \
  --queue-slots 8192 \
  --queue 1 --queue-max 8 --queue-script askr/queue.php \
  --scheduler-script askr/schedule.php \
  --pusher \
  --admin 127.0.0.1:9090 \
  --acme --acme-domain app.example.com --acme-email ops@example.com \
  --listen 0.0.0.0:443

One process tree now covers: HTTP, cache, sessions, locks, the job queue and its autoscaled workers, the scheduler, broadcasting, TLS, and a metrics/admin plane. Prefer a config file in production — see Configuration for the askr.toml equivalent, and Ubuntu setup for the systemd unit.


Regions & sizing

The shared-memory regions are fixed at startup and evict oldest-first when full. Size them for your peak concurrency:

Flag Backs Guidance
--cache-slots N cache, counters, rate limiting, Cache::lock() (≤ 4 KB values) a few × your working set of keys
--cache-large-slots N sessions, cache fragments, serialized collections (≤ 64 KB) ≈ your peak concurrent session count
--queue-slots N the job queue (askr_queue_*) ≥ your peak pending + delayed jobs (32 KB/slot)

Integers/floats are stored unserialized, so Cache::increment() (the rate limiter) is truly atomic across all workers.


Queue workers & autoscaling

queue:monitor and anything else that reads the queue's depth work from Askr 1.4.10, which exposes all four counters Laravel 13's contract asks for from one pass over the shared slot table:

Method Meaning
size() / pendingSize() Delay elapsed, no live reservation — a worker can take it now.
delayedSize() Still waiting for its delay.
reservedSize() Held by a worker whose visibility window hasn't lapsed.
creationTimeOfOldestPendingJob() When the oldest available job was pushed.

The three buckets are disjoint and sum to the jobs on that queue, which is the invariant worth knowing: a number that doesn't add up is worse than a missing one. Against a server older than 1.4.10 the driver reports pendingSize() accurately and the rest as 0/null — "unknown", not "empty".

--queue N runs N supervised queue-worker processes. Add --queue-max M to turn it into a backlog-driven autoscaling range — Horizon's balance=auto, native, with no extra daemon (Askr sees the backlog in shared memory and owns the worker pool):

--queue 1 --queue-max 8 --queue-script askr/queue.php

On a burst the pool jumps toward the target (~1 worker per 10 ready jobs), then drains one worker every couple of seconds as the backlog clears — gracefully (scaled-down workers finish their current job, then exit). Watch it on /metrics: askr_queue_workers, askr_queue_ready, askr_queue_total, askr_queue_oldest_seconds.

Named queues / priority work as usual — set --queue-script to a runner that does queue:work --queue=high,default,low.


Scheduler

--scheduler-script askr/schedule.php runs Laravel's scheduler in-process — no system cron entry. It's a supervised sidecar; it's respawned if it dies.


Broadcasting (Laravel Echo)

With BROADCAST_CONNECTION=askr and --pusher, broadcast(new Event()) publishes a Pusher-shaped frame through Askr's in-binary pub/sub, and Askr's Pusher-compatible WebSocket fan-out delivers it to Echo clients — no Reverb, no Pusher account, no Redis.

Point Laravel Echo at Askr (it speaks the Pusher protocol):

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'askr',                       // any non-empty key
    wsHost: window.location.hostname,
    wsPort: 443,                       // your Askr listen port
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
    disableStats: true,
});

Public channels work out of the box. Private/presence channels use Laravel's standard channel authorization (routes/channels.php) — no extra config. There's also a plain SSE endpoint (GET /askr/events?channel=NAME) if you'd rather not use Echo; see Broadcasting.


Durable L2 (optional)

For durability across restarts or multiple boxes, enable the SQL Anywhere L2 tier. It's behind a build feature, so the default build is unaffected:

# use the published -full image/tarball (durable L2 + observ compiled in) …
docker pull ghcr.io/kwhorne/askr:0.9-full
# … or build it yourself
cargo build --release --features sql-backend
# select per subsystem at runtime; unset falls back to L1 shared memory
ASKR_CACHE_DB=/var/lib/askr/cache.db
ASKR_QUEUE_DB=/var/lib/askr/queue.db
ASKR_BROADCAST_DB=/var/lib/askr/events.db

The PHP API and Laravel drivers don't change — only the backend does. When both L1 and L2 are on, L1 becomes a write-through read cache in front of L2: hot reads skip the database round-trip, writes go to L2 (the source of truth). Queue autoscaling reads its backlog from L2 automatically. See Storage backends.


Production checklist

  • Sessions: --cache-large-slots ≥ peak concurrent sessions. Never use SESSION_DRIVER=array in workers — it leaks into the PHP heap until OOM.
  • Leak safety: set --max-rss <MB> (well below memory_limit) so a worker is recycled gracefully before it OOMs, and/or use --cow for ~ms warm respawns.
  • Config/route/view cache: run php artisan config:cache route:cache view:cache at deploy, then reload Askr (SIGHUP) so workers pick up the new code.
  • TLS: --acme gets and renews Let's Encrypt certs with no proxy (Auto-TLS).
  • Hardening: --sandbox (seccomp no-exec) and --sandbox-write storage /tmp (Landlock) shrink the blast radius of an RCE (Sandbox, Linux).
  • Observability: --admin 127.0.0.1:9090 exposes /metrics (Prometheus) and a dashboard (Admin).

Verify it works

# 1) the package resolves + installs from Packagist
composer require kwhorne/askr-laravel

# 2) drivers are live (with --admin on): the queue gauges appear
curl -s http://127.0.0.1:9090/metrics | grep askr_queue_

# 3) a cache round-trip from tinker (while served by Askr)
php artisan tinker --execute="Cache::put('k','v',60); echo Cache::get('k');"

doctor also checks the required extension set:

askr doctor --root public

Migrating from Redis

You had You now set Notes
CACHE_STORE=redis CACHE_STORE=askr counters/rate limiter/Cache::lock() all carry over
SESSION_DRIVER=redis SESSION_DRIVER=askr needs --cache-large-slots
QUEUE_CONNECTION=redis + Horizon QUEUE_CONNECTION=askr + --queue/--queue-max autoscaling replaces Horizon balance=auto
BROADCAST_CONNECTION=reverb/pusher BROADCAST_CONNECTION=askr + --pusher Echo config points at Askr
redis-server, horizon, reverb, cron (nothing) all folded into the Askr process tree

Drop Redis entirely on a single box; reach for the L2 SQL Anywhere tier when you need durability or more than one box.


Troubleshooting

  • Driver [askr] not supported — the app wasn't served by Askr (the askr_* builtins are absent), or the region for that driver isn't enabled. Check you passed --cache-slots / --queue-slots etc., and that you're running under askr serve, not php artisan serve.
  • Sessions not persisting / evicted--cache-large-slots is too small for your concurrency; increase it.
  • php artisan … outside Askr — CLI commands run under system PHP where the askr_* builtins don't exist; the session driver degrades to a no-op and the cache/queue drivers will error if resolved. Don't point long-running CLI at the askr drivers — run queue/schedule through Askr's runner scripts instead.
  • Broadcasting silent — confirm --pusher is set and the Echo wsPort/host match your --listen.

See also: Worker mode · Shared cache · Broadcasting · Storage backends · Deployment.