Production checklist
Nothing here is checked for you unless the item says so.
SQLite (one host)
Section titled “SQLite (one host)”- One host, local disk. A network filesystem or a second host is refused at open (Choosing a store).
- Low traffic only. One writer serves every worker, and
synchronous=fullwaits forfsyncon each commit. Use Valkey for production load (why). - Linux, not macOS. macOS
fsyncdoes not flush the drive cache, so commits can be lost on power loss. - Back up with
pyrula store backup DESTon a schedule, not by copying the.dbfile. There is no replication. - Disk for retention. Finished runs are kept 7 days (
retention_seconds);pyrula store infoshows the size.
Valkey or Redis
Section titled “Valkey or Redis”- Valkey 7.2+ or Redis 6.0+, with
SCRIPT LOADandEVALSHAallowed. The store loads one Lua script and calls it by its hash. A user whose ACL denies them fails at startup with the grants to add (recipe). maxmemory-policy noeviction, on an instance dedicated to pyrula.allkeys-*deletes live run journals under memory pressure, soValkeyStorerefuses it whilemaxmemoryis above zero (PYRULA_VALKEY_EVICTION_CHECK=warn|offaccepts the risk, Installation).volatile-*only warns: run locks, leases and dedupe keys carry no TTL, so it can drop finished-run history and a few short-lived keys (which). ElastiCache, Memorystore, Azure Managed Redis and Redis Cloud default tovolatile-lruand Render toallkeys-lru; Managed Valkey and Redis gives each one’s fix. ElastiCache Serverless fixesvolatile-lru, so alarm onevicted_keysthere. Checked for you:pyrula dev --check --store URLandpyrula db check --store URLfail onallkeys-*and warn onvolatile-*before deploy.- Persistence on:
appendonly yesor an RDBsaveschedule. Without it a restart loses every in-flight run. The store cannot check this; verify on the server. - A namespace per environment when several share an instance (
PYRULA_VALKEY_NAMESPACE).
Postgres archive (optional)
Section titled “Postgres archive (optional)”Postgres holds completed runs; live state stays in Valkey. Install "pyrula[workflows,postgres]"
(pyrula-agents already depends on it), run
pyrula db migrate --postgres-dsn "$PYRULA_POSTGRES_DSN" once, then
pyrula db check --store ... --postgres-dsn ... before starting workers. A database older or
newer than the code supports fails with ArchiveSchemaMismatch
(Versioning & stability,
Workers).
Workers
Section titled “Workers”- Run a worker tier. Sleeps, signal waits, interrupts, children and schedules need a
WorkflowWorkerorpyrula worker; nothing else wakes those runs (Deployment modes, Deploy a worker). - Agents: don’t ship embedded mode. An agent under
pyrula devorcreate_app’s default refusesctx.sleep,ctx.sleep_until,ctx.run_child,ctx.spawn_workflow,ctx.gather_subagentsand waits with a deadline (details). - Give each worker a stable id.
WorkflowWorker(worker_id=...)defaults to a random id per process; use the host or instance name so logs and leases agree across restarts. Do the same withPYRULA_RUNTIME_IDfor Cloud. - Know the runbook commands.
pyrula runs list --status error --since 1h,pyrula runs show RUN_ID --journal, andpyrula runs cancel|terminate|retryread the storePYRULA_STORE_URLnames and need no running worker (CLI reference).
Authentication
Section titled “Authentication”Put auth on every HTTP surface a non-loopback address can reach; with no provider, anyone who
can reach the port can submit, stream, cancel and resume runs. The startup refusal for an open
non-loopback bind only sees hosts declared in AppOptions(host=..., auth=...), not a bare
uvicorn.run. See Serving auth.
Schedules
Section titled “Schedules”pyrula schedules create derives the id from agent, cron and params, so re-running it is a
no-op. store.schedule_create with Schedule.create(...) mints a random id unless you pass
schedule_id=. Pick one path per job, or two schedules fire. Cron runs in the schedule’s time
zone (UTC by default) and missed occurrences are not backfilled
(Schedules, Workers).
Pyrula Cloud
Section titled “Pyrula Cloud”Set PYRULA_CLOUD_API_KEY in the worker’s environment and install "pyrula[cloud]"; a key
without the client is an error, and without a key nothing is sent. A store belongs to one
project and environment. See Connect to Pyrula Cloud and
What leaves your runtime.