# Workflows

> Run an on-demand container task with run history, timeouts, and bounded retries.

Source: https://nexthomelabs.com/docs/services/workflows
Markdown: https://nexthomelabs.com/docs-md/services/workflows
Slug: services/workflows
Section: Create
Last updated: 2026-08-30
Reading time: 5 min read

---

# Workflows

A **Workflow** runs a container task **on demand** — not continuously like a [Background Worker](/docs/services/background-workers), and not on a fixed cron cadence like a [Cron Job](/docs/services/cron-jobs). Use it for bounded work that should have a **recorded run history**, a **timeout**, and **retry limits** instead of a permanently running process: a per-invoice render, a per-document export, a reconciliation pass, a data import that handles unexpected size by restarting rather than by staying online indefinitely.

Because each invocation is an isolated container run, retrying is explicit, bounded, and inspectable — the history records the captured exit status and application text per invocation.

## When to use — and when not to

**Use a Workflow when:**

- The unit of work has a natural finish line — a document produced, a dataset exchanged, an entity migrated — and there are many distinct invocations rather than a persistent queue reader.
- The run needs a formal **timeout** (kill the step after a wall-clock limit) and a **retry limit** (fail after N attempts) so a stuck input cannot stall the whole domain.
- History matters more than availability: auditors, stakeholders, or teammates need to see "which input was processed in run #37 and how long did it take?"

**Do not use a Workflow when:**

- The process must **stay alive continuously** consuming from a queue or stream — use a [Background Worker](/docs/services/background-workers).
- The process fires on a fixed **UTC cadence** with no per-input trigger — use a [Cron Job](/docs/services/cron-jobs).
- The need is an HTTP service — use [Web Service](/docs/services/web-services).

> [!INFO]
> Workflows sit between a Worker and a script: they have the control surface of a deployment (image, command, environment, timeout, retry) but the execution shape of a finite task. If "running for hours until stopped" sounds correct, lean Worker; if "run once with a watch timer" sounds correct, lean Workflow.

## Configure the task

Choose **Workflow**, provide the **container image and command** required for the task, then set the **task timeout** and **retry limit**. Each field has a distinct responsibility:

- **Container image** — the image that contains the task program and its dependencies. This is commonly an image pushed from external CI rather than the workspace’s repository build; confirm the tag or digest is the one you intend to run.
- **Command** — the invocation that defines what this workflow run *is*. For example `python scripts/render_invoice.py --id $INVOICE_ID`, `node scripts/export.js`, or a binary plus arguments. Keep the command **deterministic**: the same image plus the same command plus the same inputs should produce the same logical outcome, not timing-dependent jitter.
- **Timeout** — the wall-clock limit after which the platform terminates an unresponsive run. Choose it for the worst plausible size of a healthy invocation (for example "rendering the largest invoice" or "importing the largest batch size your application allows") rather than for the average case.
- **Retry limit** — the bounded number of retries after transient failures. Useful for temporary provider or network issues; harmful as a loop over bad inputs, so keep the limit small and the failure-classification inside the command honest.

Keep the command **deterministic** and make it **safe to retry** where possible. An import that can safely be restarted from the last externally observable checkpoint (a remote offset, a deterministically derived partition key) is less harmful to rerun than one that duplicates side effects per retry.

> [!TIP]
> Encode input narrowly. A workflow task that takes an exact invoice, document, or job ID is easier to rerun and retry than one that implicitly scans an unbounded range. Prefer `workflow --id=123` over "workflow scans for all pending jobs" when per-entity history matters.

## Review runs

The **workflow screen records each invocation and its outcome** — whether it succeeded, timed out, or hit the retry ceiling — with the exit status and application output captured per run. **Inspect a failed run before retrying** so a configuration or input problem is not repeated automatically.

What to look for when the last run failed:

- **Exit status** — non-zero signals "the task reported failure," not "the platform did." Distinguish a deterministic bug in the input (which should be fixed before any retry) from transient infrastructure or provider throttling (where a retry may genuinely help).
- **Application output** — the task’s own stdout/stderr plus the platform’s bookkeeping. The first error line in the earliest anomalous span is usually the root cause — later retries and wrapper timeout logs are often consequences.
- **Timeout vs crash** — a timeout implies the task kept running but never completed within the limit; a crash often appears as an immediate non-zero exit with a short duration. Fix them differently: a timeout wants a longer timeout or a leaner input partitioning; a crash wants a code or environment repair.

> [!WARNING]
> Do not blindly retry a failed run with invalid input. A retry storm over a malformed document accumulates the recorded retry budget while producing only repeated failures.

## Related documentation

- [Background Workers](/docs/services/background-workers) — continuous consumers with a "stay alive" model rather than per-run history.
- [Cron Jobs](/docs/services/cron-jobs) — scheduled, clock-driven finite runs in UTC where the cadence is the trigger, not the demand.
- [Logs](/docs/operations/logs) — reading application output and stage information for a task that does not serve HTTP.
- [Deployments](/docs/deployments) — observing the lineage of the workflow’s configuration alongside domain runs.
- [Services Overview](/docs/services) — placing Workflows among the other service types before committing to the model.

