Блог
DevOps · systemdDevOps 6 мин чтения

systemd-unit для Python / Node.js приложения: production-ready

Как правильно превратить Python/Node.js-скрипт в systemd-сервис: авторестарт при падении, автозагрузка, ограничения ресурсов, non-root запуск, логи в journald.

Запустил скрипт через nohup python bot.py & — при ребуте всё пропало, при падении не перезапустится. Правильный способ — systemd unit. Ниже — типовой прод-конфиг для Python и Node.js приложений на Ubuntu.

Python-приложение: пример FastAPI

Кладём в /etc/systemd/system/myapi.service.

[Unit]
Description=MyAPI FastAPI application
After=network.target postgresql.service

[Service]
Type=exec
User=deploy
Group=deploy
WorkingDirectory=/opt/myapi
Environment="PATH=/opt/myapi/venv/bin"
Environment="DATABASE_URL=postgresql://user:pass@localhost/db"
EnvironmentFile=/opt/myapi/.env
ExecStart=/opt/myapi/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000

# рестарт при падении
Restart=always
RestartSec=5

# ограничения ресурсов
MemoryMax=1G
CPUQuota=200%

# безопасность
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapi/var
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Node.js-приложение

Аналогично, /etc/systemd/system/mybot.service.

[Unit]
Description=MyBot Telegram bot
After=network.target

[Service]
Type=exec
User=deploy
Group=deploy
WorkingDirectory=/opt/mybot
Environment="NODE_ENV=production"
EnvironmentFile=/opt/mybot/.env
ExecStart=/usr/bin/node --max-old-space-size=512 index.js

Restart=on-failure
RestartSec=10
StartLimitBurst=5
StartLimitIntervalSec=60

MemoryMax=768M
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

# stdout/stderr → journald
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mybot

[Install]
WantedBy=multi-user.target

Активация и управление

Стандартные команды.

sudo systemctl daemon-reload    # после правки unit-файла
sudo systemctl enable --now myapi

# статус
sudo systemctl status myapi
sudo systemctl is-active myapi   # active/inactive
sudo systemctl is-enabled myapi  # enabled/disabled

# логи
sudo journalctl -u myapi -f              # tail -f
sudo journalctl -u myapi --since "1 hour ago"
sudo journalctl -u myapi -p err          # только errors

# рестарт
sudo systemctl restart myapi
sudo systemctl reload myapi   # если сервис поддерживает SIGHUP

Типичные грабли

На чём чаще всего спотыкаются.

  • "status=203/EXEC" — путь к бинарнику неверный. which node, вписать полный путь в ExecStart.
  • "Failed to start" без деталей** — sudo journalctl -u service -n 50 покажет реальную ошибку.
  • Приложение зависит от .env — используйте EnvironmentFile=/path/.env (не Environment=).
  • `Type=simple` vs `Type=exec` — используйте exec (доступно с systemd 240+), точнее отслеживает падение.
  • Sudo нужен внутри — плохая идея; правильно — дать нужные capabilities через AmbientCapabilities.

Частые вопросы

PM2 / Supervisor вместо systemd?
PM2 удобен для Node-стека без интеграции с системой. Supervisor — legacy. systemd — родное для Linux, интеграция с cgroups, journald, автозагрузкой.
Как ротировать логи?
journald ротирует сам (см. `SystemMaxUse` в journald.conf). Дополнительно можно `journalctl --vacuum-time=30d`.
Куда логи если приложение пишет в файл?
Правильнее — писать в stdout, journald перехватит. Если legacy пишет в файл — logrotate + систeмdlogrotate.timer.