<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Dawid Spiechowicz</title>
    <description>The latest articles on Forem by Dawid Spiechowicz (@spiechu).</description>
    <link>https://forem.com/spiechu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3104%2F673912.jpeg</url>
      <title>Forem: Dawid Spiechowicz</title>
      <link>https://forem.com/spiechu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/spiechu"/>
    <language>en</language>
    <item>
      <title>How to run Laravel scheduler in Docker container</title>
      <dc:creator>Dawid Spiechowicz</dc:creator>
      <pubDate>Sun, 10 Mar 2024 21:24:39 +0000</pubDate>
      <link>https://forem.com/spiechu/how-to-run-laravel-scheduler-in-docker-container-d1p</link>
      <guid>https://forem.com/spiechu/how-to-run-laravel-scheduler-in-docker-container-d1p</guid>
      <description>&lt;p&gt;There is nothing easier than adding&lt;br&gt;
&lt;code&gt;* * * * * cd /your-project &amp;amp;&amp;amp; php artisan schedule:run&lt;/code&gt;&lt;br&gt;
to crontab according to &lt;a href="https://laravel.com/docs/10.x/scheduling#running-the-scheduler"&gt;Laravel Docs&lt;/a&gt;, right?&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;main cron process runs on &lt;code&gt;root&lt;/code&gt; user&lt;/li&gt;
&lt;li&gt;often there are problems with missing logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will address those issues starting with minimal &lt;code&gt;Dockerfile&lt;/code&gt; with some good Docker practices included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 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 &amp;lt;&amp;lt;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 &amp;lt;&amp;lt;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 &amp;lt;&amp;lt;EOT crontab
*/1 * * * * cd /app &amp;amp;&amp;amp; php artisan schedule:run --no-ansi
EOT

ENTRYPOINT ["/entrypoint.sh"]
CMD ["/cmd.sh"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have intentionally skipped some stuff related to PHP extensions and Composer just to focus on cron-related tasks.&lt;/p&gt;

&lt;p&gt;Currently &lt;code&gt;docker build&lt;/code&gt; will obviously not work because of missing &lt;code&gt;entrypoint.sh&lt;/code&gt; and &lt;code&gt;cmd-cron.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Minimal &lt;code&gt;entrypoint.sh&lt;/code&gt; can be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/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 "$@"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally &lt;code&gt;cron-cmd.sh&lt;/code&gt;, a little hacky, but working like a charm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/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 &amp;amp;

# 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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can notice two Laravel artisan commands here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;schedule:clear-cache&lt;/code&gt; to remove any locks created by &lt;code&gt;withoutOverlapping()&lt;/code&gt; &lt;a href="https://laravel.com/docs/10.x/scheduling#preventing-task-overlaps"&gt;method&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schedule:interrupt&lt;/code&gt; to notify Laravel scheduler about incoming container termination&lt;/li&gt;
&lt;/ul&gt;

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

</description>
      <category>laravel</category>
      <category>docker</category>
      <category>cron</category>
      <category>bash</category>
    </item>
  </channel>
</rss>
