Running Laravel Cron Jobs in Docker with FrankenPHP
10/12/2025
Yesterday I spent some time digging into how cron jobs work in Laravel. There are two fairly straightforward commands involved:
php artisan schedule:run
php artisan schedule:work
The idea is that the first command should be placed into the actual system crontab in whatever environment runs your backend (or any environment that has the required environment variables and access to the database).
Your crontab entry usually looks something like this:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
This means the command runs every minute.
The idea behind this approach is to delegate the scheduling logic to PHP and Laravel. When this command runs, Laravel checks app/Console/Kernel.php for registered scheduled tasks. They typically look something like this:
$schedule->command('emails:send Taylor --force')->daily();
In this case, ->daily() means the command is expected to run once per day.
What does schedule:work do?
According to the documentation, the second command (schedule:work) is intended only for local development.
All it really does is run schedule:run every minute, but implemented in PHP using an infinite loop.
You can see the original implementation here:
https://github.com/laravel/framework/pull/34618/files
Modern Laravel versions have a much more complex implementation, but that initial pull request still gives a very clear understanding of how the mechanism works.
My setup: Docker + FrankenPHP
I run my backend application in Docker using docker-compose.
The base image is dunglas/frankenphp.
Here’s how the service is defined in docker-compose.yml:
backend-cron:
restart: unless-stopped
build:
target: dev
volumes:
- .:/app
command: "frankenphp php-cli artisan schedule:work"
healthcheck:
disable: true
env_file:
.env
Every minute I was getting the same error:
backend-cron-1 | 2025-12-09T14:54:00.043306279Z sh: 1: : Permission denied
backend-cron-1 | 2025-12-09T14:55:00.041940906Z sh: 1: : Permission denied
At first, based on past experience, I assumed the script simply didn't have permission to write to the filesystem. This is a common Laravel issue when running inside Docker, usually related to the storage and bootstrap/cache directories.
But that wasn’t the case here.
The actual problem
It turned out that FrankenPHP cannot spawn subprocesses in this setup for some reason.
I tried playing around with setcap in the Docker image, but eventually gave up and just wrote a simple bash script that replicates what schedule:work does:
#!/bin/sh
set -e
echo "Starting schedule loop..."
while true; do
# Wait until the start of the next minute
sleep $((60 - $(date +%S)))
# Run the scheduler
frankenphp php-cli artisan schedule:run
done
Thoughts on FrankenPHP
Up until this point, I hadn't experienced any issues with FrankenPHP. It really worked as a drop-in replacement for the images I used before.
I haven't tried worker mode yet mostly because I expect it will require quite a bit of tweaking in the application itself to get everything running properly.