How to Install a systemd Service in a Yocto Recipe

A Yocto systemd recipe needs three aligned pieces: (1) inherit systemd plus SYSTEMD_SERVICE in the recipe, (2) install the unit under ${D}${systemd_system_unitdir} (documented equivalently as ${D}${systemd_unitdir}/system), and (3) turn on systemd as the init manager for the distro/image. This is a minimal howto from the official systemd class and init-manager docs—no vendor layers, no pricing.

Which inherit?

One-line answer: In the recipe that packages the unit, inherit systemd and set SYSTEMD_SERVICE:${PN} to the service file name. The class is active only when systemd is in DISTRO_FEATURES.

From the Yocto Project Reference Manual systemd class:

  • inherit systemd
  • SYSTEMD_SERVICE:${PN} = "mydaemon.service" (space-separated for multiple)
  • Services auto-enable on boot unless SYSTEMD_AUTO_ENABLE:${PN} = "disable"
  • If units live outside the main package, set SYSTEMD_PACKAGES

Minimal skeleton:

SUMMARY = "Example daemon with systemd unit"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://mydaemon.service"

inherit systemd

SYSTEMD_SERVICE:${PN} = "mydaemon.service"

do_install() {
    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/mydaemon.service ${D}${systemd_system_unitdir}/
}

OE examples such as connman use SYSTEMD_SERVICE:${PN} = "connman.service". Prefer the : override form over legacy SYSTEMD_SERVICE_${PN} on current releases.

Where do the files go?

One-line answer: Install units under ${D}${systemd_system_unitdir} in do_install. The docs also describe ${D}${systemd_unitdir}/system. The class expects those paths when it fills FILES, presets, and package scriptlets.

Practical checks:

  1. List file://foo.service in SRC_URI and install from WORKDIR.
  2. A wrong path fails packaging with Didn't find service unit '…' (systemd.bbclass search).
  3. Template units (foo@.service) must match SYSTEMD_SERVICE names; instance enable follows class preset rules.
  4. Most board daemons are system units; user units use separate search paths—check the class if you need them.

Example unit:

[Unit]
Description: Example daemon
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/mydaemon
Restart=on-failure

[Install]
WantedBy=multi-user.target

Keep ExecStart aligned with where the recipe installs the binary (bindir).

Which IMAGE feature flag?

One-line answer: systemd is selected mainly via INIT_MANAGER = "systemd" (distro/local.conf), not a typical IMAGE_FEATURES toggle. inherit systemd in a recipe does not by itself make the image boot with systemd.

Per Selecting an Initialization Manager:

INIT_MANAGER = "systemd"

That pulls the systemd init-manager include and drops competing sysvinit pieces. Older write-ups often show the manual axis:

DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
GoalWhereWhat
Package unitsrecipeinherit systemd, SYSTEMD_SERVICE
Unit pathdo_install${D}${systemd_system_unitdir}
Image initdistro / local.confINIT_MANAGER = "systemd"
Class enabledDISTRO_FEATURESneeds systemd

Also ensure the app package is in the image (IMAGE_INSTALL / packagegroup). systemd init without the package means no unit on rootfs.

Wrap-up

A Yocto systemd recipe is inherit systemd + SYSTEMD_SERVICE:${PN} + install into the system unitdir; the image side picks systemd with INIT_MANAGER = "systemd". See the systemd class and Initialization Manager.

Sources