Upgrading Askr
The short version: within 1.x, an upgrade is a drop-in. Replace the binary (or
the image tag), reload, done. You don't need to touch askr.toml.
That's a promise, not a hope — the surfaces that make it true are listed in STABILITY.md, and every release since 1.0 has kept it. New features arrive as new config keys that default to off, so a config written for 1.0.0 still means exactly the same thing on the newest 1.x.
- How to upgrade
- Zero-downtime upgrades
- Rolling back
- Version-by-version notes
- What can actually bite you
How to upgrade
Release tarball (systemd install)
askr upgrade # downloads, verifies sha256, swaps the prefix
sudo systemctl reload askr # graceful; see below
askr upgrade replaces the whole prefix (binary + bundled libphp) atomically and
keeps the previous version at <prefix>/../askr.old. It does not restart the
server unless you pass --restart.
Verify the checksum yourself if you'd rather not trust the updater:
VER=v1.4.0; ARCH=$(uname -m)
curl -fLO https://github.com/kwhorne/askr/releases/download/$VER/askr-${VER#v}-linux-$ARCH.tar.gz
curl -fLO https://github.com/kwhorne/askr/releases/download/$VER/askr-${VER#v}-linux-$ARCH.tar.gz.sha256
sha256sum -c askr-${VER#v}-linux-$ARCH.tar.gz.sha256
Docker
docker pull ghcr.io/kwhorne/askr:1.4.0 # or :1.4 to follow patches
Pin the exact version in production and bump it deliberately. :1.4 follows
patch releases, :latest follows everything — convenient for a laptop, surprising
on a server at 3am.
The -full tags (1.4.0-full) are the same server built with the optional features
compiled in: sql-backend, observ, otel, http3. If you use any of those, stay
on -full.
The Laravel package
composer update kwhorne/askr-laravel
The package and the server are versioned independently; any recent package works with
any 1.x server. Upgrade it when you want a new PHP-side helper.
Zero-downtime upgrades
A reload replaces the workers without dropping a connection:
kill -HUP $(pidof askr) # or: systemctl reload askr, or POST /api/reload
Workers finish their in-flight requests, then are replaced one at a time — there is always a live worker accepting. A reload does not pick up a new Askr binary: the master process is the old one. For a new binary you need a restart, which means a brief gap unless something in front of you retries.
If you can afford one more moving part, the sturdiest sequence is:
askr upgrade(new binary on disk, old one kept)askr config-check askr.toml— catches a config that the new version rejects before you stop anything- restart
Turn on the canary so a bad application deploy can't take the fleet with it — worth having in place before you start upgrading things:
[reload]
canary = true
canary_window = 5
canary_min_requests = 20
See Deployment.
Rolling back
- Tarball: the previous prefix is at
<prefix>/../askr.old. Swap it back and restart. - Docker: run the previous tag. This is why pinning matters.
- Config: a config written for an older 1.x is still valid, so rolling back the
binary never requires rolling back
askr.toml.
Rolling back is a supported operation, not an emergency improvisation. If a downgrade ever fails on a config that the newer version accepted, that's a bug worth reporting — it means we added something that isn't additive.
Version-by-version notes
Nothing here is required. These are the things worth adopting after each upgrade.
To 1.4.0
Worth doing, in this order:
# 1. Find out what's actually worth caching — and what only looks cacheable
askr serve --traffic-log /tmp/traffic.jsonl # leave it for an hour of normal traffic
askr cache-report /tmp/traffic.jsonl
// 2. Cache the routes it called safe. No tag list to maintain: the page is tagged
// with the models it read, so a save() clears exactly the pages that showed them.
Route::get('/products/{product}', ProductController::class)
->middleware('askr.cache:300');
Requires composer update kwhorne/askr-laravel for the middleware.
One behaviour change to know about. A response carrying more cache tags than an
entry can hold (8) is now refused rather than cached. Before, the surplus tags were
silently dropped — which meant askr_cache_forget_tag() could never reach them, and
the page stayed stale until its TTL expired.
If you hand-write long tag lists you may therefore see a page stop being cached, and
askr_cache_tag_overflow_total count up on /metrics. That page was already broken;
it just failed quietly. Tag by class (posts) instead of per instance (posts:3), or
cache a smaller fragment with ESI.
--traffic-log is a diagnostic: it writes a line per PHP-served request, so turn it
off again when you have your answer.
To 1.3.0
No action needed. Internally this is a dependency refresh (including four major bumps) plus a much larger test suite; no user-visible behaviour changed.
Two things to know:
- A persisted response cache may be dropped once.
[cache] persistfiles are tied to the entry layout, and a new Askr build can invalidate them. The first boot after an upgrade then starts with a cold cache and logsresponse cache dump ignored (different build or cache size). That's the guard working — a cache is never reinterpreted across layouts. ASKR_*_DBSQLite files are opened by a newer bundled SQLite (rusqlite 0.31 → 0.40). SQLite is backwards compatible with older files, so nothing to do, but take your usual backup first if those hold queue jobs you can't lose.
To 1.2.0
Worth adopting:
# Refuse abusive traffic before PHP wakes up — enforced across the whole fleet
[server]
trusted_proxies = ["10.0.0.0/8"] # required behind a load balancer
[[ratelimit]]
path = "/login"
limit = 5
window = 300
# Keep the cache across restarts
[cache]
persist = "/var/lib/askr/rcache.bin"
persist_key = "your-release-sha"
- If you configure rate limits behind a proxy and forget
trusted_proxies, every client shares one bucket and you'll rate-limit your whole site. Askr warns at startup; take the warning seriously. - Run
askr tune --root publicfor measured starting values forworkersandmax_rss_mb. - The canary gate got much better in this release (it compares the new worker against
the rest of the fleet instead of an absolute error count). If you'd tried
canarybefore and found it aborted deploys for no reason, try it again.
To 1.1.0
Worth adopting: ESI if a single live widget is
what keeps a page uncacheable, PURGE/BAN for URL-targeted invalidation, and
[[cache.rule]] if you need cache policy for an app you can't edit.
PURGE/BAN are gated on ASKR_ADMIN_TOKEN, or restricted to loopback when no token
is set. Set the token if you want to invalidate from a deploy script on another host.
To 1.0.1
Upgrade if you're on 1.0.0. Static file serving could disclose PHP sources and
dotfiles: GET /index.php returned source, and with a document root pointed at an app
root, GET /.env returned APP_KEY and database credentials. Fixed in 1.0.1, with the
suffixed variants (index.php.bak, config.php~) fixed in 1.1.0.
While you're there, check that your document root is a dedicated public/ directory
and not the application root.
From 0.9.x to 1.0
1.0 added no features — it froze the surface. If your 0.9.12 setup worked, 1.0 works
identically.
One deprecation carried over: --acme-directory became --acme-directory-url in
0.9.7 (it was too easily confused with --acme-dir, the local certificate cache). The
old spelling still works as a hidden alias.
What can actually bite you
Honest list, in rough order of how often it happens:
- A cold cache after a restart. The response cache lives in shared memory. Unless
[cache] persistis set — and the dump is still valid — the first requests after any restart hit PHP. Coalescing stops it becoming a stampede, but a big site restarting at peak will feel it. Reload rather than restart when you can. - A config the new version rejects. Validation gets stricter as it gets better
(glob patterns that look like regexes, rules with no effect, unknown keys). This is
deliberate — a silently ignored rule is worse — but it means
askr config-check askr.tomlbelongs in your deploy script, before the restart. libphpand the binary are a matched pair. A release tarball contains both. Don't mix a newaskrwith an old bundledlibphp;askr upgradeand the Docker images handle this for you.- A PHP security release means a new Askr release. PHP is compiled into the distribution, so you can't patch it independently. Watch PHP's releases as well as ours.
- Optional features live in the
-fullbuild. Upgrading from a-fulltag to a plain one silently dropssql-backend,observ,otelandhttp3. The server will start and yourASKR_OBSERV_DSNwill simply do nothing.
After upgrading
askr doctor # PHP build, extensions, platform probes
askr config-check askr.toml # config still valid and resolving as you expect
curl -s localhost:9000/api/status # workers alive, version, rollout state
If something looks wrong, the admin API and the observability guide are the fastest ways to see what the server thinks is happening.