One good solution for this problem is to have the Docker daemon (running on the system instance of systemd) use socket activation.
That way, when you launch a docker command (say from a user systemd unit), it will have a socket to connect to, but it will block until the Docker daemon is actually ready to serve it.
The basic idea of socket activation to elide explicit dependencies is described on this article in the systemd blog, which talks about four services which traditionally have been handled through explicit dependencies, but with socket activation no longer need them to be configured. Here's an excerpt (it's long, but really makes the point):
Socket activation makes it possible to start all four services completely simultaneously, without any kind of ordering. Since the creation of the listening sockets is moved outside of the daemons themselves we can start them all at the same time, and they are able to connect to each other's sockets right-away. I.e. in a single step the
/run/dbus/system_bus_socket sockets are created, and in the next step all four services are spawned simultaneously. When D-Bus then wants to log to syslog, it just writes its messages to
/dev/log. As long as the socket buffer does not run full it can go on immediately with what else it wants to do for initialization. As soon as the syslog service catches up it will process the queued messages. And if the socket buffer runs full then the client logging will temporarily block until the socket is writable again, and continue the moment it can write its log messages. That means the scheduling of our services is entirely done by the kernel: from the userspace perspective all services are run at the same time, and when one service cannot keep up the others needing it will temporarily block on their request but go on as soon as these requests are dispatched. All of this is completely automatic and invisible to userspace. Socket activation hence allows us to drastically parallelize start-up, enabling simultaneous start-up of services which previously were thought to strictly require serialization. Most Linux services use sockets as communication channel. Socket activation allows starting of clients and servers of these channels at the same time.
The Docker daemon supports socket activation since 2014, so it's likely the version you're using already supports that.
Check if your distribution ships a
docker.socket unit, in which case, all you need to do is enable it.
If your Docker daemon supports socket activation, but your distribution doesn't include a
docker.socket unit, take a look at this tutorial for instructions on how to set it up.
One more alternative to consider is to switch from Docker to podman.
Podman tries to be a drop-in compatible replacement for Docker (so you can use the same command lines, simply replacing
The main difference between them is that podman doesn't need a daemon, so you don't need to wait for one to be up before you start a container. Podman is available as a package in most recent versions of Linux distributions. That and the fact that you can use it just as you use
docker today should make it easy to get started with it.