用 Docker Compose 部署 n8n:持久化、时区、重启策略一次配对
docker run 那条命令我前后存过三份记录。第一份只有端口,第二份加了卷,第三份加了时区和密钥,三行拼起来两百多个字符,改一个参数要整条重建,重建完还得回想刚才到底改了哪。
compose 把这些参数搬进一个文件。文件能对比、能进版本库、能一次看全。代价是改完必须重建容器,光 restart 不读新值——这条我踩过,第二节后面有实测。
这篇给一份十几行的 compose 文件,把三件事一次定死:数据放哪、几点算几点、进程挂了会不会自己回来。顺手记三个我在 2.43.7 上真撞到的坑:只设 TZ 时区还是纽约、改完环境变量 restart 不生效、中途加加密密钥直接把容器打成重启循环。
前提是机器上装了 Docker Engine 和 Compose v2,docker compose version 能出版本号就行。完全没碰过 n8n 的先看安装那篇,Docker 是什么还没搞清的看这篇。
- 为什么不继续用 docker run
- 一份够用的 compose 文件
- 第一次启动,什么时候才算真好了
- TZ 和 GENERIC_TIMEZONE 不是一回事
- 卷里到底装了什么
- 重启策略:什么情况它会自己回来
- 加密密钥,我在这一步把实例弄崩过
- 五条自检
- 几个会遇到的报错
- 安全提醒
为什么不继续用 docker run
官方 Docker 安装页给的还是 docker run 一行流。那不是错,它面向的场景是"先跑起来看看"。跑通之后你会开始问另外三个问题:数据放哪、时区对不对、机器半夜重启了它回不回来。这三个问题在 docker run 里也能回答,答案散在命令行历史里。
我见过一次真事故:同事换机器,把旧的 docker run 命令抄过去,漏了 -v。容器起来是崭新的,界面让他重新注册,他以为 n8n 坏了,重装了两遍。数据一直在旧机器的卷里躺着,没人去看。
写进文件就没这个空间。少一行,docker compose config 一眼能看出来。
另外一句:别用 latest。n8n 基本每周发一个小版本,latest 哪天变了,你早上起来行为就不一样。镜像标签写死到具体版本,升级自己挑时间。
一份够用的 compose 文件
services:
n8n:
image: n8nio/n8n:2.43.7
container_name: n8n
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
- TZ=Asia/Shanghai
- GENERIC_TIMEZONE=Asia/Shanghai
- N8N_ENCRYPTION_KEY=换成openssl生成的48位串
volumes:
- n8n-data:/home/node/.n8n
volumes:
n8n-data:
文件名用 compose.yaml,或者 docker-compose.yml,Compose v2 两个都认。文件放哪就在哪执行命令,换个目录敲会报错,原文在下面"常见错误"里。
密钥那行先别看懵,一条命令生成:
openssl rand -hex 24
出来 48 个字符,粘进去。这一行是整份文件里最不能随手改的,第六节讲为什么。
逐行说几个真会踩的:
ports前面那个127.0.0.1:不是装饰。只写5678:5678等于把面板摊在公网所有网卡上,后面接反代再放开。/home/node/.n8n是镜像里的固定路径,别改成/root/.n8n。容器里跑的是 uid 1000 的 node 用户,改路径多半是权限问题。volumes用命名卷n8n-data,不用./data:/home/node/.n8n这种绑定挂载。绑定挂载更好找文件,但目录属主是 root 时容器起不来,得先chown 1000:1000。restart写unless-stopped,理由在第五节。
第一次启动,什么时候才算真好了
docker compose up -d
docker compose ps
第一次要拉镜像,2.43.7 解压后占 1.8G。镜像本来就在机器上的话,十几秒就 Up。
但 ps 显示 Up 不代表能用。n8n 首次启动要迁移数据库、生成密钥文件,这段时间端口已经监听,接口还返回空。我这次掐着表看:12 秒时 docker compose ps 已经是 Up,/rest/settings 什么都没有;20 秒左右才返回 JSON。日志里出现这一行才算真好了:
Editor is now accessible via:
http://localhost:5678
想看进度就 docker compose logs -f n8n。第一次启动还会打一行 No encryption key found - Auto-generating and saving to: /home/node/.n8n/config,说明密钥是它自己生成的——如果你已经在 compose 里写了密钥,这行就不该出现,出现了说明环境变量没生效。

