Skip to content

Cronjobs

A cronjob runs a container on a schedule. It is the heavy-weight scheduled-job option: each run is a fresh Kubernetes CronJob pod, either built from a Git repository or run directly from a public container image. Cronjobs give you a full build environment, arbitrary languages, and their own execution history with captured logs.

A cronjob runs from one of two sources, selected by imageSource:

imageSourceWhat it doesRequired fields
githubBuilds a container from a Git repo, then runs it on schedule.repositoryName (owner/repo), plus branch, language, and build/start commands.
public_imageRuns a public image (e.g. nginx:latest) directly.publicImage; an optional command override.

The schema enforces this: repositoryName is required when imageSource is github, and publicImage is required when imageSource is public_image.

Cronjobs use a 5-field cron expression (minute, hour, day-of-month, month, day-of-week), max 120 characters — for example */5 * * * * runs every five minutes. See scheduled functions for more examples.

Each run gets a compute tier, set with instanceType:

TierNotes
microDefault.
mini
large
xlarge

For github cronjobs, language selects the build path:

LanguageValue
Node.jsnodejs (default)
Pythonpython
Gogo
Dockerdocker (uses the repo’s own Dockerfile)
FieldTypeDefaultNotes
namestring1–52 chars, RFC-1123 label. Kept short to leave room for the manual-run suffix.
imageSourceenumgithubgithub | public_image.
cronSchedulestring5-field cron, 1–120 chars.
regionstringusOnly us exists today.
instanceTypeenummicromicro | mini | large | xlarge.
repositoryNamestringowner/repo; required for github.
branchstringmain
isPublicbooleantruePrivate repos require the GitHub App installation.
languageenumnodejsnodejs | python | go | docker.
baseDirstring./
buildCmdstring''
startCmdstring''
dockerfilePathstring./Dockerfile
dockerBuildContextDirstring./
autoDeploybooleanfalse
publicImagestringe.g. nginx:latest; required for public_image.
commandstring''Optional command override for a public image.
envarray[][{ name, value }], plaintext on the wire.
secretsarray[]Stored encrypted, never returned.

The name gets a random hex suffix on create (it becomes the Kubernetes CronJob name), kept to 52 characters so the manual-run suffix fits.

Cronjobs support full CRUD plus lifecycle actions. Every mutation writes the cronjob row change and a transactional outbox row in one database transaction, which is published to worker-v3 to apply or remove the Kubernetes CronJob.

ActionWhat it doesOutbox topic
CreateProvision the cronjob and its schedule.cronjob.upsert
UpdateChange metadata and redeploy. The source and name are immutable.cronjob.upsert
PauseSuspend the schedule (stops firing).cronjob.upsert
ResumeRe-enable a paused schedule.cronjob.upsert
Run nowTrigger a manual run immediately.cronjob.upsert
DeleteRemove the cronjob and its Kubernetes CronJob.cronjob.delete

The cronjob status enum: pending · deploying · running · failed · deleting · deleted · updating · paused.

A manual run-now creates a cronjob_executions row with status: queued and a deterministic Kubernetes job name of the form <cronjob>-manual-<ts>, and returns an executionUuid you can use to follow the run:

{
"success": true,
"data": {
"executionUuid": "8f3c…",
"status": "queued"
}
}

Every run — scheduled or manual — is a cronjob_executions record:

FieldMeaning
executionUuidStable id for the run.
statusqueued | running | succeeded | failed.
startedAt / completedAtRun timing.
exitCodeProcess exit code.
errorMessageFailure detail, if any.

Captured logs are read by executionUuid. Log access is ownership-gated through the parent cronjob’s organization, so a UUID cannot be used to probe another tenant’s runs.

The cronjob itself carries aggregate run statistics: totalRuns, successfulRuns, failedRuns, lastRunAt, and nextRunAt.

Environment variables ride the deploy queue as plaintext and are returned in responses; secrets are stored encrypted and are never returned. This mirrors how projects handle environment variables and secrets.