lexik/cron-file-generator-bundle
Lexik CronFileGeneratorBundle generates cron files from your Symfony console commands, letting you define scheduled tasks in configuration and export them to a crontab-ready file.
This is a Symfony bundle, not a Laravel package — there is no service provider, no config/cron.php, and it will not install cleanly into a Laravel app. Treat this assessment as (a) how you'd use it if you touch a Symfony service, and (b) the pattern to steal for Laravel, where schedule:run is not an option (cron-only hosts, immutable containers, no long-running scheduler daemon).
As of 1.4.0 the bundle dropped its own env option (PR #16) — the rationale being that Symfony's Application already provides a global --env flag, so the bundle defining its own was redundant. Practical effect: bin/console lexik:cron:dump --env=prod still works (it's Symfony's option, not the bundle's), but re-diff the generated file after upgrading, because anything the bundle used to append from that option no longer appears on the emitted lines.
Minimal steps in a Symfony app:
composer require lexik/cron-file-generator-bundle
# Flex registers it; otherwise add to config/bundles.php
# config/packages/lexik_cron_file_generator.yaml
lexik_cron_file_generator:
output_file: '%kernel.project_dir%/var/crontab'
php_path: '/usr/bin/php8.3'
console_path: '%kernel.project_dir%/bin/console'
log_path: '%kernel.project_dir%/var/log'
# 'environment' was dropped in 1.4.0 — delete it if you have it, or the
# config tree may reject the key on upgrade.
cron:
- { name: 'cleanup', command: 'app:cleanup', expression: '0 3 * * *' }
- { name: 'import', command: 'app:import --force', expression: '*/15 * * * *' }
bin/console lexik:cron:dump --env=prod # writes output_file
Where to look first, in this order:
DependencyInjection/Configuration.php — the authoritative config tree. This is where the 1.4.0 change lands, and option names differ subtly between versions; check it before copying any YAML from a blog post.Command/DumpCronCommand.php — CLI flags and exit codes, so you can wire it into deploy scripts. After 1.4.0 there's one fewer flag here than older docs show.Service/CronFileGenerator.php — the exact line format it emits; this is the bit you'd reimplement in Laravel.First use case: stop hand-editing crontabs on a box where every app:foo invocation must be documented, logged, and versioned in Git. The dumped file becomes an artifact you diff in CI.
Deploy-time dump, not runtime. Never generate the crontab from a web request or the app's own scheduler. Add it as a post-deploy step:
bin/console lexik:cron:dump --env=prod
crontab var/crontab # or: cp var/crontab /etc/cron.d/myapp (see gotchas)
Per-environment artifacts. Dump crontab.prod and crontab.staging with --env, keep both in the release tarball, install the right one per host. This is the main reason to use the bundle over a hardcoded file: one YAML source, N environments. Note that since 1.4.0 the env value is no longer a bundle-level config concern — it flows through Symfony's global --env, so the runtime environment the cron job actually boots into comes from the server environment (or a readable .env), not from the dump invocation. Verify on staging before you trust it in prod.
Docker/immutable images. Run the dump during image build (RUN bin/console lexik:cron:dump --env=prod), COPY the result in, and have the entrypoint install it. Because paths come from %kernel.project_dir%, build and runtime paths must match or you'll bake in a path that doesn't exist.
Per-job log files. Give noisy jobs their own log_file so failures are greppable; otherwise everything lands in the shared log_path. Generated lines typically look like:
*/15 * * * * /usr/bin/php8.3 /app/bin/console app:import --force >> /app/var/log/cron_import.log 2>&1
Upgrade recipe for 1.4.0. Treat it as a potential breaking change even though the version bump is minor:
# before
bin/console lexik:cron:dump --env=prod > /tmp/crontab.before
composer update lexik/cron-file-generator-bundle
# after
bin/console lexik:cron:dump --env=prod > /tmp/crontab.after
diff -u /tmp/crontab.before /tmp/crontab.after
If the diff touches every line, the env suffix changed and your installed crontab has been silently stale since the upgrade. Reinstall and re-baseline.
Call the service directly if you need the string in another command (e.g. a deploy:check command that asserts the installed crontab matches what would be dumped — a cheap drift detector).
Laravel port (the realistic path for you). Copy the format, not the code. A ~60-line cron:dump Artisan command reading config/cron.php:
foreach (config('cron.jobs') as $job) {
$line = sprintf('%s %s %s %s >> %s 2>&1',
$job['expression'], PHP_BINARY, base_path('artisan'), $job['command'], $job['log']);
$lines[] = $line;
}
file_put_contents(base_path('var/crontab'), implode(PHP_EOL, $lines).PHP_EOL);
Then one real crontab entry (* * * * * php artisan schedule:run) or the full generated file, depending on your constraint. You get the same Git-diffable artifact without the Symfony dependency — and you skip the config-tree churn that just bit the upstream users.
env option. If your YAML sets environment:, drop it and clear the container cache (bin/console cache:clear) — a leftover key can fail config validation or, worse, be silently ignored depending on your Symfony version. Also delete any internal doc/code that references a bundle-specific env flag; the surviving flag is Symfony's global one.composer.json (e.g. ^1.4.0) so upgrades are deliberate./etc/cron.d/* files require a user column between the schedule and the command; a user crontab does not. Install the dumped file with crontab <file> (user format) unless you verify the generator emits a user field. Mixing these up silently produces zero-run crontabs with no error.PATH is roughly /usr/bin:/bin. Use the full PHP binary path (which php on the target host, not your dev box) — version managers (phpbrew, asdf, Herd) will bite you in prod.%kernel.project_dir% is resolved when the dump runs, so container path ≠ host path is a real failure mode. Assert the binary exists in your deploy pipeline.output_file at a crontab that humans also edit — someone's MAILTO header or manual entry will vanish on the next deploy.mkdir for you, and cron swallows the error.*/15 schedule will stack. Wrap the command yourself: flock -n /tmp/app-import.lock php ....> are passed through largely verbatim; you're responsible for escaping. Quote paths that could contain spaces..env nuance. --env=prod selects the environment for the dump, not for the job. The runtime env still comes from the server environment (or a .env the cron user can read). Don't assume your dev .env applies.symfony/config into a Laravel container — you'd have inherited this upgrade's diff for no benefit.cron:diff command that compares installed crontab to a fresh dump catches "someone hotfixed prod" before the next deploy silently reverts it — and is the fastest way to see what 1.4.0 actually changed on your hosts.How can I help you explore Laravel packages today?