Systemd targets are a special kind of unit, this is how I used them to serialize the boot order of my server and how I am planning on using them to shape the supervision tree of my system.

As a Linux user I have systemd installed on my devices but I usually tweak somebody else unit or write a service unit to start a program at boot.

This has changed some time ago when I started looking more into other unit types that are available for use.

I started with timers to automate recurring tasks such as renewing certificates using certbot but there is little information about how to use the rest of the unit types.

For this time we are going to look at a target unit because I see it as the most useful to get out and I happened to be working on mine recently.

targets

As I run more and more services on my server I find that there is more need to divide them into layers looking at their common dependencies and what they provide; some of them are there to provide an internal service and others are there for displaying HTML to me. E.g. most of my web services use the same postgresql cluster and they should all be started when this becomes available.

Basically for this instance we are going to divide programs I run into two categories, infrastructure and front-facing. For both groups we will make a target unit to group them and impose a start order.

For this experiment we will make two targets, infra.target and front.target, group the services to be started by their target and add everything to the supervision tree.

# infra.target
[Unit]
Description=infrastructure target
# a lists of units that will be started along this one
Wants=postgresql.service nginx.service
Wants=multi-user.target

# front.target
[Unit]
Description=front-end target
After=infra.target
Wants=infra.target web-app.service

Now for a brief explanation, when starting infra.target we are also starting the units postgresql.service and nginx.service as they are listed as Wants and the same is true for starting front-end.target starting infra.target and web-app.service.

With out dependencies out of the way we also have to ensure order of execution, starting a web app before the database server is started is not what we want.

What we have to understand here is that we have expressed dependency but not ordering; to express ordering of units startup we have to use the After, Before directives.

So going back to the previous example we want front.target to be started after infra.target and all its dependencies have completed the start up sequence as this means that both postgresql.service and nginx.service have been started.

We have taken our initial problem, ensuring some infrastructure will be started, and put a time constraint on when our front end is started. But we are not done yet!

The startup sequence differs from unit type to unit type; e.g. a service unit has finished the startup sequence when all ExecStart* directives have been issued, what about a target unit1?

This could become an issue with a long startup time, e.g. connection to a another machine to register you have booted, but for things such as starting nginx or a small postgresql cluster things should be fast enough.

The last step to take is to notice that we have a chain linking our targets front-end.target -> infra.target -> multi-user.target and we can then change the default target from default.target to front-end.target and the boot sequence will still go through you previous setup.

So wrapping up we saw how you can use a target unit to wrap together part of your services and wait for them to start all together, this will enable you to have layers of services and overall abstract a little more over the server you are booting.

  1. truth is I can’t find this information around, I hope the systemd project publishes a little more about it