Normally when I have a Docker container that I want to automatically come up whenever the machine restarts, I simply use –restart=always when running the container (or “restart: always” in a Docker Compose YAML file). Recently encountered a situation where that didn’t meet my needs. I thought it would be quick and easy to start the container from a service (a la systemd). It ended up being easy, but it wasn’t as quick as I thought because I made some incorrect assumptions and sloppy mistakes along the way–so in case I need to do this again I am documenting what I did here . . .
I was using an Ubuntu 16.04 machine and for my example I’m using my Beaverton School District Calendar image. To create the actual service, I created the file /etc/systemd/system/schoolCal.service with the contents:
[Unit] Description=School Calendar Service Requires=docker.service After=docker.service [Service] Restart=always ExecStart=/etc/systemd/system/schoolCal.sh [Install] WantedBy=multi-user.target
There’s nothing special about the service definition, it basically runs the schoolCal.sh script. The problem I encountered when creating the service file was I forgot to add the dependency on the docker.service (I’m not sure if both “Requires” and “After” need to be set, but at least one of them does). To enable the service I ran:
sudo systemctl enable schoolCal
Here are the contents of the schoolCal.sh script:
#!/bin/sh docker pull bakchoy/beavertonschoolcalendar docker run -i --net host -p 9999:9999 bakchoy/beavertonschoolcalendar
The script is very simple, but it took several tries for me to get it right. Here are some details I encountered/considered:
- It’s necessary to make the script executable
- The explicit pull means that the latest image will always be used when starting up a new container.
- Since I want the container log to be available via journalctl, the container has to be run in interactive mode “-i” instead of in detached mode.
- Normally when I run stuff in interactive mode, I use “-i -t”. When I had that, the script worked fine when I ran it manually, but when invoked by the service it would fail with “the input device is not a TTY”. It took me awhile to figure out the fix was simply to remove the “-t”.
- In this case, I wanted the container ip/hostname to be the same as the host, so I set “–net host”. In most situations that probably isn’t necessary.
- Space isn’t an issue here and I have a different mechanism for cleaning old containers. Otherwise I might have added a “-rm” (but I’m not certain it would work as expected).
I found https://docs.docker.com/engine/admin/host_integration/ which also has an example invoking a Docker container via systemd (and upstart), but it seems closer to using a Docker restart policy than what I’m doing. Although in general I think using the built-in Docker restart policies is a better approach, here are some aspects that differentiate my approach:
- No specific container tied to the service–a container doesn’t need to exist for things to work when the service is started
- I pull request can be included to basically provide automatic updates
- Logging can be directed to the standard service logging mechanism (such as journalctl)
- The service can be monitored with the same tools for monitoring other services rather than Docker way