← all posts

Managing a Docker application with systemd

Let's say you have a docker application you want to always have running on your server (e.g. a home server, a DigitalOcean droplet, etc..)

You want this application to run as hands-off as possible, which means:

  1. You want it to start automatically when your server starts/restarts
  2. You want it to restart automatically if it ever crashes or the process is killed

On Linux we can do both of these easily with systemd, a service manager that comes pre-installed with most Linux distributions.

The examples below refer to mealie, a recipe manager which comes packaged as a Docker image. However, these instructions can be adapted for any docker container you'd like to manage with systemd.

Creating wrapper scripts

systemd manages services by calling pre-defined commands that start, stop, and reload a given service. So the first thing we'll do is define bash wrapper scripts that systemd can use to start and stop our docker container(s).

Notice how the "start" script safely creates containers only when one doesn't exist. Implementing custom logic like this is one of the advantages of wrapping our docker commands in a bash script instead of providing systemd with a direct docker start or docker stop command.

A few things to note:

  1. I'm choosing to store my wrapper scripts under /var/mealie/bin, but you can choose any directory.
  2. It is good practice is to use long options (--env) instead of shorthand (-e) for automated scripts. They are easier to read and understand, and these aren't commands you'll be typing out manually.
  3. Your own docker command might look slightly different (e.g. different volumes, ports, etc..), but be sure to always specify a --name. A fixed --name makes it easier to grep the docker ps list for your container. If you don't specify one, docker will generate a random name.

systemd runs as root, so ensure these scripts are owned by and accessible to root:

chmod -R 700 /var/mealie/bin/
chown -R root /var/mealie/bin/

Creating the systemd Unit File

systemd uses configuration files (which it calls "unit files") to define services and their behavior. On many Linux installations these files are located at /etc/systemd/system/ (see this post on how to list all the directories systemd uses).

Create a unit file /etc/systemd/system/{name}.service for your new service, replacing {name} with the name of your service.

systemd provides many more configuration options. See the [Unit] man page and [Service] man page.

The above configuration file:

  1. Defines a service (in this case, one named mealie).
  2. Defines dependent services (docker, containerd) that must be started before this service starts.
  3. Defines commands (our wrapper scripts) that can be used to start and stop the service.
  4. Tells systemd to start this service automatically on system boot (WantedBy=)
  5. Tells systemd to restart this service automatically if it stops for any reason (besides a user manually stopping it)

Set the permissions for this new file:

chmod -R 644 /etc/systemd/system/mealie.service
chown -R root /etc/systemd/system/mealie.service

Finally, enable your new service. systemctl is the command used to interface with systemd services.

sudo systemctl enable mealie

Conclusion

And that's it! ✨

You can now easily start, stop, and reload your service at any time using systemctl:

sudo systemctl start mealie
sudo systemctl restart mealie
sudo systemctl stop mealie

start your service and verify the status is now "active (running)":

$ sudo systemctl status mealie
● mealie.service - Mealie recipe web application
     Loaded: loaded (/etc/systemd/system/mealie.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2021-11-12 15:43:37 EST; 1 day 3h ago
   Main PID: 1239 (bash)
      Tasks: 10 (limit: 2147)
     Memory: 73.5M
     CGroup: /system.slice/mealie.service
             ├─1239 bash /var/mealie/bin/start
             └─1379 docker start --attach mealie_v0.5.1

You can also verify your docker container is running using docker ps (you may need to be root):

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED        STATUS        PORTS                                   NAMES
b9ea471c7153   hkotel/mealie:v0.5.1   "/bin/sh -c /app/mea…"   28 hours ago   Up 27 hours   0.0.0.0:9925->80/tcp, :::9925->80/tcp   mealie_v0.5.1