$ aoox

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

OwnerWhereIn the containerSeparate container
ApplicationApplication's Jobs tabexec in the application containerCurrent application image + application env & mounts
Managed databaseDatabase's Jobs tabexec in the database containerThe 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 stackStack's Jobs tabexec in the chosen service containerThat service's image & env
Example database job (Separate container, PostgreSQL)
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "VACUUM ANALYZE"

Two execution targets

TargetHow it runsConsequences
In the containerdocker exec in the running application container.Shares CPU/RAM with the application. Fails if the application is stopped.
Separate containerA 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.
Need state from the running process (in-memory cache, a signal)? → In the container
Heavy, long-running, or must not disturb the main process (migration, batch)? → Separate container
Application is stopped but the job still needs to run? → Separate container

Creating a job

  1. Open the Jobs tab → New job

    FieldDetails
    NameThe job's label.
    Schedule (cron)5 fields, e.g. 0 3 * * *. Leave empty for a manual-only job. Validated on save (400 if invalid).
    CommandRun through sh -c — pipes, &&, and quotes are all fine.
    TargetIn the container / Separate container.
    Timeout (seconds)Default 600. Enforced inside the container.
  2. 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.

  3. 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 TZ is set in .env.dist; the form shows the zone in use).

Example jobs

JobCronCommandTarget
DB migration after deploy(manual)npm run migrateSeparate container
Clean up temp files0 3 * * *find /app/tmp -mtime +7 -deleteIn the container
Daily report0 7 * * 1-5node scripts/report.jsSeparate container
Warm cache every 15 minutes*/15 * * * *curl -fsS http://127.0.0.1:3000/warmIn the container
5-field cron
┌─ 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:00

Behavior

  • 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 a jobFailure notification 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.

An image without 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

SymptomCause & fix
Run fails: container not runningTarget is In the container but the application is stopped. Use Separate container, or start the application.
command not foundThe 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 timeThe 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 succeededThe last exit code was ≠ 0 — e.g. grep with no matches. End with || true if that's actually fine.
Not available yet
Streaming output while a run is in progress; history beyond 20 in the UI.

Next steps

Found a mistake? Edit this page on GitHub ↗