systemd 服务里设置工作目录:WorkingDirectory

写 Python / Node 服务的 systemd 单元时,程序里各种相对路径都得基于”工作目录”来解读。systemd 提供了 WorkingDirectory 字段,比在 ExecStartcd 一下再启动规范得多。

最简单的例子

[Unit]
Description=My App
After=network-online.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 main.py
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

WorkingDirectory=/opt/myapp 的效果:

  • 进程启动时 cwd 就是这个目录
  • ExecStart 里的相对路径按它算(比如 python3 main.py 就是 /opt/myapp/main.py
  • Python 里 os.getcwd() 返回这个目录
  • 写日志到 ./logs/app.log 就是 /opt/myapp/logs/app.log

为什么不用 cd

见过这种写法:

ExecStart=/bin/bash -c 'cd /opt/myapp && python3 main.py'

能跑,但坏处一堆:

  • 多一层 shell,进程树多了个 bash
  • 语义没那么直观
  • systemd 的 Restart / 信号处理会作用在 bash 上,不是真进程
  • 环境变量、User、日志跟你想的不一样

能用字段就用字段cd 是最后的兜底。

常配套的字段

User / Group

以指定用户启动,不用 root 跑业务:

User=appuser
Group=appuser

注意 WorkingDirectory 里的目录必须让这个用户能读、能进:

sudo chown -R appuser:appuser /opt/myapp

EnvironmentFile

把环境变量放外面,方便修改:

EnvironmentFile=/etc/myapp/env

env 文件内容:

DATABASE_URL=postgres://user:pass@localhost/mydb
APP_PORT=8000

代码里 os.getenv("DATABASE_URL") 就能拿到。

Restart

崩溃后的行为:

Restart=always              # 无条件重启
RestartSec=3                # 3 秒后重启
StartLimitInterval=60       # 60 秒内
StartLimitBurst=5           # 最多重启 5 次,再多就放弃

生产上都需要——不然崩了以后就得手动来。

日志

默认 stdout / stderr 会走 journald:

journalctl -u myapp -f
journalctl -u myapp --since "10 minutes ago"

想直接写文件:

StandardOutput=append:/var/log/myapp/out.log
StandardError=append:/var/log/myapp/err.log

append: 需要 systemd 240+,老版本用 file:

一个”生产可用”的模板

[Unit]
Description=My FastAPI App
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=3
StartLimitBurst=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

启用:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp

一句话总结

WorkingDirectory=/path 就是 cwd,不要在 ExecStart 里套 bash -c 'cd ... && ...'。搭配 UserEnvironmentFileRestart 一份 systemd 单元就是完整的服务定义。