界面是英文的,跟部署方式无关,是账号里的语言设置;站点上那台是中文,因为它当初注册时选过。
TZ 和 GENERIC_TIMEZONE 不是一回事
两个变量名字长得像,管的东西完全不同。
TZ 管容器这个操作系统的时区,date、日志时间戳、系统里跑的脚本都看它。GENERIC_TIMEZONE 是 n8n 自己的实例时区,定时类节点按它排班,文档写的默认值是 America/New_York。
我第一次只写了 TZ,觉得时区这事就完了。结果是这样:
docker exec n8n date
Mon Nov 9 09:25:33 CST 2026
系统时钟没问题。但登录进去看工作流设置,时区那栏写着 Default - America/New_York。也就是说,画布上写"每天九点"的任务,会按纽约时间九点跑,跟你差 13 个小时。全程不报错,日志也不吭声。
补上 GENERIC_TIMEZONE=Asia/Shanghai,重建容器,那栏变成 Default - Asia/Shanghai。

想更直接地确认时区生效,跑一次带时间的工作流。我在"整理字段"节点里放了个 $now.toFormat('yyyy-MM-dd HH:mm'),手动执行,输出是本地时间:

两个变量都写,别省。只写 GENERIC_TIMEZONE 的话,调度对了,但容器里 date 还是 UTC,看日志又得自己换算。
卷里到底装了什么
进容器看一眼 /home/node/.n8n:
docker compose exec n8n ls -la /home/node/.n8n
里面五个东西有用:config(56 字节,只有一行密钥)、database.sqlite 加两个 WAL 文件(工作流、凭据、执行记录都在这)、nodes(装的社区节点)、storage(二进制数据,比如节点里存的图片)。
所以判断一个实例是不是真持久化,只看一件事:down 之后再 up,工作流还在不在。
docker compose down
docker compose up -d
down 删容器和网络,卷留着。我这么折腾了一遍,列表里两条工作流原封不动,连创建时间都没变。

down -v 是另一回事,-v 连卷一起删。我为了拍下面这张图也跑了一遍:容器重新起来,界面回到首次设置账号那页,账号、工作流、执行记录一起消失,只剩一个空的 SQLite。

