RunRail
A self-hosted workflow orchestrator for the scripts you already have. Schedule Python scripts, Jupyter notebooks, SQL files, and shell commands with dependencies, retries, logs, backfills, and a fast web UI — without rewriting anything.
Most teams outgrow cron long before they need a distributed orchestrator. RunRail covers that middle ground: one process, one SQLite file, and your existing code running on a real scheduler with full observability. No DAG files to author, no decorators to add, no platform to operate.
Features
- Bring your own code — point RunRail at a folder; scripts, notebooks, SQL, and shell commands run as-is in isolated subprocesses
- Dependency graphs with parallel execution — independent tasks run concurrently; each task starts the moment its dependencies succeed
- Timezone-aware scheduling built for high frequency — pick "daily at 9:00 in Asia/Dubai" from dropdowns (cron stays available as the advanced mode), with minute-level schedules, per-workflow concurrency limits, coalescing, and missed-run tolerance
- Managed Python environments — declare pip requirements in the UI; RunRail builds and atomically swaps isolated virtualenvs, keeping the last working build on failure
- Failure webhooks with sane semantics — one alert on the first failure, one on recovery; Slack webhooks and Microsoft Teams (Power Automate workflow) URLs work as-is
- Auto-pause — optionally disable a workflow after N consecutive failures instead of failing hundreds of times overnight
- Backfills and retries — queue one run per date over a range; re-run any finished run with identical parameters in one click
- Workflows as code —
runrail export/runrail applyround-trip definitions through YAML for version control and code review - Live dependency graph — every run renders its task DAG with statuses updating in place as branches execute
- Live logs and artifacts — streamed stdout/stderr with ANSI colors, search, tail-follow, timestamped notebook outputs, automatic retention cleanup
- A UI you'll actually use — command palette (⌘K), Gantt timelines, activity heatmaps, a full-screen wallboard for the team TV, dark and light themes
Install
RunRail ships as a single Python package with the web UI bundled in — no Node required to run it. Python 3.11+ is the only prerequisite.
pipx install runrail # isolated, recommended — or: pip install runrail
runrail serve
Then open http://127.0.0.1:8080. That's the whole setup.
On first launch RunRail creates its database, logs, and artifact store automatically and starts the API, web UI, scheduler, and worker in a single process — no separate init step, no migrations to run by hand. Everything lives in a per-user application-data directory:
| OS | Default location |
|---|---|
| macOS | ~/Library/Application Support/RunRail |
| Linux | ~/.local/share/RunRail (honours $XDG_DATA_HOME) |
| Windows | %LOCALAPPDATA%\RunRail |
Set RUNRAIL_HOME to store everything somewhere else (e.g. RUNRAIL_HOME=./.runrail to keep data beside a project). For notebook tasks, install the extra: pipx install "runrail[notebook]".
From the UI, connect a project folder, add an environment, and build your first workflow.
Core concepts
Projects point at directories where your code lives. Environments define how Python runs — a managed virtualenv built from declared pip requirements, an existing interpreter, or a Conda environment. Workflows group tasks into a dependency graph with an optional cron schedule. Environments resolve per task, falling back to the workflow default, then the project default; Python and notebook tasks never silently run on RunRail's own interpreter.
The recommended environment setup is Environments → New environment → Managed Python: declare requirements such as pandas==2.3.0, and RunRail builds an isolated virtualenv, records the build log and Python version, and rebuilds atomically when the requirements change. A failed rebuild preserves the previous working runtime.
CLI
runrail run daily-refresh --param region=ca # queue a manual run
runrail backfill daily-refresh --from 2026-06-01 --to 2026-06-30
runrail export -o workflows.yml # workflows as version-controllable YAML
runrail apply workflows.yml # declarative upsert by name
runrail import ~/old/.runrail # bring a previous setup into this home
runrail cleanup --older-than-days 30 --dry-run # prune old runs, logs, artifacts
runrail status
# run components as separate processes
runrail api
runrail scheduler
runrail worker --concurrency 8
Templating
Task commands and paths are Jinja templates. Every run receives:
| Variable | Meaning |
|---|---|
ds |
Logical date (2026-07-08); injected per-date during backfills |
ts / ts_nodash |
Run timestamp, ISO and compact (20260708T141005) |
run_id, task_run_id |
Identifiers for the current execution |
project_root, artifacts_dir |
Resolved paths for the run |
| your parameters | Run parameters and per-task parameters, merged |
python scripts/daily.py --date {{ ds }} --region {{ region }}
Notifications
Set RUNRAIL_NOTIFY_WEBHOOK_URL globally or a webhook per workflow. RunRail posts on the first failure after a success and again on recovery — never once per red run, so a broken two-minute schedule produces one alert rather than three hundred. Slack and custom receivers get a JSON payload whose text field renders directly, with structured fields alongside. For Microsoft Teams, create a Workflow from the "when a webhook request is received" template (Teams → channel → Workflows; Microsoft retired the classic Office 365 connector webhooks) and paste the generated URL — RunRail detects Teams-shaped URLs and sends the Adaptive Card envelope that flow expects. Pair with per-workflow auto-pause to stop repeat failures entirely.
Docker
docker compose up --build
Compose starts RunRail with PostgreSQL and persistent volumes on port 8080. Docker is optional for local use.
Architecture
A FastAPI process serves the API and the prebuilt React UI. APScheduler acts purely as a clock, writing due runs to the database; while a run executes, the next scheduled iteration coalesces to a single queued run instead of piling up or being dropped. A bounded worker pool (RUNRAIL_WORKER_CONCURRENCY) claims queued runs atomically, so different workflows execute concurrently and a long job never blocks a frequent one. Runs of the same workflow are serialized by its max_concurrent_runs (default 1) so a slow run can't overlap its own next iteration. Within a run, the task graph executes with real parallelism (RUNRAIL_TASK_PARALLELISM): independent tasks run concurrently, each starting the moment its dependencies succeed, and every task is a subprocess with its own logs, timeout, and process-group cleanup.
All of that concurrency happens on one machine (threads and subprocesses); RunRail does not yet distribute work across remote worker nodes.
SQLite in WAL mode is the default store; set RUNRAIL_DB_URL for PostgreSQL. Logs live under $RUNRAIL_HOME/logs/run_<id>/, artifacts under $RUNRAIL_HOME/artifacts/<id>/ with timestamped filenames so frequent runs and retries never collide.
Configuration
| Variable | Default |
|---|---|
RUNRAIL_HOME |
Per-user data dir (see Install) |
RUNRAIL_DB_URL |
SQLite at $RUNRAIL_HOME/runrail.db |
RUNRAIL_HOST / RUNRAIL_PORT |
127.0.0.1 / 8080 |
RUNRAIL_WORKER_CONCURRENCY |
4 concurrent runs |
RUNRAIL_TASK_PARALLELISM |
4 concurrent tasks per run |
RUNRAIL_RETENTION_DAYS |
unset; when set, finished runs older than this are deleted with their logs and artifacts |
RUNRAIL_NOTIFY_WEBHOOK_URL |
unset; default failure/recovery webhook |
RUNRAIL_BROWSE_ROOT |
user home; confines the UI file picker |
Schedules evaluate in each workflow's own timezone (UTC when unset), so "daily at 9:00" means 9:00 on that workflow's wall clock across DST changes; the UI displays occurrences in your local timezone. For deployments reachable beyond localhost, set RUNRAIL_BROWSE_ROOT to a narrow directory and place the server behind authentication — RunRail does not yet ship its own.
Current limitations
- Runs on a single machine — workflows and independent tasks run concurrently there, but work is not distributed across remote worker nodes yet
- No built-in authentication or RBAC
- SQL tasks execute against SQLite only
- Secrets are plain environment variables
Roadmap
Warehouse and database adapters, artifact previews, secrets management, API tokens and authentication, and richer run analytics. See open issues for details.
Development
pip install -e '.[dev]'
pytest
ruff check .
cd frontend && npm install && npm run build # writes the bundle into src/runrail/web/static
Licensed under Apache-2.0.
Metadata
Release files for runrail 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| runrail-0.4.0.tar.gz | 530.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| runrail-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.1 MB
Release files / runrail-0.4.0.tar.gz
| Download URL | runrail-0.4.0.tar.gz |
|---|---|
| Size | 530.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
644fddaff5a5ed39ec6002e6fcfbce2e052fbbb41f3e20bce63bf5a145c02f5e
|
|
BLAKE2b-256 checksum How to use checksums |
ff15bc336c852320515a94dbe6b04dad875a119271fb92b9ef3fb4b6f64d6381
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.
Transparency logRelease files / runrail-0.4.0-py3-none-any.whl
| Download URL | runrail-0.4.0-py3-none-any.whl |
|---|---|
| Size | 541.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5da7e6da77a14b26830438c64f8e0c2bfa62a608bd5e27dfb0367b84b0a3f10c
|
|
BLAKE2b-256 checksum How to use checksums |
a5c6441d08d99f2a7c865fb3764835398aa3fb3308a39bc96889e5bd3821beef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.
Transparency logSecurity for runrail 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for runrail-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.