n8n 自部署实录:一台便宜 VPS 撑起的自动化中枢

写在前面

前阵子我想给博客加个自动化:定时抓一批技术博客的更新,出现关键词就通知我。拿 Zapier 试,免费版一个月 100 次任务就见底;上 n8n 官方 Cloud,最便宜的档也要 20 多美元一个月,一年 240 美元,折人民币 1700 多,就为了跑几个通知流程。

我最后在一台 500 块买来的便宜 VPS 上把 n8n 自建了起来,到今天跑了三个月,没重启过一次。

这篇不是照着点的操作手册,是一份实录:自建到底值不值、我怎么把它跑起来的、花了多少、坑在哪。你看明白个中思路就行,不一定要逐行照抄。

这是上篇,讲部署。跑起来之后怎么用它搭自动化流程、怎么帮你赚钱,见下篇《n8n 实战:从 0 跑通你的第一个工作流》,会在本博客发布,发布后此处补上链接。

1. 先算账:自建到底值不值

n8n 官方 Cloud 起步约 20 美元一个月,一年就是 240 美元,折合人民币 1700 往上。这还只是单人档,要多人协作价格再翻。官网的定价页摆着这个价,截了一张放在下面:

n8n Cloud 定价页

一台入门 VPS 什么价?几百块买来,按年摊下来每个月几十块。我那台同时跑着 n8n、PostgreSQL 和一个 AI 网关项目,月成本摊薄后比 n8n Cloud 便宜一个数量级。

账面上自建完胜,但代价得说在前头:Cloud 是别人替你管运维,自建是你自己管。容器偶尔要升级、配置偶尔要调、哪天连不上了得你自己查。对个人和小团队来说,这点麻烦换每个月省下的订阅费,划算;如果你连 SSH 都不想碰,那还是老老实实交钱。

所以这篇适合的是后者:愿意花一晚上把它跑起来,之后基本不用管的人。

2. n8n 是什么,凭什么值得折腾

一句话:开源的可视化自动化工具,Zapier 的平替。在网页上把一个一个「节点」拖出来连成流程,让它自动跑:定时触发、抓数据、过滤、发通知,全都不用写代码。GitHub 上 100k 多 Star,自动化这个赛道里的开源第一梯队。

有个容易踩线的地方提前说:n8n 用的是 fair-code 授权(Sustainable Use License),不是标准开源协议。自己部署、公司内部用,都免费;但拿它包装成 SaaS 卖给别人用,不行。想接活赚钱的,先看清这条再动手,别等到做大了被人找上门。

3. 机器不用好,够用就够

n8n 很轻。我的机器是腾讯云入门款,2 核 4G 内存 60G 盘,上面同时跑着 PostgreSQL、n8n,还有一个别的项目,日常内存占用不到整机一半。跑个人和小团队的自动化,绰绰有余。

选型上不用纠结:2C4G 起步,1C2G 也能跑,留点余量更稳;它是定时干活的后台服务,不是给人天天刷的网站,带宽和延迟都不敏感;成本上入门 VPS 一年摊下来,远低于 n8n Cloud 三个月的订阅费。

4. 我是怎么把它跑起来的

默认你手上已经有一台装了 Docker 的机器、还有一个解析好的域名。这两步不细说,网上一搜一大把,不是这篇的重点。

重点在下面这份配置。n8n 官方推荐生产环境用 PostgreSQL 存数据,默认的 SQLite 升级时容易出幺蛾子。我把数据库和 n8n 写进同一个 Compose 文件,一条内部网络互联,密码和密钥单独放 .env,不进 git。

.env(和 compose 放同一目录,compose 会自动读它做变量替换):

POSTGRES_PASSWORD=换成一个强密码
N8N_ENCRYPTION_KEY=生成一串随机字符串,后面细讲
chmod 600 .env

Compose 文件:

services:
  postgres:
    image: postgres:16
    container_name: postgres
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - ./pgdata:/var/lib/postgresql/data
    networks:
      - n8n_network
    deploy:
      resources:
        limits:
          memory: 1G

  n8n:
    image: n8nio/n8n:2.20.6
    container_name: n8n_workflow
    restart: always
    depends_on:
      - postgres
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - GENERIC_TIMEZONE=Asia/Shanghai
      - N8N_TIMEZONE=Asia/Shanghai
      - N8N_TELEMETRY_DISABLED=true
      - WEBHOOK_URL=https://n8n.你的域名.com/
      - EXECUTIONS_DATA_PRUNING=true
      - EXECUTIONS_DATA_MAX_AGE=168
    volumes:
      - ./n8n_data:/home/node/.n8n
    networks:
      - n8n_network

networks:
  n8n_network:
    driver: bridge

几个关键配置点

下面这些是我当时特意处理过、也最容易踩坑的地方:

N8N_ENCRYPTION_KEY 是最高优先级的一行。n8n 用它加密你存在里面的账号密码、API Token。这串字符串丢了,所有已保存的凭据全部作废,而且救不回来。生成后找个密码管理器存好。别让 n8n 自动生成,自动生成的话密钥存在容器里,容器一删就没。

