1. Overview

As system administrators, we’ll invariably encounter the need to schedule tasks. We can achieve this by using cron services in Linux systems. We can also enable cron scheduling services in container systems.

In this tutorial, we’ll discuss two different ways of enabling cron services in the Docker containers. In the first approach, we’ll embed the cron services inside the docker image using Dockerfile. For the second method, we’ll illustrate how to install the scheduling services in a container.

2. Cron Services – Using the Dockerfile Approach

Building images using Dockerfile is one of the easiest ways to create container images. So how do we do it? Basically, a Dockerfile is a simple text file that contains a set of instructions to build an image. We need to feed the scheduling task and cron details, as well as invoke the cron services from the Dockerfile.

2.1. Writing the Dockerfile

Let’s take a quick look at an example:

$ tree
.
├── Dockerfile
└── get_date.sh
0 directories, 2 files

Generally, the first line of a Dockerfile starts with the FROM command, which will take the requested image from the configured registry. In our case, the default registry is configured as DockerHub. Then comes the MAINTAINER, which is the metadata for capturing the author’s information. The ADD instruction copies the get_date.sh script from the image build path of the host machine to the destination path of the image.

After copying the script into the build image, the RUN instruction gives the executable permissions. Not only that, the RUN instruction helps to execute any shell command as a new image layer on top of the current layer, and commits the results. RUN updates the apt repository and installs the latest cron services in the image. It also performs the cron scheduling in the crontab.

Now we’ll start the cron services using the CMD instruction:

$ cat Dockerfile

# Dockerfile to create image with cron services
FROM ubuntu:latest
MAINTAINER baeldung.com

# Add the script to the Docker Image
ADD get_date.sh /root/get_date.sh

# Give execution rights on the cron scripts
RUN chmod 0644 /root/get_date.sh

#Install Cron
RUN apt-get update
RUN apt-get -y install cron

# Add the cron job
RUN crontab -l | { cat; echo "* * * * * bash /root/get_date.sh"; } | crontab -

# Run the command on container startup
CMD cron

2.2. Building and Running the Cron Image

Once the Dockerfile is ready, we can then build the image using the docker build command. The dot (.) instructs the Docker Engine to take the Dockerfile from the current path. The build command creates the docker layers for every instruction given on the Dockerfile to form the final build image. The typical build output is showcased below:

$ docker build .
Sending build context to Docker daemon  3.072kB
Step 1/8 : FROM ubuntu:latest
---> ba6acccedd29
Step 2/8 : MAINTAINER baeldung.com
---> Using cache
---> e6b3946b2382
Step 3/8 : ADD get_date.sh /root/get_date.sh
---> 4976f058d428
Step 4/8 : RUN chmod 0644 /root/get_date.sh
---> Running in 423a4e9adbab
Removing intermediate container 423a4e9adbab
---> 76d972a082ba
Step 5/8 : RUN apt-get update
---> Running in badc0d84f6ff
Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease [114 kB]
...
... output truncated ...
...
Removing intermediate container badc0d84f6ff
---> edb8a19b891c
Step 6/8 : RUN apt-get -y install cron
---> Running in efd9b8a67d98
Reading package lists...
Building dependency tree...
...
... output truncated ...
...
Done.
invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
Removing intermediate container efd9b8a67d98
---> 2b80000d32a1
Step 7/8 : RUN crontab -l | { cat; echo "* * * * * bash /root/get_date.sh"; } | crontab -
---> Running in 1bdd3e0cc877
no crontab for root
Removing intermediate container 1bdd3e0cc877
---> aa7c82aa7c11
Step 8/8 : CMD cron
---> Running in cf2d44873b36
Removing intermediate container cf2d44873b36
---> 8cee091ca87d
Successfully built 8cee091ca87d

Since we pre-installed the cron services into the image, and embedded the tasks in the crontab, the cron job gets activated automatically when we run the container. Alternatively, we can start the container using the docker run command. Subsequently, the “-it” option of docker run helps to get into the container with the bash prompt.

The below illustration shows the execution of the get_date.sh script in the container using cron:

$ docker run -it 8cee091ca87d /bin/bash
root@88f5bb1f0a08:/#
root@88f5bb1f0a08:/# date
Mon Nov 15 14:30:21 UTC 2021

root@88f5bb1f0a08:/# ls -ltrh ~/date.out
ls: cannot access '/root/date.out': No such file or directory

root@88f5bb1f0a08:/# ls -ltrh /root/get_date.sh
-rw-r--r-- 1 root root 18 Nov 15 14:20 /root/get_date.sh

root@88f5bb1f0a08:/# crontab -l
* * * * * bash /root/get_date.sh

root@88f5bb1f0a08:/# ls -ltrh ~/date.out
-rw-r--r-- 1 root root 29 Nov 15 14:31 /root/date.out

root@88f5bb1f0a08:/# cat /root/date.out
Mon Nov 15 14:31:01 UTC 2021

3. Cron Services – Live Container Approach

Alternatively, we can set up the cron services in Docker containers for running the cron jobs. So what’s the modus operandi?

Let’s quickly run an ubuntu container using the docker run command. Usually, the containers are a lightweight OS that won’t comprise cron services as their default package.

We’ll need to get into the interactive shell of the container and install the cron services using apt repository commands:

$ docker run -it ubuntu:latest /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
7b1a6ab2e44d: Pull complete
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:latest
root@77483fc20fc9:/#
root@77483fc20fc9:/# which cron
root@77483fc20fc9:/# apt update -y
Get:1 http://archive.ubuntu.com/ubuntu focal InRelease [265 kB]
...
... output truncated ...
...
All packages are up to date.
root@77483fc20fc9:/# apt upgrade -y
... 
... output truncated ...
...
root@77483fc20fc9:/# apt install cron vim -y
Reading package lists... Done
...
... output truncated ...
...
Done.
invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
root@77483fc20fc9:/# which cron
/usr/sbin/cron

$ docker cp get_data.sh 77483fc20fc9: /root/get_date.sh

We can use the docker cp command to copy get_date.sh from the host to the container. The crontab -e edits the cron job using the vi editor. The below cron configuration runs the script every minute. Furthermore, the output indicates the timestamp of the script execution:

root@77483fc20fc9:/# export EDITOR=vi
root@77483fc20fc9:/# crontab -e
* * * * * bash /root/get_date.sh

root@77483fc20fc9:/# date
Mon Nov 17 11:15:21 UTC 2021

root@77483fc20fc9:/# ls -ltrh ~/date.out
ls: cannot access '/root/date.out': No such file or directory

root@77483fc20fc9:/# ls -ltrh /root/get_date.sh
-rw-r--r-- 1 root root 18 Nov 17 11:09 /root/get_date.sh

root@77483fc20fc9:/# ls -ltrh ~/date.out
-rw-r--r-- 1 root root 29 Nov 17 11:16 /root/date.out

root@77483fc20fc9:/# cat /root/date.out
Mon Nov 17 11:16:01 UTC 2021

4. Conclusion

In this article, we explored the nuts and bolts of running cron jobs inside a Docker container. Using Dockerfile embeds the cron services and tasks into the image, which automatically executes the script in accordance with the cron schedule configuration.

Although cron can be installed and configured in a running container, it’s only a runtime construct unless we construct an image using docker commit.

Both options have their advantages depending on our usage circumstances.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.