Automatic Start-up with systemd
Since our Docker containers are web services, they need to be running all the time in order to be useful. To ensure this happens, it's useful to automatically start them when the host machine boots. We can do this easily using systemd on Linux.
TL;DR: if you don't want to learn how this works and you just want the solution, download the attached
docker-compose@.service file and place it in the
/etc/systemd/system directory. Then skip to Using our new service.
Systemd is a service manager and init system. This means it is responsible for starting, stopping, and supervising services running on the machine. Systemd has the capability to automatically start services on boot, as well as managing dependencies and ordering. This allows us to automatically bring our Docker Compose stacks up when our host machine boots.
Systemd also has a feature called template units.
Template units have names that end with an
They allow you to define a generic service that can have specific instances.
We will use this to define a generic service for Docker Compose stacks.
Then we'll create specific instances of this for each of our different stacks.
Creating the file
Fist, we have to create a new unit file. We can do that in two ways: (1) use the
systemctl edit command or (2) place the file in the
Option 1 handles this automatically, so that's what I'll use.
We'll run the following command to create a new service file. The name can be arbitrary, but it must end with
@.service to make it a template service file.
# systemctl edit --full docker-compose@.service
Defining the service
Here are the contents of the
[Unit] Description=Start Docker Compose stack at %I Requires=docker.service After=docker.service [Service] WorkingDirectory=%I Type=oneshot RemainAfterExit=true ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down ExecReload=/usr/bin/docker compose up -d [Install] WantedBy=multi-user.target
Now let's walk through what it does.
[Unit] section contains metadata and dependency information.
Description property is just a description for the unit.
Don't worry about the
%I placeholder yet. We'll get to that later.
Requires property defines units that are required for the
docker-compose@ service to start.
Since we'll be using Docker, we need Docker to be running.
After directive tells systemd to wait until
docker.service has started before starting
Without this, they would both start simultaneously.
[Service] section contains instructions for systemd to start, stop, and supervise our service.
Systemd services can be started, stopped, and reloaded. Reloading means they'll reload their configuration.
Services can also be restarted, but this is implemented as just stopping and starting the service sequentially, so it isn't really a separate action as far as we're concerned.
ExecReload directives define commands to run for each action.
We just call
docker compose up/down for each one.
We pass the
-d flag when bringing the stack up as we don't need systemd to capture the log output since Docker already handles that.
Since Docker Compose automatically reloads the
docker-compose.yml file when
docker compose up is run, the reload and start actions can be the same.
WorkingDirectory option is the most important one here. It tells systemd to change to the given directory before starting the service. But
%I isn't a directory. What's up with that?
Remember how I mentioned template units? When activating a template unit, you have to specify a value after the
@ sign, like
docker-compose@/srv/swag.service. Systemd will take the value after the
@ sign, called the instance name, and fill it in wherever
%I occurs in the unit file.
Systemd requires the instance name to be escaped. This can be done manually or by using the
systemd-escape command. For simple paths like the ones I'm using, slashes (
/) get replaced with hyphens (
-), meaning we'd use
So if we provide a directory path as the instance name when enabling our service, systemd will run the
docker compose commands in that directory, because the value of
WorkingDirectory is the
This means we can use the same service file for multiple Docker Compose stacks.
Finally, we need to tell systemd when to trigger our service.
This is done in the
WantedBy directive defines a target unit that our service will be a part of when it is enabled.
In systemd, the
multi-user target is the default target that the system will activate, so we use this.
Using our new service
This is the easiest part. Now all we need to do is enable our service. Enabling a service means telling systemd to start it when the target that wants it is started. So if we enable our new service, systemd will start it when the system boots, because the default target wants it.
But we can't just
systemctl enable docker-compose@.service.
We need to provide an instance name.
Remember that the instance name is the escaped directory where our
docker-compose.yml file is located.
For example, let's enable the Secure Web Application Gateway stack, which is located in
/srv/swag. First, we'll escape the path:
$ systemd-escape /srv/swag -srv-swag
Then we'll use the escaped path as the instance name. Note that
systemctl must be run as root.
# systemctl enable docker-compose@-srv-swag.service Created symlink /etc/systemd/system/multi-user.target.wants/docker-compose@-srv-swag.service → /etc/systemd/system/docker-compose@.service.
Now our service is enabled and it will start next time we reboot the system. You can repeat this process for other Docker Compose stacks by using different directory paths.
Note: enabling a service does not start it. If you want to enable it and start it at once, you can use
systemctl enable --now <service>.