Workflows
Run a Docker image to completion — on a cron schedule or on demand — and keep a record of every run with its exit code and output. No repository or build step involved.
What a workflow is
A workflow is one Docker image, an optional command, environment variables, an optional cron schedule and a timeout. Each time it fires, HostStack pulls the image, starts a container, waits for it to exit and records the result as a run. Workflows belong to a project and an environment, and their containers join that project's private network, so a run can reach your services on their internal addresses — see Networking.
| Use it when | |
|---|---|
| Workflow | The job is already an image — a vendor tool, curl, a CLI you publish yourself — and should run now and then, finish, and leave a record. |
| Cron service | The job is code in your repository. A cron service is built from the repo like any other service and runs its start command on a schedule — see Cron & uptime. |
| Service | The work is continuous: an HTTP server or a worker that never exits. |
Create a workflow
Open Workflows
In the dashboard sidebar, under Workloads, click Workflows. If your team has more than one project, pick the project first.
Fill in the form
Click New Workflow and set a Name and a Docker image, such as
curlimages/curl:latest. Optionally set a Command, a Schedule in cron syntax (leave it blank for a workflow you only run by hand), a Timeout in seconds, and environment variables asKEY=VALUElines.Create it and run it once
Click Create Workflow, open it, and click Run now to check it works before the schedule fires. The run appears under Recent runs; click it to see its status, timings, exit code and output.
The form splits Command on spaces, so it can't express an argument that contains a space. Use the HTTP API for that — it takes the command as an array. The command replaces the image's CMD; the image's ENTRYPOINT still runs. That is why curlimages/curl, whose entrypoint is curl, takes just the arguments: -fsS https://api.example.com/health.
Trigger a run
A workflow with a schedule fires on it. Schedules are standard 5-field cron expressions (*/5 * * * *), evaluated in UTC; a 6-field expression is read with a leading seconds field. Any workflow — scheduled or not — can also be run by hand with Run now, or through the API. A trigger can pass extra environment variables, which are merged over the workflow's own for that run only:
curl -X POST \
https://hoststack.dev/api/teams/$TEAM_ID/workflows/wf_.../trigger \
-H "Authorization: Bearer hs_live_..." \
-H "Content-Type: application/json" \
-d '{"env": {"DRY_RUN": "true"}}'The response is 202 with the new run, whose publicId starts with wfr_. Poll GET /api/teams/:teamId/workflows/runs/:runId until its status is terminal. There is no CLI command, SDK method or MCP tool for workflows; the dashboard and the HTTP API are the whole surface.
How a run executes
Every fire — scheduled, manual or retry — creates a run in queued and hands it to a HostStack machine. The machine pulls the image, starts the container on the project network, and kills it if it is still running when the timeout is up. The timeout clock starts once the image is pulled.
| Status | Meaning |
|---|---|
| queued | Created, not yet picked up by a machine. |
| running | A machine has accepted the run and is pulling the image or running the container. |
| succeeded | The container exited with code 0. |
| failed | The container exited non-zero, the image couldn't be pulled or started, no machine was available, or the run never reported back (see below). The reason is in the run's error message. |
| timed_out | The container was still running at timeoutSec and was killed. Its exit code is recorded as -1. |
| cancelled | You cancelled it with Cancel or the API while it was queued or running. |
A run that is still queued or running two minutes after its timeout — because the machine running it went away — is marked failed with the message “Agent did not report completion within timeout window”. That check runs every minute.
The run sandbox
Each run container gets the same sandbox as a cron job: 512 MB of memory, one CPU, at most 256 processes, a read-only root filesystem, and writable /tmp and /var/tmp of 64 MB each that can't hold executables. Write scratch files to /tmp.
Retries
To run a finished run again, open it and click Retry, or call POST /api/teams/:teamId/workflows/runs/:runId/retry. The retry is a new run with trigger retry and its attempt number one higher, and it uses the workflow's current image, command and environment — not any extra variables the original trigger passed. Only a run in a terminal state — succeeded, failed, cancelled or timed_out — can be retried.
Failure notifications
When a run ends failed or timed_out, HostStack sends the workflow.failed event to your team's notification channels — Slack, Discord or email. The message is titled “Workflow run failed” or “Workflow run timed out”, names the workflow with the error message or exit code, and links to the workflow in the dashboard. workflow.failed is one of the critical events a new channel subscribes to by default.
Runs that fail before they start — no machine available — and runs marked failed because they never reported back don't send it. Cancelled runs don't either.
Configuration reference
The body of POST /api/projects/:teamId/:projectId/workflows. PATCH takes any subset of the same fields except projectId and environmentId.
projectId numberrequiredThe project's numeric id. This value, not the one in the URL, decides where the workflow is created, so keep the two the same.
environmentId numberThe environment to run in. Omit it for the project's production environment.
namestringrequired1–100 characters, unique among the environment's workflows.
imagestringrequiredAn image reference, up to 512 characters, pulled without credentials — so it must be public.
ghcr.io/…and other registries work if the image is public.commandstring[] | nullUp to 64 arguments of 1–1,024 characters each. Replaces the image's
CMD.nullor omitted uses the image's own.envRecord<string, string>Names up to 128 characters, values up to 4,096. Stored unencrypted.
schedulestring | nullA cron expression, 5 to 100 characters.
nullor omitted means manual runs only.timeoutSec number1 to 86,400 (24 hours).
Default:
3600maxRetries number0 to 10. Stored, but not acted on yet — see Retries.
Default:
0
HTTP API
Send an API key as Authorization: Bearer hs_live_.... Reads work for any team member. Everything that changes something needs the member role or above and, with an API key, a key with full access. :workflowId is the workflow's wf_ id and :runId the run's wfr_ id.
| Method and path | What it does |
|---|---|
| GET /api/projects/:teamId/:projectId/workflows | List a project's workflows. Paged with page and perPage (default 25); filter with environmentId. |
| POST /api/projects/:teamId/:projectId/workflows | Create a workflow. Returns 201. |
| GET /api/teams/:teamId/workflows/:workflowId | Get one workflow. |
| PATCH /api/teams/:teamId/workflows/:workflowId | Change any of its fields. |
| DELETE /api/teams/:teamId/workflows/:workflowId | Delete it. Its schedule stops; a run already in progress is not cancelled. |
| POST /api/teams/:teamId/workflows/:workflowId/trigger | Start a run, with optional { "env": { … } }. Returns 202. |
| GET /api/teams/:teamId/workflows/:workflowId/runs | List its runs, newest first. Paged like the workflow list. |
| GET /api/teams/:teamId/workflows/runs/:runId | Get one run. |
| GET /api/teams/:teamId/workflows/runs/:runId/logs | Get its stdout and stderr lines. |
| POST /api/teams/:teamId/workflows/runs/:runId/cancel | Cancel a queued or running run. |
| POST /api/teams/:teamId/workflows/runs/:runId/retry | Start a new run from a finished one. Returns 202. |
trigger and retry are rate limited to 30 requests a minute for each workflow or run, counted per user — or per IP address for an API key.
Next steps
Was this page helpful?