The hard part of automating video creation is not the generation. It is that renders take minutes, HTTP requests do not, and every naive version of this workflow dies in the gap between those two facts.
There is no shortage of workflow templates that turn a prompt into a published video. They demo beautifully and then fall over in production for reasons that have nothing to do with the creative part: a render API that returns before the file exists, a workflow that times out at ninety seconds, a retry that produces two videos instead of one, and intermediate files written to a container filesystem that is gone by morning. Those are the parts worth designing.
Table of contents
- The shape of a video pipeline
- Why the naive workflow times out
- Polling an async render job properly
- Where the intermediate files live
- Idempotency and retries
- Running n8n somewhere it will not fall over
- How this fits the rest of the stack
- FAQ
The shape of a video pipeline
Strip away the specific services and almost every automated video workflow is the same six stages.
- Trigger. A schedule, a webhook, or a new row in a sheet acting as the queue of things to make.
- Script and prompt generation. Turning a topic into a script, scene list, and image prompts.
- Asset generation. Images, video clips, and voiceover, each from a different API, each asynchronous.
- Assembly. Stitching the assets into a finished file, usually through a rendering API.
- Publishing. Uploading to one or more platforms with a title and description.
- Bookkeeping. Writing the result back so the same item is not processed twice.
Stages two, five, and six are ordinary API work. Stages three and four are where the timeouts live, because they are the ones that take minutes rather than seconds. Design for those and the rest follows.
Why the naive workflow times out
The obvious build is a straight line of HTTP Request nodes: call the render API, take the URL from the response, upload it. It works in testing with a five-second clip and fails on anything real.
Two separate limits are involved and they get confused with each other. The first is the HTTP request timeout on the node itself, which is measured in seconds and applies to a single call. The second is the overall execution timeout on the workflow, which is what kills a run that has been alive for twenty minutes waiting on something.
There is a third if the workflow is webhook-triggered. A webhook caller is waiting on a response, and most callers give up long before a render finishes. A webhook that triggers a long job should respond immediately and let the work continue behind it, not hold the connection open.
The general rule: never let a workflow’s runtime be bounded by how long an external render takes. Break the wait into a loop of short polls instead of one long call, and the timeouts stop being your problem.
Polling an async render job properly
Most video generation APIs are asynchronous by design. You submit a job, get an id back immediately, and poll a status endpoint until it reports done. The n8n shape for this is a Wait node feeding a status check feeding an IF node that loops back.
// Code node after the status check.
// Decide whether to keep waiting, finish, or give up.
const status = $json.status;
const attempts = ($json.attempts ?? 0) + 1;
if (status === 'succeeded') {
return [{ json: { done: true, url: $json.output_url } }];
}
if (status === 'failed' || attempts > 60) {
throw new Error(`render ${$json.id} ended as ${status} after ${attempts} polls`);
}
return [{ json: { done: false, id: $json.id, attempts } }];
Three things make this robust rather than merely working. Carry an attempt counter so a job stuck in processing forever eventually errors instead of looping until the execution timeout kills it with no useful message. Treat an explicit failed status as an error immediately rather than polling a dead job sixty more times. And throw with the job id in the message, because that is what you will want when you read the execution log a week later.
Set the Wait interval to something proportionate. A render that takes three minutes does not need a poll every five seconds; twenty seconds is plenty and costs a fraction of the API calls.
Where the intermediate files live
A video pipeline produces a lot of bytes on the way to the finished file: generated images, individual clips, a voiceover track, and the assembled result. Where those live matters more than it seems.
- Not in the container filesystem. Anything written to local disk in a containerised n8n is lost on the next restart or deploy. That includes the finished video if you downloaded it before uploading.
- Not held as binary data through the whole workflow. n8n keeps binary data in memory by default, and a workflow carrying several video files through a dozen nodes is a memory spike waiting to happen. Pass URLs between stages and download only at the moment you upload.
- In object storage, with a lifecycle rule. Generated intermediates are worth keeping for a few days for debugging and worthless after that. A rule that deletes them automatically stops the bill growing quietly.
- Referenced in a database row, not just in the execution. If the run fails at publishing, you want to resume from the assembled file rather than regenerate everything.
That last point is the one that separates a workflow you can operate from one you rerun from scratch every time something goes wrong.
Idempotency and retries
Retries are the feature most likely to cost you money in a generation pipeline, because a retried render is a second billed render and often a second published video.
The pattern that works is a status column on whatever holds your queue, written at each stage rather than only at the end.
- Mark the row as processing the moment the workflow picks it up, so a second run started by an overlapping schedule skips it.
- Write the render job id to the row as soon as you have it. If the workflow dies mid-poll, the next run can resume polling that job instead of submitting a new one.
- Write the assembled file URL before the publishing step. A publish failure then costs a retry of the upload, not of the entire generation.
- Only mark done after the final publish confirms, and store the returned platform id so you can tell a duplicate from a fresh post.
Turn off automatic retries on the nodes that submit generation jobs, and leave them on for the idempotent ones such as status polls and metadata writes. A blanket retry policy across a pipeline that spends money per call is not a safety net, it is a multiplier.
Running n8n somewhere it will not fall over
A pipeline like this changes what n8n itself needs. Long executions, binary handling, and scheduled runs that overlap all push on the same resources.
- Persistent Postgres, not the default SQLite file. Execution history, credentials, and workflow definitions belong in a real database that survives a container being recreated.
- Memory headroom for binary data. Even with the pass-URLs-not-files discipline, some nodes buffer. An instance sized for a light webhook workflow will not survive a video pipeline.
- A prune policy on execution history. Workflows that run hourly and store full execution data fill a database faster than anyone expects. Set a retention window deliberately.
- Timezone and schedule sanity. Overlapping scheduled runs are a common source of duplicate work, and they are easy to miss when the run takes longer than the interval.
On RunxBuild, n8n is a managed tool with its own plan, custom domains, environment variables, autoscaling, and logs, and the Postgres instance behind it is a managed database on the same platform. The Basic plan at $6 with a database beside it covers a pipeline of this shape comfortably; heavier assembly work is a case for moving up the ladder or letting autoscaling handle the bursts.
How this fits the rest of the stack
An automated video pipeline is a small distributed system wearing a workflow editor. The costs are the automation runtime that has to stay up, the database holding its state, and the storage for everything it produces on the way. Seeing those as three numbers rather than one is what makes it possible to decide whether the pipeline is worth running at the volume you have in mind, and the RunxBuild hosting calculator breaks them out that way. The n8n instance, its managed Postgres, and the storage attached to it are separate line items you can move independently.
Useful related references:
- n8n Use Cases: What It Is Genuinely Good At, and What It Is Not
- n8n Respond to Webhook Node: Controlling What the Caller Actually Gets Back
- n8n System Requirements: Smaller Than You Think, Until They Are Not
- Services on RunxBuild
FAQ
Why does my n8n video workflow time out?
Because a render takes minutes and the workflow is waiting on it in a single HTTP call. Two limits apply: the per-node request timeout and the overall workflow execution timeout. Replace the long call with a Wait node and a status poll loop so no single request is long-running, and the execution stays alive on short calls instead.
How should I handle an async render API in n8n?
Submit the job, take the id from the immediate response, then loop a Wait node into a status check with an IF node deciding whether to continue. Carry an attempt counter so a stuck job errors out with a useful message, and treat an explicit failed status as an error rather than polling a dead job.
Where should generated video files be stored?
In object storage, not the container filesystem, which is wiped on restart or redeploy. Pass URLs between workflow stages and download the file only at the point you upload it, so n8n is not holding several videos in memory at once. A lifecycle rule that deletes intermediates after a few days keeps storage costs flat.
How do I stop retries producing duplicate videos?
Track state per item in a database row rather than relying on the execution. Mark it processing on pickup, store the render job id as soon as you have it so a resumed run polls the existing job instead of submitting a new one, and only mark it done after publishing confirms. Disable automatic retries on the nodes that submit paid generation jobs.
What does n8n need to run a pipeline like this?
A Postgres database rather than the default SQLite file, so workflows and execution history survive restarts; enough memory that binary handling does not exhaust the instance; and a prune policy on execution history, which grows quickly on a workflow that runs hourly. Check that scheduled runs cannot overlap if each one takes longer than the interval.