Self-host n8n on a VPS with Docker A developer provides a step-by-step guide for self-hosting n8n on a VPS using Docker, emphasizing cost savings, data control, and security. The setup involves installing Docker, configuring a docker-compose.yml with n8n and Postgres, and accessing the instance via SSH tunnel or reverse proxy. The guide recommends a 2 GB VPS for most workloads and highlights the benefits of flat monthly pricing versus per-execution cloud seats. n8n is the kind of tool you start using lightly and then quietly route half your operations through. At which point "it's running on someone's cloud seat, metered per execution, with my API keys living on their servers" starts to feel less great. Self-hosting fixes all three — flat cost, no execution cap, and your keys stay on a box you own. With Docker it's a fifteen-minute job. Honest numbers first, so you don't over- or under-buy: n8n isn't CPU-hungry at rest; it spikes during runs. A 2-core box is fine for most setups. More on matching specs to workload in the sizing guide https://dev.to/blog/vps-sizing-for-ai-agents . On a fresh Ubuntu/Debian box, install Docker: curl -fsSL https://get.docker.com | sudo sh Make a folder and a docker-compose.yml — n8n with a persistent volume and Postgres: services: n8n: image: docker.n8n.io/n8nio/n8n restart: always ports: - "127.0.0.1:5678:5678" environment: - N8N HOST=n8n.yourdomain.com - N8N PROTOCOL=https - WEBHOOK URL=https://n8n.yourdomain.com/ - DB TYPE=postgresdb - DB POSTGRESDB HOST=db - DB POSTGRESDB PASSWORD=change-me volumes: - ./n8n-data:/home/node/.n8n depends on: db db: image: postgres:16 restart: always environment: - POSTGRES PASSWORD=change-me - POSTGRES DB=n8n volumes: - ./db-data:/var/lib/postgresql/data sudo docker compose up -d Two things worth pointing out: the volumes n8n-data , db-data are what keep your workflows alive across restarts and upgrades — don't skip them. And n8n is bound to 127.0.0.1 , not 0.0.0.0 — it's not exposed to the internet directly. That's deliberate; the next step handles access safely. 127.0.0.1:5678 . This is where a ssh -L 5678:127.0.0.1:5678 user@server , then open localhost:5678 . Works fine on a NAT VPS.n8n holds your API keys and credentials, so the box must be tight: do the security checklist https://dev.to/blog/secure-new-vps-checklist SSH keys, firewall, no password login before you put real credentials in. restart: always in the compose file already means Docker brings n8n back after a crash or reboot — that's your uptime handled. Be honest with yourself about volume. If you run automations constantly, self-hosting wins on cost flat vs per-execution and removes any workflow/run limits. If you trigger a few flows a month, a hosted seat is less hassle. But the control argument stands regardless: your workflows, your data, your keys — on your server, not rented. For most people who got serious about n8n, that's the deciding factor. A 2 GB box, a compose file, a domain or a tunnel , and you're running your own automation hub — payable in crypto with no KYC https://dev.to/blog/crypto-vps-no-kyc , live in minutes. Around 2 GB is the comfortable sweet spot for n8n plus its Postgres database and normal workflows. It runs in 1 GB if workflows are light, but you'll feel it during bigger runs. Heavy parallel executions or large payloads — go to 4 GB. It's by far the easiest way — one compose file gives you n8n, a database and persistent storage, and upgrades are a single pull. You can install via npm instead, but Docker saves you the dependency and upgrade headaches. If you run a steady stream of automations, yes — a flat monthly VPS beats per-execution pricing, and there's no workflow or execution cap. For a handful of runs a month, a hosted seat may be simpler. The other reason people self-host is control: your data and API keys never leave your server. For OAuth-based nodes Google, etc. and clean HTTPS, yes — point a domain at the server and put n8n behind a reverse proxy with a certificate. For purely internal use you can reach it over an SSH tunnel without a domain. Internal/SSH-tunnel use works on a NAT box. If you want a public URL with your own domain and HTTPS needed for some OAuth callbacks and webhooks , a dedicated-IP plan is the cleaner fit since you control all ports. Originally published at eqvps.com.