Chris
Chris White Web Developer

Laravel Scheduler CRON Job on Elastic Beanstalk Docker Environments

28 April 2017 ~2 minute read

This week I had to set up something which I assumed would be trivial, but turned out to be a little more involved. So I’m jotting it down here to refer to later, or help anybody who might stumble upon it from a frantic Google search.

One of the projects I’m working on right now has a staging environment on AWS Elastic Beanstalk, using a multi-container docker configuration. It’s a Laravel application, and it has some scheduled tasks that run every 1 and 24 hours. If you’re familiar with Laravel’s scheduler, you’ll know that you have to set up a single Cron job that runs every minute. The scheduler then takes care of running each task based on its defined schedule.

First thing’s first, since we’re in a Docker environment on Elastic Beanstalk we need to build an artisan Docker image to run our schedule:run command. I used the following Dockerfile.

1FROM php:7.1-cli
2 
3RUN apt-get update && apt-get install -y libmcrypt-dev
4RUN docker-php-ext-install pdo_mysql mbstring mcrypt
5 
6RUN usermod -u 1000 www-data
7WORKDIR /var/www/html
8ENTRYPOINT ["php", "artisan"]

Build it, tag it, and push it to Elastic Container Registry.

1docker build -t yourproject/artisan .
2docker tag yourproject/artisan:latest 132274109557.dkr.ecr.eu-west-1.amazonaws.com/yourproject/artisan:latest
3docker push 132274109557.dkr.ecr.eu-west-1.amazonaws.com/yourproject/artisan:latest

Now we have an artisan image that we can use to run our schedule:run command in our cronjob. The next step is creating the crontab file.

If you’ve never used an .ebextensions directory with your Elastic Beanstalk app, it’s essentially just a place to put configuration files that are acted upon by Elastic Beanstalk when it deploys your codebase.

Create a new .ebextensions directory in the root of your project, and create a file inside it called cronjob.config. Paste the following contents, replacing the ECR URL with your actual repository URL.

1files:
2 "/etc/cron.d/scheduler":
3 mode: "000644"
4 owner: root
5 group: root
6 content: |
7 #* * * * * root docker run --rm -v /var/app/current:/var/www/html 132274109557.dkr.ecr.eu-west-1.amazonaws.com/yourproject/artisan:latest schedule:run >> /dev/null 2>&1
8 
9container_commands:
10 001-uncomment-cron:
11 leader_only: true
12command: "sed -i -e 's/#//' /etc/cron.d/scheduler"

Deploy your app, and you should now have a working scheduler. It’s probably also a good idea to add some logging to your scheduled commands, so that you can be sure they are indeed running. 😉

If you’re curious about why I’m deploying a commented out crontab file and then using a container command to remove the comment – it’s because Elastic Beanstalk by nature will deploy the same crontab file across the entire fleet of servers running in your environment. The problem is that the scheduler should only be running on one server to avoid tricky race conditions. To get around this, we deploy a commented out crontab file to every server, and then have the lead server uncomment its crontab file using a sed call.

In Elastic Beanstalk terms, the “lead server” is the one that is coordinating the deployment of your application. Elastic Beanstalk selects this server from your fleet when you deploy your app.

Made with Jigsaw and Torchlight.