DEV Community

Great Sage
Great Sage

Posted on

Most self-hosted n8n deploys cost more than they need to (a minimum-viable config)

Most self-hosted n8n deploys cost more than they need to, and it's not because n8n itself is expensive to run

If you've followed one of the common "self-host n8n" guides, there's a good chance you ended up with four services running: n8n itself in queue mode, a separate worker process, Redis to pass jobs between them, and Postgres for storage. That's the setup n8n's own docs recommend for production, and it's the right call once you've got multiple people triggering workflows concurrently and you need executions to survive a container restart mid-job.

It's also complete overkill if you're the only person using it, or a small team running a few dozen workflows a day. You're paying for four services (CPU + RAM on each, plus Postgres storage) to solve a concurrency problem you don't have yet.

What actually changes between "queue mode" and regular mode:

  • Regular mode: n8n runs executions in the same process that serves the UI/API. One container, one thing to monitor, no Redis in the loop.
  • Queue mode: the main process only enqueues jobs, one or more separate workers pull from Redis and execute them. Worth it once you're running enough concurrent workflows that a single process becomes the bottleneck, or you need to scale workers independently from the UI.

Most solo/small-team setups never hit that ceiling. If you're not sure whether you have, you probably don't.

The other place hosting guides overspend: Postgres. n8n supports SQLite as a first-class backend (DB_SQLITE_POOL_SIZE is a real, documented config option, not a hack), which means no separate database service, no connection string to manage, and one less thing that can fall out of sync with your app container. The tradeoff is honest: SQLite doesn't want a lot of concurrent writers, so if you're running heavy parallel executions that all write to the DB simultaneously, Postgres is the better call. For typical automation workloads — a webhook fires, a workflow runs, it writes a handful of rows — it's plenty.

So the minimum viable n8n, stripped to what most people actually need:

n8nio/n8n:1.121.3
Enter fullscreen mode Exit fullscreen mode
  • Regular mode, no queue, no Redis, no worker process.
  • SQLite, persisted on a volume at /root/.n8n/ — workflows, credentials, and execution history all survive a redeploy.
  • N8N_ENCRYPTION_KEY set once and kept stable (this one matters: if it changes, existing saved credentials become undecryptable — the value has to persist alongside the SQLite file, not get regenerated per deploy).
  • N8N_PROXY_HOPS=1 set correctly for whatever's terminating TLS in front of it (skip this and webhook URLs / IP logging silently misbehave behind a reverse proxy).

That's one container instead of four, and it's the config I run.

I maintain a one-click Railway template that deploys exactly this — full disclosure, I get a kickback if you deploy through it: https://railway.com/deploy/n8n-cheapest-to-run?referralCode=Z1xivh&utm_medium=integration&utm_source=template&utm_campaign=devto

If you'd rather not go through a referral link, the same image runs anywhere Docker does — it's just n8nio/n8n:1.121.3 with the env vars above and a volume mounted at /root/.n8n/. n8n's own docs have a plain docker-compose for the SQLite/regular-mode setup if you want to wire it up yourself: https://docs.n8n.io/hosting/configuration/supported-databases-settings/#sqlite

Worth being honest about when this setup stops being enough: once you've got multiple people triggering workflows concurrently, or a handful of long-running workflows that would block a single process, that's the point to switch to queue mode with Postgres and workers, not before. Don't provision for the traffic you don't have yet.

Top comments (0)