DEV Community

Dawid Spiechowicz
Dawid Spiechowicz

Posted on

How to run Laravel scheduler in Docker container

There is nothing easier than adding
* * * * * cd /your-project && php artisan schedule:run
to crontab according to Laravel Docs, right?

Have you tried to containerize it with Docker? You probably have. Traditional approach is to just apt install cron and add crontab entry to some /etc/cron.d/app. It will work, but there are some issues with this approach:

  • main cron process runs on root user
  • often there are problems with missing logs

We will address those issues starting with minimal Dockerfile with some good Docker practices included:

# syntax=docker/dockerfile:1

# Normally you inherit from your Laravel app image
FROM php:8.3-fpm AS cron

# Use 'root' user only when you need to
USER root

# Install dependencies, make sure to add '--no-install-recommends' flag
RUN <<EOT bash
    set -e
    apt update
    apt install -y curl --no-install-recommends
    rm -rf /var/lib/apt/lists/*
EOT

# Latest releases available at https://github.com/aptible/supercronic/releases
ARG SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.29
ARG SUPERCRONIC=supercronic-linux-amd64
ARG SUPERCRONIC_SHA1SUM=cd48d45c4b10f3f0bfdd3a57d054cd05ac96812b

# Install Supercronic instead of typical 'apt install cron'
RUN <<EOT bash
    set -e
    curl -fsSLO "{$SUPERCRONIC_URL}/${SUPERCRONIC}"
    echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c -
    chmod +x "${SUPERCRONIC}"
    mv "${SUPERCRONIC}" "/usr/local/bin/${SUPERCRONIC}"
    ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic
EOT

# Entrypoint file 'entrypoint.sh' is common for Webserver, Cron and Horizon Docker images
COPY --chown=www-data:www-data --chmod=744 entrypoint.sh /entrypoint.sh

# CMD for 'cron' image is 'cmd-cron.sh'
COPY --chown=www-data:www-data --chmod=744 cmd-cron.sh /cmd.sh

# Switch to application user provided by 'php:8.3-fpm' image
USER www-data

# Create directory with 'www-data' ownership
WORKDIR /app

# Run Laravel Schedule on every minute
COPY --chown=www-data:www-data --chmod=644 <<EOT crontab
*/1 * * * * cd /app && php artisan schedule:run --no-ansi
EOT

ENTRYPOINT ["/entrypoint.sh"]
CMD ["/cmd.sh"]
Enter fullscreen mode Exit fullscreen mode

I have intentionally skipped some stuff related to PHP extensions and Composer just to focus on cron-related tasks.

Currently docker build will obviously not work because of missing entrypoint.sh and cmd-cron.sh.

Minimal entrypoint.sh can be something like this:

#!/usr/bin/env bash

set -e

echo "Checking app configuration"
# Your entrypoint tasks

echo "Clearing app cache"
echo "php artisan config:cache --no-ansi"
# Other tasks

exec /usr/local/bin/docker-php-entrypoint "$@"
Enter fullscreen mode Exit fullscreen mode

Finally cron-cmd.sh, a little hacky, but working like a charm:

#!/usr/bin/env bash

set -e

supercronic_pid=0

sig_handler() {
  if [ $supercronic_pid -ne 0 ]; then
    # Notify Laravel Schedule to not take any more commands to run
    echo "php /app/artisan schedule:interrupt --no-ansi"

    # Notify Supercronic it should stop working
    kill -SIGTERM "$supercronic_pid"
    wait "$supercronic_pid"
  fi

  # Exit 143 in Docker world means container terminated gracefully
  exit 143
}

# Setup signal trap
trap 'sig_handler' SIGTERM SIGQUIT SIGINT

# Remove any open Laravel Schedule locks on startup
echo "php /app/artisan schedule:clear-cache --no-ansi"

# Spawn Supercronic process in the background
supercronic crontab &

# And catch its PID into variable
supercronic_pid="$!"

# Supercronic should never exit on its own
wait "$supercronic_pid"

# If it does, it means an abnormal exit occurred
exit 1
Enter fullscreen mode Exit fullscreen mode

You can notice two Laravel artisan commands here:

  • schedule:clear-cache to remove any locks created by withoutOverlapping() method
  • schedule:interrupt to notify Laravel scheduler about incoming container termination

You cannot simply exec supercronic crontab in cron-cmd.sh, because you will stop receiving termination signals. exec in bash will replace current process PID, and would change to supercronic in this case.

Top comments (2)

Collapse
 
Sloan, the sloth mascot
Comment deleted