DEV Community

Cover image for Setting up dockerized cron jobs: example using PHP and MySQL
yactouat
yactouat

Posted on • Updated on

Setting up dockerized cron jobs: example using PHP and MySQL

Hello, wondering how to run cron jobs in your dockerized environment ? It's actually quite easy !

Let's follow a few simple steps with a dummy program that will use PHP to insert rows in a MySQL table.
Neither the program nor the database structure are relevant in this example, they are just vehicles to set up a minimal environment focusing on just one thing: running a cron on Docker.
So let's get started and create a new project in your IDE !

pre requisites

  • have a working standard Docker installation on your system

the PHP code

Contained in one script, our code to insert a row in a predefined table looks like this, let's call it test_cron.php and put it at the root of our project =>

<?php

echo "connecting to DB".PHP_EOL;

if( in_array ('pdo_mysql', get_loaded_extensions())) {
    // ! this code is bad, credentials are not read from a file or from env variables !
    $dsn = "mysql:host=db;dbname=test_db";
    $opt = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC];
    echo "connected to DB".PHP_EOL;
    try {
        $connection = new PDO($dsn, "root", '', $opt);

        $val = 'testStr';
        // nothing is dynamic here, this code really sucks !
        $sql = 'INSERT INTO test_table(test_field) VALUES(:val)';   
        $statement = $connection->prepare($sql);
        $statement->execute([
            ':val' => $val
        ]);   

    } catch (\PDOException $pdoe) {
        echo $pdoe->getMessage();
    }
}
Enter fullscreen mode Exit fullscreen mode

... well, you get the idea, comments speak for themselves :)

So this is the script that we will run periodically in our cron.
But to be able to run it, we must a have a database and an existing test_table to write to.

the test SQL table

Let's write the SQL script to create our test table, the database creation will be covered in the later docker-compose part of this article. Let's call this file table.sql and put it as well in the root of our project.

CREATE TABLE IF NOT EXISTS `test_table` (
  `id` MEDIUMINT NOT NULL AUTO_INCREMENT,
  `test_field` varchar(255)
) ENGINE=InnoDB;
Enter fullscreen mode Exit fullscreen mode

Now, let's get to the cron configuration part.

running the cron tab

On a Linux system that has the cron package installed, each user has a crontab. The crontab is a file that represents a tasks scheduler, the user would just write in there what tasks need to be run periodically. There is a syntax to define the frequency of these tasks. To get a better feel at it, I would recommend checking the awesome https://crontab.guru/ website !

So let's write this crontab that we will use, in the next step, in a Docker image definition. Just create a file called crontab, with no extension, at the root of your project.

# run the test script every minute and output whatever it echoes in a log file
# using `>` instead of `>>` to append a new line at each cron iteration
* * * * * /usr/local/bin/php /cron_scripts/test_cron.php >> /cron_scripts/test_cron.log 2>&1
Enter fullscreen mode Exit fullscreen mode

(thx to @bhargavmoradiya for pointing out the >> instead of > for the output file)

(also thx to @catalinul who pointed out => "when you create/edit/save the crontab file be sure to use LF and not CRLF, on Visual Code see this in the bottom right corner")

the Dockerfile to run the PHP cron

Now let's define our Docker image to use to run our script on a schedule. This is to be called Dockerfile and, guess what ? it lives at the root of the project as well :) let's keep it simple !

FROM php:8.1-cli

# installing cron package
RUN apt-get update && apt-get -y install cron

# installing PHP PDO extension to talk to MySQL
RUN docker-php-ext-install pdo_mysql

# putting our test PHP script somewhere in the filesystem
RUN mkdir /cron_scripts
WORKDIR /cron_scripts
COPY test_cron.php /cron_scripts

# creating the log file that will be written to at each cron iteration
RUN touch test_cron.log

# copy the crontab in a location where it will be parsed by the system
COPY ./crontab /etc/cron.d/crontab
# owner can read and write into the crontab, group and others can read it
RUN chmod 0644 /etc/cron.d/crontab
# running our crontab using the binary from the package we installed
RUN /usr/bin/crontab /etc/cron.d/crontab
Enter fullscreen mode Exit fullscreen mode

Now, there is only one piece of the puzzle that is missing to fit all this together: a docker-compose.yml file to orchestrate all of this !

docker compose

# persisting db data in volume
volumes:
  db-vol:

services:

  # We have 4 services: the database, the db seeder, a cron that writes in db, and phpmyadmin to see the results of our running cron in a friendly UI

  db:
    image: mysql:latest
    container_name: db
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "true"
      # this is the name of our database, that will be created automatically
      MYSQL_DATABASE: test_db
    restart: unless-stopped
    volumes:
      - db-vol:/var/lib/mysql
    ports:
      - "3306:3306"

  # we'll use a seeder container to create our test table, that our scheduled PHP script will write to
  db_seeder:
    image: mysql:latest
    # we copy our table creation script into the container
    volumes:
      - ./table.sql:/table.sql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "true"
    # we give the db time to initialize then we import our SQL script
    entrypoint: [ "bash", "-c", "sleep 10 && mysql --user=root --host=db --port=3306 test_db < /table.sql && exit"] 
    depends_on:
      - db

  # this is the container that will execute our cron
  cron:
    build:
      context: ./
      dockerfile: ./Dockerfile
    # run crond as main process of container
    entrypoint: [ "bash", "-c", "cron -f"] 
    depends_on:
      - db_seeder

  # our nice UI to browse our test table
  phpmyadmin:
      image: phpmyadmin:latest
      restart: unless-stopped
      ports:
          - 8080:80
      environment:
          # we specify that we connect to an arbitrary server with the flag below
          # "arbitrary" means you're able to specify which database server to use on login page of phpmyadmin      
          - PMA_ARBITRARY=1
      depends_on:
        - db_seeder
Enter fullscreen mode Exit fullscreen mode

Let's docker compose up this thing !

see that everything works fine

After all containers have been fired up and the db seeder has exited with code 0 (meaning seeding the db went fine); let's check if our table gets written at the defined schedule.

To do that, let's check out our phpmyadmin instance to browse our table. We specified in our docker-compose.yml file that our phpmyadmin will be accessible on port 8080 on our host machine, so head to http://localhost:8080; server is db, user is root and we have no password set up.

Once in, you'll see on the left hand side of the UI the test table we created, click on it and browse it: if you refresh the page every minute, you will see that a new row is added 😎 .

Now, let's check out our cron log file that we copied in our container, go the test_cron.log file: it should be updated every minute !

Now you have learned a great superpower: automate tasks on a schedule in a Linux Dockerized environment; this can have an infinity of use-cases other than inserting rows in a db table, your imagination is the limit.

You can find the code of this example on https://github.com/yactouat/docker-cron-example

I hope you enjoyed this, see ya 👋

Top comments (4)

Collapse
 
catalinul profile image
Catalin

Nice, this really helped me! I just want to add one more thing, this is very important, when you create/edit/save the crontab file be sure to use LF and not CRLF, on Visual Code see this in the bottom right corner. This if you are on a machine with Windows.

CRLF works on Windows but does not work on Linux, wasted good time having no idea why my cron file was not running.

Collapse
 
yactouat profile image
yactouat

thanks for your feedback @catalinul , updating the article right away !

Collapse
 
bhargavmoradiya profile image
BhaRgav-MoRadiya

Awesome article..thanks a lot.
1 issue i found is to append to log file using >> instead of overriding with >.for some time i thought its not working.but then realised that file was continuously being overwritten.

# before
* * * * * /usr/local/bin/php /cron_scripts/test_cron.php > /cron_scripts/test_cron.log 2>&1

#after
* * * * * /usr/local/bin/php /cron_scripts/test_cron.php >> /cron_scripts/test_cron.log 2>&1
Enter fullscreen mode Exit fullscreen mode
Collapse
 
yactouat profile image
yactouat

hey @bhargavmoradiya , thanks a lot for pointing this out ! I will update the article and the git repo