DocsApplications
Scheduled jobs
Run scheduled (cron) or manual commands in the context of an application, managed database, or compose stack — migrations, cleanup, reports.
- Scope
- Applications, databases, compose stacks
- Target
- In the container · Separate container
- History
- Last 50 runs, 64 KB output
Three kinds of job owner
| Owner | Where | In the container | Separate container |
|---|---|---|---|
| Application | Application's Jobs tab | exec in the application container | Current application image + application env & mounts |
| Managed database | Database's Jobs tab | exec in the database container | The engine image (psql/mysql/redis-cli available) with env DB_HOST DB_PORT DB_USER DB_PASSWORD DB_NAME — same as the backup recipes |
| Compose stack | Stack's Jobs tab | exec in the chosen service container | That service's image & env |
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "VACUUM ANALYZE"Two execution targets
| Target | How it runs | Consequences |
|---|---|---|
| In the container | docker exec in the running application container. | Shares CPU/RAM with the application. Fails if the application is stopped. |
| Separate container | A one-shot container from the current image with the same env, mounts, and resource limits; removed once it's done. | Isolated. Not visible to monitoring/container-down notifications. |
Creating a job
Open the Jobs tab → New job
Field Details Name The job's label. Schedule (cron) 5 fields, e.g. 0 3 * * *. Leave empty for a manual-only job. Validated on save (400 if invalid).Command Run through sh -c— pipes,&&, and quotes are all fine.Target In the container / Separate container. Timeout (seconds) Default 600. Enforced inside the container. Run it manually to try it out
Click Run. Run history appears when the job row is expanded: status, exit code, duration, and output. The page refreshes every 2 seconds while a run is in progress.
Turn on the schedule
Jobs with a cron have a Schedule on switch on the row; turn it off to pause the automatic schedule (Run still works manually). Jobs without a cron show no switch. The schedule is registered immediately, no API restart needed; the timezone follows the API container (UTC unless
TZis set in.env.dist; the form shows the zone in use).
Example jobs
| Job | Cron | Command | Target |
|---|---|---|---|
| DB migration after deploy | (manual) | npm run migrate | Separate container |
| Clean up temp files | 0 3 * * * | find /app/tmp -mtime +7 -delete | In the container |
| Daily report | 0 7 * * 1-5 | node scripts/report.js | Separate container |
| Warm cache every 15 minutes | */15 * * * * | curl -fsS http://127.0.0.1:3000/warm | In the container |
┌─ minute (0-59)
│ ┌─ hour (0-23)
│ │ ┌─ day of month (1-31)
│ │ │ ┌─ month (1-12)
│ │ │ │ ┌─ day of week (0-6, 0 = Sunday)
* * * * *
*/15 * * * * every 15 minutes
0 3 * * * every day at 03:00
0 0 * * 0 every Sunday at 00:00Behavior
- A cron tick is skipped (not queued) while the previous run is still going. Running manually while a run is active → 409.
- Run status:
success(exit 0) /failed(exit ≠ 0) /timeout. A failure or timeout sends ajobFailurenotification if enabled. - Stdout+stderr output is kept up to 64 KB per run; the last 50 runs per job. The UI shows the last 20.
- A new deploy doesn't stop a run in progress in a Separate container; a run In the container dies along with the old container.
- For applications in service mode (Swarm), target In the container execs into a task that exists on the host — the Engine API can't exec into a task on another node.
How timeouts work
The timeout is enforced inside the container via the timeout command (if present in the image), because an exec can't be killed from the outside. The API only gives up waiting after timeout + 30 seconds.
timeout (e.g. distroless) can't bound the duration: a run is only marked timeout once the API stops waiting, while the command may still be running. Include coreutils or busybox in the image if a job might run long.Common pitfalls
| Symptom | Cause & fix |
|---|---|
| Run fails: container not running | Target is In the container but the application is stopped. Use Separate container, or start the application. |
| command not found | The image doesn't have that binary (e.g. curl on minimal alpine). Use wget, or add it to the Dockerfile. |
| Schedule doesn't run at the expected time | The API runs in UTC unless TZ is set in .env.dist (e.g. TZ=Asia/Jakarta, then recreate the api container). The form shows the zone in use; otherwise offset the hour. |
| Job stays failed even though the command succeeded | The last exit code was ≠ 0 — e.g. grep with no matches. End with || true if that's actually fine. |
Next steps
Found a mistake? Edit this page on GitHub ↗