4

I have a script that contains the following:

sudo machinectl start "$machinename"
sudo systemd-run -PM root@"$machinename" "$command"
Failed to connect to bus: No such file or directory
Failed to start transient service unit: Transport endpoint is not connected

This fails because the first line only starts booting the container; the second line runs before the container finishes booting. For now, I have a solution that constantly polls for the container's status and blocks till it's ready:

while [ "$(sudo systemctl show "systemd-nspawn@$machinename" -P StatusText)" != "Container running: Ready." ]
do
true
done

How do I wait for the container to finish booting, without constantly polling for the container's status?

1 Answer 1

6

Depends what you are trying to do. I'll give a direct answer to your question then answer some alternatives. I'm assuming you're using systemd as the init system in the container, which will be true if your container OS is based on Debian / Arch / Ubuntu or similar.

Executing a command after starting a nspawn container

In the .nspawn file (/etc/systemd/nspawn/yourcontainer.nspawn) add:

[Exec]
NotifyReady=yes

Then sudo machinectl start yourcontainer will wait until the container has finished booting before exiting. The second line of your script will now work because the container is ready (unless your container fails to boot, which would have put your polling into an infinite loop).

Under the hood, the host's systemd-nspawn is setting up a Unix domain socket /run/host/notify in the container. When the container's systemd is ready (in other words when it reaches the multi-user.target target), it sends a READY=1 notification to that socket. The host's systemd-nspawn service waits for this message to be received.

The drawback of this approach is they you can't boot the container asynchronously anymore (unless using & notation), which gets annoying if you are debugging and the boot time is long.

Some other approaches, in order of complexity:

Running command in chroot

Assuming the container is not running,

sudo chroot /var/lib/machines/yourcontainer /bin/bash -c "$command"

This is useful if you have just created a container and are initialising it programmatically a number of times. Obviously it doesn't benefit from the sandboxing features. It also won't work if you've previously run the same container with PrivateUsers=yes, because the files will be chowned with a high UID. It might give undefined results if the container is already running.

Using systemd-nspawn directly

This approach does not need the NotifyReady=yes|no explained above.

systemd-nspawn -M yourcontainer -P /bin/bash -c "$command"

This will run the command inside the container with all the sandboxing turned on, but the command will run as the only process (and with PID=1) - your init service will not have run. So for example the networking will not be available (unless you are using host networking anyway).

This command will not work if the container is already running.

Socket activation

If you are waiting for a server in the container to be ready, then just use socket activation (assuming your server is compatible). This is better explained elsewhere, in summary systemd will wait for a connection to your socket (such as TCP port 80). When a client does connect, systemd will then start your container and then forward the traffic to it. In olden times inetd did the same thing.

This would require a line like [email protected] in the .socket file.

1
  • NotifyReady was exactly what I wanted, thanks!
    – 25pwn
    Mar 29, 2022 at 3:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .