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
Ubuntu server setup (recommended)

Ubuntu server setup (recommended)

A production, step-by-step setup for running a Laravel app with Askr on Ubuntu — using the pre-built release, a hardened systemd service, TLS, and recommended settings. Building from source instead? See Building.

You'll end up with: Askr running as a non-root service on :443, serving your app in worker mode (booted once, ~9× the FPM model), with opcache tuned, a shared cache, zero-downtime canary deploys, and a localhost admin dashboard.

Tested on Ubuntu 22.04 / 24.04 LTS (x86_64 and arm64).


1. System user and layout

sudo useradd --system --home /opt/askr --shell /usr/sbin/nologin askr
sudo mkdir -p /opt/askr /etc/askr/tls /var/www/app

Recommended layout:

Path Purpose
/opt/askr the Askr release (binary + lib/ + examples/)
/etc/askr/askr.toml configuration
/etc/askr/tls/ TLS certificate + key
/var/www/app your Laravel application

2. Install the release

Download the tarball for your architecture from the releases page and extract it into /opt/askr:

cd /tmp
VER=v1.4.14
ARCH=$(uname -m)            # x86_64 or aarch64
curl -fsSLO https://github.com/kwhorne/askr/releases/download/$VER/askr-${VER#v}-linux-$ARCH.tar.gz
tar xzf askr-${VER#v}-linux-$ARCH.tar.gz
sudo cp -r askr-${VER#v}-linux-$ARCH/* /opt/askr/
sudo chown -R root:root /opt/askr

Install the PHP runtime libraries the embedded interpreter links (usually already present):

sudo apt-get update
sudo apt-get install -y libssl3 libxml2 libonig5 libsqlite3-0 \
  libicu74 libcurl4 libpng16-16 libjpeg-turbo8 libfreetype6 libwebp7 \
  libzip4 libpq5 zlib1g

Verify:

/opt/askr/askr-run.sh doctor

You should see the PHP version, a non-ZTS build, all required extensions, and — on Linux — the io_uring kernel check.

3. Deploy your app

Put your Laravel code in /var/www/app (git clone, rsync, CI artifact — your choice), install dependencies, and set permissions:

cd /var/www/app
composer install --no-dev --optimize-autoloader
php artisan config:cache   # optional; see the reload note below
sudo chown -R askr:askr /var/www/app

Make sure storage/ and bootstrap/cache/ are writable by the askr user, and that .env has a real APP_KEY and production settings (APP_ENV=production, APP_DEBUG=false).

Copy the worker script into place (or reference it from the release):

# examples ship inside the release at /opt/askr/examples/
ls /opt/askr/examples/laravel-worker.php

4. Configure Askr

OPcache is built into libphp on PHP 8.5 (nothing to locate or load) — enable it in the INI (see below).

Create /etc/askr/askr.toml (adjust the opcache path to match):

[server]
listen = "0.0.0.0:443"
root = "/var/www/app/public"
workers = "auto"          # one process per CPU core
max_requests = 1000       # recycle each worker after N requests
max_body_size = "32M"

[worker]
script = "/opt/askr/examples/laravel-worker.php"
app_base = "/var/www/app"
# Tuned opcache. Match the extensions/ directory name to your build.
ini = "opcache.enable=1\nopcache.enable_cli=1\nopcache.validate_timestamps=0\nopcache.memory_consumption=256\nopcache.interned_strings_buffer=32\nopcache.max_accelerated_files=20000\nopcache.jit=tracing\nopcache.jit_buffer_size=128M"

[tls]
cert = "/etc/askr/tls/fullchain.pem"
key = "/etc/askr/tls/privkey.pem"

[cache]
slots = 16384             # ~70 MB shared cache (askr_cache_* / no Redis)

[admin]
listen = "127.0.0.1:9000" # localhost only

[reload]
canary = true             # zero-bad-deploy reloads

Validate it:

/opt/askr/askr config-check /etc/askr/askr.toml

opcache.validate_timestamps=0 maximises throughput (no per-file stat). New code is picked up on reload (below), not automatically — so don't skip the reload step in your deploys. If you php artisan config:cache, re-run it and reload after each deploy.

5. TLS certificate

Askr terminates TLS itself and can obtain and renew a Let's Encrypt certificate on its own — no certbot, no proxy. Add the ACME flags to ExecStart (the master answers HTTP-01 challenges on port 80 before forking; a renewal thread rolls workers when the cert nears expiry). See Auto-TLS:

ExecStart=/opt/askr/askr serve --config /etc/askr/askr.toml \
  --acme --acme-domain example.com --acme-email you@example.com \
  --acme-http 0.0.0.0:80

Bind --listen to the HTTPS port (0.0.0.0:443) in askr.toml, and grant the service the low-port capability (see the unit below). For local testing use --tls-self-signed.

Prefer to manage certs yourself? Point [tls] cert/key at your PEM files and reload Askr (systemctl reload askr) after each renewal.

Behind a load balancer that terminates TLS? Drop TLS entirely, set https = true under [server], and listen on a plain port (e.g. 0.0.0.0:8080).

6. systemd service (hardened)

/etc/systemd/system/askr.service:

[Unit]
Description=Askr PHP application server
Documentation=https://github.com/kwhorne/askr
After=network.target

[Service]
Type=simple
User=askr
Group=askr
WorkingDirectory=/opt/askr
Environment=ASKR_APP_BASE=/var/www/app
ExecStart=/opt/askr/askr serve --config /etc/askr/askr.toml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=2
KillSignal=SIGTERM
TimeoutStopSec=30
LimitNOFILE=65536

# Bind :443 as a non-root user, nothing more.
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

# Sandboxing
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/www/app/storage /var/www/app/bootstrap/cache /var/lib/askr
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictSUIDSGID=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now askr
sudo systemctl status askr

AmbientCapabilities=CAP_NET_BIND_SERVICE lets the non-root askr user bind :443 without running as root. ProtectSystem=strict makes the filesystem read-only except the app's storage/ and bootstrap/cache/.

7. Firewall

sudo ufw allow OpenSSH
sudo ufw allow 443/tcp
sudo ufw enable

The admin plane is bound to 127.0.0.1 — reach it over an SSH tunnel:

ssh -N -L 9000:127.0.0.1:9000 user@server   # then open http://127.0.0.1:9000

8. Verify

curl -I https://example.com/                 # 200, HTTP/2
sudo journalctl -u askr -f                   # logs

Operating Askr

Zero-downtime deploys

# 1. put the new code in place
cd /var/www/app && git pull && composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache    # if you cache config

# 2. reload (canary: rolls one worker, health-checks it, then the rest)
sudo systemctl reload askr

With [reload] canary = true, a broken deploy takes down one worker instead of the whole fleet, and the reload aborts — check journalctl -u askr for canary UNHEALTHY. Fix and reload again.

Upgrading Askr itself

Askr can update its own release in place — it downloads the matching Linux tarball, verifies the sha256, and swaps /opt/askr atomically (keeping the old version at /opt/askr/../askr.old for rollback):

sudo /opt/askr/askr upgrade --check      # is a newer release out?
sudo /opt/askr/askr upgrade --restart    # install it + restart the service

Without --restart it only swaps the files; the running server keeps the old libphp until you sudo systemctl restart askr. Pin or roll back with --version X.Y.Z. See CLI.

curl -s http://127.0.0.1:9000/api/status     # workers alive, RSS, uptime
curl -s http://127.0.0.1:9000/api/metrics    # req/s, PHP vs I/O, latency

For a liveness probe use /healthz, not /api/status:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9000/healthz

/api/status returns PIDs and memory figures, so it is gated as soon as you set ASKR_ADMIN_TOKEN — and a probe pointed at it then declares a healthy server unhealthy. /healthz is deliberately unauthenticated and answers liveness only. See Admin.

Day-to-day operations — what to check, log rotation, backups, cache clearing, certificates and the mistakes worth avoiding — are in Maintenance.

Logs

Askr logs to stdout → journald: journalctl -u askr. Set verbosity with Environment=RUST_LOG=askr=debug in the unit if needed. Your app's logs go wherever Laravel is configured (storage/logs or stderr).


Recommended settings

Setting Recommended Why
[server] workers auto one process per core; matches CPU.
[server] max_requests 5002000 recycle workers to bound drift/leaks.
[server] max_body_size your largest upload + headroom rejects oversized bodies (413).
opcache.validate_timestamps 0 max throughput; reload on deploy.
opcache.memory_consumption 256 enough for a large app's opcode cache.
opcache.jit tracing JIT for CPU-bound code.
[cache] slots size to your working set shared cache/rate-limit; 0 to disable.
[admin] listen 127.0.0.1:9000 never expose the admin plane publicly.
[reload] canary true stop bad deploys at one worker.

Sizing & memory

Each worker holds a booted app (tens of MB for Laravel). Budget roughly workers × per-worker RSS + the shared cache. Worker RSS is flat across requests (no per-request growth), so recycling is about long-term drift. Watch per-worker RSS on /api/status.

Whole runtime in one binary (optional)

Run queue workers and the scheduler in the same service — no Horizon, no crontab:

[queue]
workers = 2
script = "/opt/askr/examples/askr-queue.php"

[scheduler]
script = "/opt/askr/examples/askr-scheduler.php"

The queue needs your app's queue connection configured as usual. See Deployment.

CoW mode (optional, experimental)

For ~ms warm respawns and shared memory, add --cow (not via config yet) with a worker script that calls askr_cow_ready() — see CoW. It's experimental and disables the admin plane and sidecars; validate under load first.


Troubleshooting

  • libphp.so: cannot open shared object file — run via /opt/askr/askr (its rpath points at ./lib), or from /opt/askr. Install the runtime libs in step 2.
  • could not bind 0.0.0.0:443 — the CAP_NET_BIND_SERVICE capability is missing from the unit, or another service holds :443.
  • New code not showing — you have validate_timestamps=0; run systemctl reload askr after deploying.
  • askr doctor reports a missing extension — you're on the minimal libphp; use the release tarball (built with the laravel profile).
  • io_uringaskr doctor reports kernel support (≥ 5.1; 5.10+ recommended). The current I/O layer is epoll-based; io_uring is the next step.