Running scheduled tasks in Docker containers using systemd timer-units

Systemd timer-units can be set up to depend on other systemd units. This is great if need to run periodic maintenance tasks inside docker containers that come and go on demand.

Systemd timer-units workflow

Control-flow of systemd timer-units in a nutshell.

Manually editing and maintaining crontab files on hosts with many coming and going services can become cumbersome very fast.

In this scenario, replacing cron-jobs with systemd timer-units has several advantages:

For it to work, two new unit-files are needed:

Lets have a look at how you would setup ownCloud “cron-jobs”.

Example of use with ownCloud

OwnCloud is (self-hosted) server software that enables you to access and share files, calendars, contacts or more, similar to e.g. Google Drive.

A system like ownCloud sometimes requires tasks to be done on a regular basis without the need for user interaction or hindering ownCloud performance. For that purpose, as a system administrator, you can define background jobs (for example, database clean-ups) which are executed without any need for user interaction.

Admin manual: Defining Background Jobs

In this example, we’re going to setup cron.php to be run periodically at fixed 15 minute intervals by executing a command inside the container.

As a prerequisite, you should have systemd manage Docker on your machine, and a working ownCloud-instance that you can log into.

Choose the right cron-type

Owncloud has three options for scheduling background maintenance tasks:

owncloud cron-type selection

Selecting cron-job type in owncloud admin section

First, select option “Cron” in the admin configuration section. 1

While this is not strictly necessary, it will prevent automatic “AJAX” calls from degrading performance, and disallow someone from outside to trigger background tasks just by making a HTTP requests.

Create systemd unit-files

Next, create three files in systemd’s local configuration folder:

touch /etc/systemd/system/owncloud.service /etc/systemd/system/owncloud-cron.{timer,service}

The contents of respective unit-files (copy-n-paste/modify as needed) should look like this:

# /etc/systemd/system/owncloud.service
[Unit]
Description=Owncloud PHP-FPM Docker container
After=docker.service
Requires=docker.service
# This will auto-start the cron timer
Wants=owncloud-cron.timer

[Service]
EnvironmentFile=-/etc/sysconfig/owncloud
# Giving the container a name (owncloud.service in this case) is really helpful later on
ExecStart=/bin/docker run --rm -name %n $OPTIONS macedigital/phpfpm
ExecStop=/bin/docker stop %n
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

The owncloud.service runs a Docker container with the same name, so it’s easy to identify it later on. Also, whenever the service is started, it will try to activate the owncloud-cron.timer unit:

# /etc/systemd/system/owncloud-cron.timer
[Unit]
Description=Run owncloud cron.php every 15 minutes
# Auto-shutdown if 'owncloud.service' is not available
Requires=owncloud.service

[Timer]
# Explicitly declare service that this timer is responsible for
Unit=owncloud-cron.service
# Runs 'owncloud-cron' relative to when the *timer-unit* has been activated
OnActiveSec=15min
# Runs 'owncloud-cron' relative to when *service-unit* was last deactivated
OnUnitInactiveSec=15min

[Install]
WantedBy=timers.target

The important parts in the timer-unit are OnActiveSec, and OnUnitInactiveSec 2. The former setting will activate ‘owncloud-cron.service’ (15min, in this case) after the timer has been activated. The latter setting will schedule ‘owncloud-cron.service’ every 15min after the service has been inactive.

This way, the timer-unit will run ‘owncloud-cron.service’ even after the host machine has been rebooted.

If, and only if, owncloud.service is running, the timer-unit schedules a owncloud-cron.service to run every 15 minutes:

# /etc/systemd/system/owncloud-cron.service
[Unit]
Description=Cronjob for owncloud.service
Requires=owncloud.service
After=owncloud.service

[Service]
Type=oneshot
# Execute cron inside the 'owncloud.service' container as user 'apache'
ExecStart=/bin/docker exec -u apache owncloud.service /bin/php /var/www/owncloud/cron.php

The owncloud-cron.service simply runs cron.php inside the container. The container can be identified by the name (owncloud.service) we gave it earlier on. For convenience, the service requires owncloud.service, so it would attempt to start it in case it is not running, yet.

Service dependencies in action

Now that we have the configuration in place, reload the systemctl daemon, so it picks up the changes we have made, and start owncloud.service.

systemctl daemon-reload && systemctl start owncloud

Use the systemctl list-timers command line tool to verify the timer is running now as well:

systemd list-timers output

Getting information about timer units

As you can see, the timer-unit was activated automatically, without any need to manually do so. You can see exactly when a job was executed last, and when it will be called next time.

Whenever you shutdown owncloud.service, it’s dependent timer-unit(s) will be deactivated, too. Active cron-tasks will always (attempt to) complete, before owncloud.service is shut down.

Systemd’s built-in dependency management ensures cron-jobs only get scheduled, if the services they rely on are actually active and running. Only one “cron-job” will be running at any given point in time.

Maybe someone has a better idea of implementation, but right now I’m quite happy with the working solution timer-units offer.


  1. Alternatively, if you are using the example configuration files by the letter, just type docker exec -u apache owncloud.service php /var/www/owncloud/occ background:cron.
  2. Thanks to Alexander Groß for pointing out the correct conditions for triggering the cron-service.