Automatic Start-up of Docker Container Managed by systemd

At a very beginning of the Docker there was a problem that after machine is restarted (that runs Docker) or even docker service was restarted, the containers which were running were not started again. It was solved in Docker v1.1.2 and right after that release in v1.2.0 auto-restart of containers using policies was added as well (it was in 2014). Nevertheless administrators didn’t wait for it and started writing the service files to manage particular containers within the Docker. It was a workaround but in some environments still you can see that practice.

The main aim of it was to just write a service file that was detected by systemd and then such container become fully manageable by systemctl command. Nowadays we use Docker’s restart policies of course and problem is just gone but I wanted to give you a simple example how it was over 10-years ago. By the way you will also learn how to create your own service (it can be anything) and manage it with Linux built-in tool which is systemctl.

Let’s start with a docker installation of course, I will do it on CentOS8. Follow below instructions if you are missing Docker, if you have it already, go straight to writing a service file.

Older versions of Docker went by the names of docker or docker-engine. Uninstall any such older versions before attempting to install a new version, along with associated dependencies:

$ sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

It’s OK if yum reports that none of these packages are installed. If something is wrong and installation does not proceed most likely you have already other container runtime environment that you need to remove, like for instance podeman. Then please remove it:

$ sudo yum remove podman -y

Once we validated that no longer old versions of Docker are used, let’s install the yum-utils package (which provides the yum-config-manager utility) and set up the Docker repository:

$ sudo yum install -y yum-utils
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

And finally, installation of Docker engine:

$ sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Enable and start Docker service:

sudo systemctl enable --now docker

Verify that Docker Engine installation is successful by running the hello-world image.

$ sudo docker run hello-world 

This command downloads a test image and runs it in a container. When the container runs, it prints a confirmation message and exits. You have now successfully installed and started Docker Engine.

Now it’s time to create our application container, in this case I will use a nginx web server image that will simulate my critical application called webserver with exposed port 8080 on my Docker machine:

$ sudo docker run -d --name webserver -p 8080:80 docker.io/nginx

Double check that it is running:

$ docker ps

You should see similar out to this one:

Writing a service file

To create our own service to manage container, in this case webserver container we need to create a service file. I will call it docker-web1.service and it need to be created in one of the two locations:

  • /lib/systemd/system/ – Directory where system’s copy of unit files are generally kept. When software installs unit files on the system, this is the location where they are placed by default.
  • /etc/systemd/system/ – Unit files found in this directory location take precedence over any of the other locations on the filesystem. If you need to modify the system’s copy of a unit file, putting a replacement in this directory is the safest and most flexible way to do this.

In our case it would be /etc/systemd/system/:

$ vi /etc/systemd/system/docker-web1.service

And now fill it with below content:

[Unit]
Description=My web container
Requires=docker.service
After=docker.service

[Service]
Restart=always
ExecStart=/usr/bin/docker start -a webserver1
ExecStop=/usr/bin/docker stop -t 2 webserver1

[Install]
WantedBy=default.target

To give you a little bit of understanding what is inside, I will explain directives that I used.

[Unit] Section Directives:

  • Description=: This directive can be used to describe the name and basic functionality of the unit. It is returned by various systemd tools, so it is good to set this to something short, specific, and informative.
  • Requires=: This directive lists any units upon which this unit essentially depends. If the current unit is activated, the units listed here must successfully activate as well, else this unit will fail. These units are started in parallel with the current unit by default.
  • After=: The units listed in this directive will be started before starting the current unit. This does not imply a dependency relationship and one must be established through the above directives if this is required.

[Service] Section Directives:

  • ExecStart=: This specifies the full path and the arguments of the command to be executed to start the process. This may only be specified once (except for “oneshot” services). If the path to the command is preceded by a dash “-” character, non-zero exit statuses will be accepted without marking the unit activation as failed.
  • ExecStop=: This indicates the command needed to stop the service. If this is not given, the process will be killed immediately when the service is stopped.
  • Restart=: This indicates the circumstances under which systemd will attempt to automatically restart the service. This can be set to values like “always”, “on-success”, “on-failure”, “on-abnormal”, “on-abort”, or “on-watchdog”. These will trigger a restart according to the way that the service was stopped.

[Install] Section Directives:

  • WantedBy=: This directive is the most common way to specify how a unit should be enabled. This directive allows you to specify a dependency relationship in a similar way to the Wants= directive does in the [Unit] section. The difference is that this directive is included in the ancillary unit allowing the primary unit listed to remain relatively clean. When a unit with this directive is enabled, a directory will be created within /etc/systemd/system named after the specified unit with .wants appended to the end. Within this, a symbolic link to the current unit will be created, creating the dependency. For instance, if the current unit has WantedBy=multi-user.target, a directory called multi-user.target.wants will be created within /etc/systemd/system (if not already available) and a symbolic link to the current unit will be placed within. Disabling this unit removes the link and removes the dependency relationship.

One we save and exit the service file we have to reload the systemctl configuration, to allow systemd be aware of our service at all:

$ systemctl daemon-reload

And now we are done. Our webserver service should be recognized and we should be able to manage it via systemctl commands. Let’s test it:

$ systemctl start webserver
$ systemctl status webserver
$ systemctl stop webserver
$ systemctl enable webserver

Hope you learn how to build your own service and manage it with systemctl. It is not so hard as it seems.