端口绑 127.0.0.1 而不是 5678:5678,差别很大。前者 n8n 只对这台服务器本机可见,公网碰不到;后者直接把 5678 敞在公网上,登录密码走明文 HTTP,谁都能试。绑了 127.0.0.1 之后,前面由 Nginx 挂上 HTTPS 证书代理出门,外面看到的只有带锁的域名。

WEBHOOK_URL 走域名加 HTTPS 之后必须设,否则工作流里 Webhook 节点生成的回调地址是错的,外部服务回调不进来。这个坑我第一次配的时候漏了,调了半天才发现地址不对。

EXECUTIONS_DATA_PRUNING=trueEXECUTIONS_DATA_MAX_AGE=168 是自动清理 7 天前的执行记录。不开这个,每跑一次流程数据库就胖一点,几个月后磁盘告警。时区两个变量都设成上海,否则定时流程的时区能让你怀疑人生。遥测关掉,服务器上跑什么不给别人汇报。镜像标签钉死在具体版本号,不用 latest,每次升级拉到什么版本全凭运气的事我不干。数据目录都挂出来,容器删了重建,库、流程和凭据都还在。

首次启动 n8n 可能会因为数据库还没就绪而退出重启一两次,restart: always 会自动拉起来,等它稳了就行,别慌。

那个 postgres 是哪来的

配置里 DB_POSTGRESDB_HOST=postgres 这行最容易让人犯嘀咕:postgres 是什么 IP?去哪查?答案是它既不是 IP,也不是机器的 hostname,而是 compose 里那个 postgres 服务的名字。

两个容器之间要能对话,得先在同一个网络里。compose 底部的 networks: n8n_network 建的就是这么一张私有虚拟网,类型是 bridge(Docker 的默认网络驱动)。n8n 和 postgres 都挂在它下面,等于插进了同一个交换机。

Docker 给这张网内置了一个 DNS:每个容器进网时,会用「服务名」和 container_name 注册成一个域名。n8n 想连数据库,内部发一次查询,问 postgres 是谁,Docker 就把 postgres 容器在这张网里的 IP 回给它。这个 IP 是 Docker 自动分配的(一般是 172.x 那一段),你不用管、也不用写死。哪怕容器重启 IP 变了,名字不变,连接照旧。

这里有个反直觉的坑:容器里的 localhost 指的是容器自己,不是宿主机,更不是另一个容器。所以你没法用 localhost 去连 postgres,那样会指向 n8n 自己。跨容器只能靠这张网里的名字来认人。

顺着这个逻辑,配置里的几处网络设置就都能对上了:n8n 的 ports: "127.0.0.1:5678:5678" 是另一回事,它是把宿主机的本机回环端口转发进容器,绑定在 127.0.0.1 意味着只有这台机器自己能访问,这是宿主机(以及上面的 Nginx)到达 n8n 的入口,和容器之间的私有网是两码事;postgres 没写 ports,说明它根本没向宿主机开门,数据库只在 n8n_network 内部可访问,外面连不到,这正是它安全的地方;Nginx 跑在宿主机上,走 127.0.0.1:5678 就能代理到 n8n,而 n8n 连 postgres 走的是内部的 postgres 名字,两条路径互不相干。

还有两种模式容易和上面混在一起,提前说清。

一种是 host 网络模式,写法是给容器加一行 network_mode: host

services:
  n8n:
    image: n8nio/n8n:2.20.6
    network_mode: host
    # 注意下面没有了 networks 声明,也用不着 ports 映射
    environment:
      - DB_POSTGRESDB_HOST=127.0.0.1

这种方式让容器彻底放弃自己的虚拟网络,直接套用宿主机的网络栈。几个行为和前面讲的 bridge 模式完全相反:容器里访问 127.0.0.1,看到的不是容器自己,而是宿主机的 127.0.0.1,宿主机上跑着什么、监听着什么端口,容器里都直接碰到;反过来也一样,容器里 n8n 监听的 5678,就等于宿主机在 5678 上监听,外面直接能连进来。

因为已经没有虚拟网了,compose 里的 ports: 映射在这里完全失效,写了也等于没写,端口直接落在宿主机。这听起来省事,另一头却要付出代价:Docker 内置的名字解析也跟着失效,前面靠 postgres 这个名字找到数据库的那套机制,在 host 模式下不复存在,多个容器之间不能用服务名互相找。

那 host 模式下 n8n 怎么连数据库?只能靠 127.0.0.1。但前提很苛刻:得让 postgres 也用 host 模式,并且它的 5432 监听到了宿主机的网络接口上,n8n 写 DB_POSTGRESDB_HOST=127.0.0.1 才连得通。可一旦 postgres 也走 host 模式,5432 就直接绑在宿主机所有网卡上,公网也能敲门。这正是 host 模式最容易被忽略的坑:为了容器互联方便,把数据库逼到裸奔。

bridge 和 host 两种模式摆在一起看会更清楚:

维度bridge(本文用)host
网络栈容器各自独立共用宿主机
外部怎么访问容器ports: 做端口映射端口直接落在宿主机,无需映射
容器之间怎么认人服务名自动解析只能靠 127.0.0.1
网络隔离有,容器互不干扰无,共享一张网
数据库的暴露面可关在网内只给 n8n 用被迫对宿主机所有网卡开放

host 模式不是没用,它适合另一类场景:对网络吞吐要求极高、想省掉端口映射那层开销的应用,或者是本身就是网络工具(抓包、建隧道、跑某些 VPN)需要在宿主机真实网卡上工作的程序。n8n 不在其列。本文用 bridge,换来的是数据库只活在容器私有网里、公网够不着;n8n 再用 127.0.0.1 端口映射这一层把入口收口到本机,由 Nginx 统一加证书出门。隔离、收口、可解释,这三件事 host 模式都给不了。

另一种是外部网络,就是我机器上的实际情况:我的 VPS 上数据库不是新起的,机器先跑着一个给数据项目用的 PostgreSQL,我就让 n8n 挂进那个已有网络直接连它,省掉一个数据库容器。做法是删掉 compose 里的 postgres 服务,n8n 的网络声明改成 external: true 指向已存在的网络名。一台机器上服务多了以后,这个思路能省不少内存,代价是几个项目共用一个库实例,重启维护要互相照应。

把锁挂上:Nginx 反代加 HTTPS

前面 n8n 只对本机开放,要让人从浏览器安全访问,得在前面架一层 Nginx,挂上 HTTPS 证书(我用 acme.sh 申请免费的,有通配符证书更省事,一条管所有子域)。反代配置注意这几行:

location / {
    proxy_pass http://127.0.0.1:5678;
    proxy_set_header Host $host;
    # WebSocket,n8n 编辑器实时刷新需要
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    # 工作流导入导出的包会超过默认 1M
    client_max_body_size 20m;
}

docker compose up -d 之后证书配好,浏览器打开 https://n8n.你的域名.com,第一步会让你创建 owner 账号,设好邮箱密码,部署就算完了。登录进去之后的工作流列表页大概长这样:

n8n 控制台工作流列表

(我截图时还在用 localhost:5678 直接访问,图里最下面那个浮层是自托管版的 AI Assistant 提示,不用管。)

顺带一句:如果这套东西只在你的内网或 VPN 里自用,把端口改回 "5678:5678" 直接 IP 访问也跑得起来,省掉域名和反代。我最初就是这么用的,后来要接公网回调的 Webhook 才把锁挂上。

5. 跑了三个月,真实开销

docker stats 现场实测:

项目实测
n8n 容器内存420M
PostgreSQL 容器内存190M
整机(含另一个 AI 网关项目)2G / 3.7G,还剩 1.7G 可用
磁盘(整机)20G / 59G
连续运行3 个月,0 次人工重启

我这台的数据库还兼着别的项目的活,190M 是共摊的账。就算你全新起一个,内存量级也差不多。n8n 本体吃 400 兆上下,一台 2C4G 的机器想再塞两三个轻量服务都有余地。机器成本摊到每个月,比 n8n Cloud 便宜一个数量级。第一节那笔账,到这里有了实打实的佐证。

6. 踩过的坑

加密密钥没备份,这是最高优先级的坑,前面说了,丢了凭据全没,且救不回来。

直接 pull 升级,n8n 升级偶尔会带数据库结构变更,先 pg_dump 备份再升级,翻车了能回滚。我吃过一次升级后库结构对不上的亏,从此升级前必备份。

镜像标签用 latest,升级节奏不受你控制,钉死版本号,想升级时再手动改。

忘了设执行记录清理,数据库无限膨胀,见第 4 节配置。

凭据明文进工作流导出文件,导出的 JSON 里凭据是加密的,但手动把 Token 填进节点参数里再导出分享,会漏。凭据一律走 n8n 的 Credentials 管理。

密钥写进 compose 文件,compose 会进 git、会被顺手备份到各种地方。密钥和密码放 .envchmod 600,compose 里只留变量名。

数据库端口映射到公网,除了招扫描没有第二个作用,容器网络里名字互通就够了。我早年图省事把 5432 映射出去,日志里天天是陌生 IP 的连接尝试,后来老老实实关掉了。

7. 下篇预告

部署完你得到的只是一个空壳子,n8n 的价值在工作流里。下篇我带你从 0 搭一个真实场景:定时监控 RSS,关键词过滤,然后自动通知。包括第一次运行、故意弄失败看报错、跑一晚看稳定性,以及这套东西怎么帮你赚到钱。

延伸阅读:Outline + Pocket ID 自部署:国内自建知识库完整踩坑记


我在写一本《不会写代码,怎么用 AI 做出你的第一个产品》,专讲普通人怎么用开源工具和 AI 做出能赚钱的东西,首发 9.9 元。这篇文章里的思路手册里都有展开,感兴趣的话可以搜一下这本书(链接待补充)。

← 返回文章列表