Scheduled Jobs (App Cron)
Scheduled jobs run a command on a cron schedule inside your application's own
container — with its image, its environment variables, and its code. It's the
app-level equivalent of Heroku Scheduler or Render Cron Jobs, ideal for things
like php artisan schedule:run, a nightly cleanup, a digest email, or a
periodic import.
Scheduled jobs vs. daemons vs. server cron
| Scheduled jobs | Background workers (daemons) | Server cron jobs | |
|---|---|---|---|
| Runs in | App container (one-shot per fire) | App container (long-running) | Host server |
| Lifecycle | Fires on a schedule, exits | Always running (restart policy) | Host crontab entry |
| Has your app's env | ✅ | ✅ | ❌ (host only) |
| Good for | schedule:run, cleanups, reports | queues, schedulers, websockets | Docker/host maintenance |
Add a scheduled job
- Open your app → the Scheduled tab.
- Add job and fill in:
- Name — a label (e.g.
nightly-cleanup). - Command — what to run, e.g.
php artisan schedule:run. It runs inside the container viabash -lc, so your runtime (php/artisan, python, node) resolves exactly as it does for the app. - Schedule — a standard 5-field cron expression, in UTC. Use a preset or type your own.
- Name — a label (e.g.
- Save. The job runs automatically at its scheduled times. Use the play icon to run it once immediately (handy for testing).
Schedule format
Schedules are standard 5-field cron, evaluated in UTC:
| Expression | Runs |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour, on the hour |
0 3 * * * | Every day at 03:00 UTC |
0 3 * * 0 | Every Sunday at 03:00 UTC |
0 3 1 * * | The 1st of each month at 03:00 UTC |
Times are UTC, not your local timezone. 0 3 * * * is 03:00 UTC — convert
from your zone when picking a time (e.g. WIB is UTC+7, so 10:00 WIB = 0 3 * * *).
How it runs
Every minute, Vessl checks which enabled jobs are due and runs each one with
docker exec inside the app's currently running container. The command's
exit code and output tail are recorded and shown per job (success /
failed / skipped).
- A run is skipped (not failed) when the app is stopped or mid-deploy — there's no running container to exec into. It runs normally at the next scheduled time once the app is active again.
- A run is failed when the command exits non-zero. The last output tail is saved so you can see why.
- The command runs inside your container (its own filesystem, env, and code) — it cannot reach the host or other tenants.
Notes & limits
- The app must be deployed and running for a job to fire (a stopped app skips its jobs).
- The image needs a shell (
bash) for the command — standard language images (PHP, Node, Python) have one; minimalscratch/distroless images do not. - Schedules are UTC. Overlap protection: a slow run won't stack on top of the next minute's fire.
See also: Background Workers · Server Cron Jobs · Run a Command / Console.