RunWisp vs Dagu: not every job needs a DAG.
Dagu orchestrates multi-step pipelines with dependencies, conditionals, and parameter passing. RunWisp schedules independent tasks and babysits long-running services. Different tools, different problems. Pick the one that matches your workload.
Should you switch?
The 10-second version. Skim, decide, scroll down for the receipts.
Consider RunWisp if
- Every job on your box is independent. No step B waits on step A's output.
- You also need long-running services supervised alongside scheduled tasks.
- Dedicated service supervision, coalesced alerts, and a TUI matter more than DAG edges.
- You want per-run history with stdout/stderr captured automatically.
- A TUI over SSH is part of your workflow.
Stay on Dagu if
- Any of your jobs form a pipeline: extract then transform then load.
- Steps pass outputs to downstream steps via parameters.
- Conditional execution matters: skip step C if step B produced no rows.
- You need the DAG visualization in the web UI to reason about your workflow.
- You're building data pipelines, not running cron jobs.
Yes / no, at a glance
Yes / no — and both columns win on something.
| Capability | Dagu | RunWisp |
|---|---|---|
| Single binary, no deps | ✓ | ✓ |
| Step dependencies (DAG) | ✓core feature | ✗ |
| Conditional execution | ✓preconditions | ✗ |
| Parameter passing between steps | ✓output vars | ✗ |
| Cron scheduling | ✓ | ✓5/6-field + aliases |
| Long-running processes | ~scheduled start / stop / restart | ✓supervision + replicas |
| Run history per firing | ✓ | ✓SQLite rows |
| Captures stdout / stderr | ✓ | ✓ |
| Retries with backoff | ✓retryPolicy | ✓constant / linear / exp |
| Failure notifications | ✓Slack / Teams / email / Telegram / webhook | ✓Slack / Discord / email / Telegram / webhook |
| Overlap / concurrency control | ✓queues + catch-up policies | ✓skip / queue / kill |
| Catch-up after downtime | ✓window + queues | ✓latest / all / skip |
| Timezone-aware scheduling | ✓CRON_TZ | ✓named DST behavior |
| Web UI | ✓DAG graph view | ✓run history + logs |
| TUI | ✗ | ✓ |
| DAG visualization | ✓ | ✗ |
| All self-host features in GPL build | ✗SSO / RBAC / audit require paid license | ✓ |
| Config format | ~YAML | ~TOML |
Pros & cons
What Dagu does better
- DAG execution is the whole point. Steps depend on each other, run conditionally, pass outputs downstream.
- Preconditions let you skip or abort a step based on the output of a previous one.
- The web UI renders the DAG as a graph. You can see which step failed and why the downstream steps never ran.
- Parameters and templating let you build reusable workflow definitions.
- Sub-DAG execution: one DAG can call another DAG as a step.
- If your workload is a pipeline, Dagu was built for it. RunWisp was not.
What RunWisp adds
- Dedicated service supervision alongside scheduled tasks: restart-on-failure backoff, graceful stop, and replicas.
- DST handling as named behaviour: fall-back deduped, spring-forward shifted forward by the gap (02:30 runs at 03:30).
catch_up = 1/N/0per task, without enabling a separate queue subsystem.on_overlap = "skip"/"queue"/"kill"per task.- Coalesced failure notifications. Five flaps become one Slack message, not five.
runwisp tuistreams run history and logs over SSH. No browser needed.- Simpler config for the simple case. If every job is independent, a flat TOML stanza per task is less ceremony than a YAML DAG.
The simple case
Run a backup script. Dagu defines a single-step DAG. RunWisp defines a scheduled task. For one independent job, the DAG wrapper is ceremony.
# backup.yaml: a single-step Dagu DAG
schedule: "0 3 * * *"
steps:
- id: backup
run: /usr/local/bin/backup.sh # runwisp.toml
[tasks.backup]
cron = "0 3 * * *"
run = "/usr/local/bin/backup.sh" The version that runs in prod
A four-step ETL pipeline. Dagu expresses the dependency chain: extract, validate, transform, load. RunWisp can run the same four scripts, but they fire independently on the same schedule. If step order matters, Dagu is the right tool here.
# etl-pipeline.yaml: extract, transform, load with dependencies
schedule: "0 4 * * *"
params:
- name: DATE
default: "2026-09-10"
steps:
- id: extract
run: |
/opt/etl/extract.sh "${params.DATE}"
printf 'raw_file=%s\n' "/tmp/raw-${params.DATE}.csv" >> "$DAGU_OUTPUT_FILE"
outputs:
- name: raw_file
- id: validate
run: /opt/etl/validate.sh "${steps.extract.outputs.raw_file}"
depends: extract
- id: transform
run: /opt/etl/transform.sh "${steps.extract.outputs.raw_file}"
depends: validate
- id: load
run: /opt/etl/load.sh
depends: transform
retry_policy:
limit: 3
interval_sec: 30 # runwisp.toml: same four scripts, but no dependency chain
# RunWisp can't express "load depends on transform".
# If your ETL steps MUST run in order, keep Dagu.
# If they're truly independent, RunWisp is simpler:
[tasks.extract]
cron = "0 4 * * *"
timeout = "30m"
keep_runs = 60
notify = ["slack-ops"]
run = "/opt/etl/extract.sh"
[tasks.validate]
cron = "0 4 * * *"
timeout = "10m"
keep_runs = 60
run = "/opt/etl/validate.sh"
[tasks.transform]
cron = "0 4 * * *"
timeout = "45m"
retry_attempts = 3
retry_delay = "30s"
retry_backoff = "exponential"
keep_runs = 60
run = "/opt/etl/transform.sh"
[tasks.load]
cron = "0 4 * * *"
timeout = "20m"
retry_attempts = 3
retry_delay = "30s"
retry_backoff = "exponential"
keep_runs = 60
notify = ["slack-ops"]
run = "/opt/etl/load.sh" Migration cheat sheet
Only migrate if your DAGs are actually independent jobs. If steps depend on each other, keep Dagu.
| Dagu | RunWisp |
|---|---|
schedule: in the YAML | cron = on each [tasks.*] |
steps[].run | run = |
steps[].depends | No equivalent. If jobs are independent, drop it. If not, keep Dagu. |
outputs + $DAGU_OUTPUT_FILE | No equivalent between tasks. Keep a pipeline on Dagu, or explicitly manage a shared file. |
steps[].preconditions | Check inside the script itself, exit non-zero to fail. |
retry_policy.limit | retry_attempts = |
retry_policy.interval_sec | retry_delay = + retry_backoff = |
| Dagu notification rule (global / workspace / DAG) | notify = ["slack-ops"] |
| Dagu web UI (DAG graph) | RunWisp web UI (flat run history) or runwisp tui |
params: + ${params.NAME} | Environment variables in env = { ... } or inline in the script |
Gotchas
RunWisp does not do DAGs
This is the big one. If step B depends on step A finishing successfully, RunWisp cannot express that. Tasks fire on their own schedule, independently. Wrapping a pipeline in a single shell script works for simple chains but loses Dagu's per-step visibility, retry, and conditional logic. If you need a DAG engine, keep the DAG engine.
No parameter passing between tasks
Dagu's output: lets step A export a variable that step B reads. RunWisp tasks are isolated. The workaround is a shared file or environment variable, but that's on you to manage. If your workflow relies on output chaining, that's a reason to stay on Dagu.
Dagu can time long-running runs; RunWisp supervises services
Dagu can schedule start, stop, and restart times for a long-running workflow. RunWisp adds dedicated service semantics for unexpected exits: restart policies with backoff, graceful stop, and multiple instances. Pick RunWisp when the process must be kept alive, not merely started on a timetable.
Independent jobs get simpler config
If your Dagu DAGs are all single-step (one steps: entry, no depends:), the YAML wrapper adds ceremony. A flat [tasks.backup] stanza with cron = and run = is fewer lines and easier to scan.
FAQ
When should I pick Dagu over RunWisp?
Can I wrap a multi-step pipeline in one RunWisp task?
Does RunWisp have any plans to add DAG support?
Can I run Dagu and RunWisp on the same machine?
What does RunWisp do that Dagu doesn't?
Is Dagu hard to set up?
How do notifications compare?
Which one uses less memory?
Not every job needs a DAG. Some just need to run on time.
If your tasks are independent, a flat TOML stanza per job is all you need. Install the binary, write a schedule, watch it land in the dashboard.
curl -fsSL https://get.runwisp.com | sh