备份也一样:光备 database.sqlite 没用,密钥在 config 里。两个一起,才叫备了。
重启策略:什么情况它会自己回来
restart: unless-stopped 的行为分三段:进程自己退出,会拉起;你手动停过,它就一直停着,连宿主机重启都不理;always 的区别是手动停过的,守护进程重启后会再拉起来。
我按三种情况试了一遍,结论跟直觉有点出入。
第一种,从外面 docker kill:
docker kill n8n
docker ps -a --filter name=n8n
容器 Exited (137),五秒后再看还是 exited,重启次数 0。Docker 把 stop 和 kill 当成"你主动停的",策略直接不生效,官方文档里写的是防止陷入重启死循环。
第二种,进容器把 n8n 主进程杀掉。先看进程:
docker compose exec n8n ps -o pid,args
第一行是 tini,PID 1;第二行 n8n 才是真正干活的,PID 每次不一样。对着它 kill -9,两秒后容器自己回来了,重启次数变成 1。这才叫验证重启策略。
第三种,docker compose stop。停完它就一直躺着,得 start 或者 up -d 叫醒。
查重启次数:
docker inspect n8n | grep -E '"RestartCount"|"Status"'
会打出两三行,其中一行是 "RestartCount": 1。
最后一句提醒:配置写错的时候,重启策略会把它反复拉起来。docker compose ps 里状态显示 Up 3 seconds,过一会再看还是 Up 3 seconds——那不是健康,是崩溃循环。下一节就是这个现场。
加密密钥,我在这一步把实例弄崩过
事情是这样的:实例已经跑了一天,里面有工作流也有凭据。我想着密钥该写进 compose,方便以后迁移,就往 environment 里加了一行 N8N_ENCRYPTION_KEY=,值是新生成的另一串。
up -d 之后容器起不来了,日志每几秒重复这一段:
Error: Mismatching encryption keys. The encryption key in the settings file /home/node/.n8n/config does not match the N8N_ENCRYPTION_KEY env var. Please make sure both keys match.
Error: Failed to load command "start"
原因不复杂:密钥已经在 config 文件里了,现在环境变量又给了一个不同的值,n8n 不知道该信哪个,直接不启动。
两个解法,看你的实例处在哪一步。
已经在跑的:把环境变量改成文件里那个值,别反过来。
docker compose exec n8n cat /home/node/.n8n/config
里面就一行 encryptionKey,抄进 compose,再 up -d,起来了,凭据也还能解开。
全新实例:从第一天就把密钥写死,先写再启动。我实测过顺序——新卷加环境变量密钥,首次启动写进 config 的就是环境变量那个值,两边一致,之后不会再撞这个错。
为什么非要管这个密钥:n8n 拿它加密数据库里的凭据。密钥对不上,凭据就是一堆解不开的字符串。删卷重来能绕过去,但工作流、执行记录、凭据一起没了。
密钥跟着卷走,卷没了密钥也没了。这也解释了为什么"换了台机器重新部署"最容易出事:镜像一样、compose 一样,密钥不一样,导过来的数据库打不开凭据。
五条自检
- 容器在跑:
docker compose ps显示 Up,不是 Restarting。 - 接口通了:
curl -s http://127.0.0.1:5678/rest/settings | head -c 200,能看到 JSON。返回空说明还在启动。 - 时区对了:工作流菜单 → 设置,Timezone 那栏是
Default - Asia/Shanghai。 - 能跑:随便建个工作流,手动执行一次,输出里的时间是本地时间。
- 真持久化:
docker compose down再up -d,工作流还在。
五条都过,这套东西才算立住。少一条,下次出问题你就得从头猜。
几个会遇到的报错
Mismatching encryption keys
上一节那个。改 env 之前先看 config 里是什么,别硬删文件——删了密钥,凭据全废,而且没有任何提示,直到你打开某个凭据节点才知道。
Error: Failed to load command "start"
这行本身没信息量,它是崩溃的收尾。真正的原因在它上面几行,可能是密钥不匹配,也可能是权限。
用绑定挂载 ./data:/home/node/.n8n 的时候常见的是权限:
Error: EACCES: permission denied, open '/home/node/.n8n/config'
宿主机上新建的目录属主是 root,容器里跑的是 uid 1000。修法是 chown -R 1000:1000 ./data。嫌麻烦就用命名卷,权限 Docker 自己管。
failed to bind host port 0.0.0.0:5678/tcp: address already in use
端口被占了。这台机器上可能还跑着别的东西,或者你之前那个 docker run 起的容器没删。查:
ss -ltnp 'sport = :5678'
docker ps -a
要么停掉占用的,要么改 compose 前半段那个宿主端口。
no configuration file provided: not found
不在 compose 文件所在目录执行。cd 过去再敲,或者用 docker compose -f /路径/compose.yaml up -d。
改了环境变量,restart 之后没变化
没有报错,就是没生效。我实测过:改完 GENERIC_TIMEZONE 跑 docker compose restart,进容器 printenv GENERIC_TIMEZONE 是空的,时区还是纽约。restart 只是把进程重启,不重建容器,环境变量是创建容器时定死的。
要生效只能用 docker compose up -d,它会发现配置变了,重建容器。看到输出里写着 Recreated 才算改到了。
容器里时间对,定时还是差 13 小时
只看 TZ 没看 GENERIC_TIMEZONE,第四节那个坑。
安全提醒
这三条只跟本篇的部署方式有关。
ports保持127.0.0.1:5678:5678。要公网访问就加反代和证书,别图省事改成5678:5678。n8n 面板里存的是各种第三方凭据,被人登进去等于全部泄露。- compose 文件现在含加密密钥,权限收一下
chmod 600,也不要提交到公开仓库。要进 git 就把密钥挪进.env并忽略它。 docker compose down -v会连数据一起删。做升级、迁移这类动作之前,先把卷备份出来,别在生产实例上手滑。
小结
compose 不长,真正要定死的就三样:卷挂 /home/node/.n8n、时区两个变量都写、restart 给 unless-stopped。这三样对了,剩下的都是细节。
密钥那行建议从第一次启动就写上,事后再加,大概率撞 Mismatching encryption keys,而且是在你已经存了凭据之后才发现。
改配置只认 up -d,restart 不读新环境变量。这条不记住,会浪费你半小时去怀疑时区数据库。
接着往下
容器跑起来只是开头,四个基础词(工作流、节点、连线、执行)搞不清,后面全是猜:Workflow、Node、Connection、Execution:四个词讲清 n8n 的世界。上一篇是 Docker 是什么:给 n8n 新手的最小必要知识,整套教程按阶段排在课程目录里。
参考资料
- Install with Docker(docker run 完整参数、卷与 PostgreSQL 变体)
- Install using Docker Compose(官方 compose 结构、卷与端口说明)
- Timezone and localization(
GENERIC_TIMEZONE与默认值America/New_York) - Deployment environment variables(
N8N_ENCRYPTION_KEY、N8N_USER_FOLDER、N8N_LISTEN_ADDRESS) - Set a custom encryption key(密钥何时写入配置文件)
- Start containers automatically(四种重启策略,手动停止对策略的影响)
最后更新:2026 年 11 月 9 日,用的是 n8n 2.43.7。

