Once you have a workstation that can hold a 70B model in memory, the next question is what to point at it. An inference server on its own is a curiosity. An inference server that a scheduled workflow calls every morning is infrastructure.

This is the plumbing between those two states: getting n8n to talk to a local OpenAI-compatible endpoint, with no API key, no egress, and no per-token bill. Everything here matches the Local LLM RSS Digest template in our workflow library, so you can read the finished JSON alongside the explanation.

The endpoint is the easy part

Lemonade Server, vLLM, llama.cpp's llama-server, and an Ollama proxy all expose the same thing: POST /v1/chat/completions taking a model, a messages array, and the usual sampling parameters. That compatibility is the whole reason this is straightforward — n8n does not need a dedicated node, because a plain HTTP Request node already speaks the protocol.

The body the digest workflow sends is exactly what you would send to any OpenAI-compatible server:

{
                  "model": "local-model",
                  "messages": [
                    { "role": "system", "content": "You write concise technical digests." },
                    { "role": "user", "content": "{{ prompt }}" }
                  ],
                  "temperature": 0.3
                }
                

And the response comes back in the same shape, which is why the extraction step is a one-liner:

{{ $json.choices?.[0]?.message?.content || $json }}
                

The optional chaining matters. When the call fails, or the server returns an error object instead of a completion, you get the raw response in your output field instead of undefined propagating silently through three more nodes.

The hard part is networking

This is where most first attempts die, and the error message is never helpful.

If n8n runs in Docker and your model server runs on the host, localhost inside the n8n container is the container, not your machine. The model server is not there. You get a connection refused that looks exactly like the model server being down.

The workflow template uses:

http://host.docker.internal:8080/v1/chat/completions
                

host.docker.internal resolves to the host from inside a container. On Docker Desktop it works out of the box. On plain Docker under Linux you have to add it explicitly:

services:
                  n8n:
                    image: docker.n8n.io/n8nio/n8n
                    extra_hosts:
                      - "host.docker.internal:host-gateway"
                

If both n8n and the model server are in the same Compose project, skip all of this and use the service name — http://lemonade:8080 — which is cleaner and does not depend on host networking at all. The Docker Compose builder will lay out either arrangement.

One more thing to check: a model server bound to 127.0.0.1 is unreachable from any container regardless of DNS. It has to listen on 0.0.0.0 for a container to reach it. That single flag accounts for a surprising share of "the endpoint is up but n8n cannot see it."

Smoke test before you build

The Manual Prompt → Lemonade template exists entirely for this. Five nodes: a manual trigger, a hardcoded prompt, one HTTP call, one extraction. Import it, click Test workflow, and you get a definitive answer to "can this n8n instance reach my model" in about two seconds.

Do this before you build anything real. Debugging a six-node scheduled workflow that was never going to connect is a genuinely bad afternoon, and it is entirely avoidable.

Timeouts are not the default you want

n8n's HTTP Request node defaults to a timeout far shorter than a local model needs. A 70B model producing a few hundred tokens on a first, uncached request can take a while — and unlike a hosted API, there is no autoscaler hiding the cold start from you.

Set the timeout explicitly in the node's options, generously. Thirty seconds is not enough for a long summary; two to five minutes is more realistic for large models on a batch job that runs at 07:00 and has nowhere to be. Then bound the work itself so the timeout is a backstop rather than a load-bearing part of the design:

  • Cap max_tokens so a runaway generation cannot hold the queue
  • Trim the input before it reaches the prompt — feeding a whole RSS document into a model with an 8K context silently truncates the interesting part
  • Keep temperature low for summarization work; the digest template uses 0.3

Treat the model like any other flaky dependency

A local model removes the API key, the rate limit, and the bill. It does not remove failure. The server can be restarted, the GPU can be busy with something else, and a request can come back with a response your parser did not expect.

The same reliability patterns from the rest of the workflow library apply directly:

  • Set onError: continueRegularOutput on the model call so a failure becomes a branch you can route, not a dead execution
  • Validate the output before it is committed anywhere — an empty or truncated summary is still a "successful" 200 response
  • Give the workflow somewhere to put the failure. A digest that silently stops arriving is worse than one that emails you an error

Why bother

The digest workflow reads a feed, summarizes it, and formats the result. Every one of those steps could run against a hosted API for a few cents a month, and it would be less work.

What you get instead is a pipeline where the content never leaves the building, the cost is fixed at the electricity you were already paying, and nothing breaks when a provider deprecates a model or changes its pricing. For a daily digest that is a nice-to-have. For anything touching customer data, contracts, or source code, it is the entire point.

The digest template is on the workflows page, the hardware setup is here, and if you are picking a schedule, the cron generator will show you the next five run times before you commit.