Run a cron job as an app
A cron job is an app that runs on a schedule instead of serving on a port. You create it from a git repository, a folder on your computer or a Docker image, exactly like any other app, and pick a schedule such as "every day at 03:00". On every run Cloider starts your app's image, runs it until it exits, and keeps its status and output. Between runs nothing is running.
A cron job keeps everything an app has: builds and deploys, environment variables, build variables and build
arguments, the deploy hook of an image app, logs, the web terminal, cloider app exec, insights, pause, resume and
delete.
| Cron job | Web service | |
|---|---|---|
| Runs | On its schedule, until the command exits | All the time |
| Port, subdomain, domains | None | One port, served on your domains |
| What runs | The start command, or the image's own ENTRYPOINT and CMD | The same |
| New version | A build or deploy, picked up by the next run | A build or deploy, rolled out at once |
| Logs | Per run, for the last 5 succeeded and 5 failed runs | The running app's |
| Terminal | A shell in a running run, from the Runs tab | A shell in the running app |
How runs work
- The schedule is a standard cron expression and is always in UTC.
- Each run is a fresh container of the version the app has deployed, with the app's
environment variables. A run that exits with code
0has Succeeded, any other exit code is Failed. A failed run is not retried. - One run at a time. When a run is still going at the next scheduled time, that scheduled run is skipped.
- A run is stopped after 12 hours.
- A run that could not start on time, for example during maintenance, is dropped after 5 minutes instead of running late.
- The last 5 succeeded and 5 failed runs are kept, with their output, in the Runs tab.
What a run executes is the app's start command, when it has one:
- Built from source with a buildpack: the start command is built into the image. Left empty, your project's own
Procfilewebentry or start script runs. - Built from your own
Dockerfile, or a Docker image: the start command runs withsh -cin place of the image'sENTRYPOINTandCMD. Left empty, those run. This is how one image runs several jobs, for examplepython manage.py clearsessionsin your web app's image, orpg_dump ...inpostgres.
Create a cron job
- Dashboard
- CLI
- API
- MCP
- Open Apps in the sidebar and click Create App.
- Pick your code: a git repository, a Docker image, or a folder to upload.
- In Configure your app, set Type to Cron job.
- Pick a Schedule from the list, or type your own cron expression. The port and subdomain fields go away, a cron job has neither.
- Set the Start Command to what each run executes, for example
python manage.py clearsessions. Leave it empty to use your project's ownProcfileor start script, or the image's ownCMD. - Add environment variables if the job needs any, and click Create App.
The first build deploys the job, and it runs on its schedule from then on. Click Run now on the Runs tab to try it right away.
--schedule creates a cron job, from a directory with cloider deploy, or from a repository or
an image with cloider app create:
cloider deploy --name cleanup --schedule '0 3 * * *' --start-command 'python cleanup.py'
cloider app create --git github --repo acme/jobs --schedule '*/30 * * * *'
cloider app create --image ghcr.io/acme/web --tag v2 --schedule '@daily' --start-command 'python manage.py report'
Detected Python
✓ Created cron job cleanup, run on 0 3 * * * (UTC)
...build output...
✓ Deployed version 1 of cleanup
Next run 2026-10-29 03:00, or start one with `cloider app run cleanup`.
Quote the schedule, the shell would otherwise expand its *. A cron job takes no --port or --subdomain.
Pass schedule when you create the app. port and subdomain are not used:
curl -X POST https://cloider.com/api/apps \
-H "Authorization: Bearer $CLOIDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"app_name": "cleanup",
"schedule": "0 3 * * *",
"start_command": "python cleanup.py",
"source_token": "9f2c4e7a1b3d5f60718293a4b5c6d7e8"
}'
deploy_app, create_app_from_git and create_app_from_image take a schedule:
Deploy ~/code/jobs as a cron job named cleanup that runs python cleanup.py every day at 03:00 UTC.
Schedule syntax
A schedule has five fields, separated by spaces, in UTC:
┌───────────── minute (0 to 59)
│ ┌─────────── hour (0 to 23)
│ │ ┌───────── day of the month (1 to 31)
│ │ │ ┌─────── month (1 to 12, or JAN to DEC)
│ │ │ │ ┌───── day of the week (0 to 6, Sunday is 0, or SUN to SAT)
│ │ │ │ │
* * * * *
Each field takes * (every value), a number, a list (1,15), a range (1-5), a step (*/15, 0-30/10, or 5/20, which
is 5-59/20), or the three letter names of months and weekdays. When both the day of the month and the day of the week
are set, a day matching either one runs the job.
| Schedule | Runs |
|---|---|
*/5 * * * * | Every 5 minutes |
0 * * * * | At the start of every hour |
30 2 * * * | Every day at 02:30 UTC |
0 9 * * MON-FRI | Every weekday at 09:00 UTC |
0 0 * * 0 | Every Sunday at midnight UTC |
0 0 1 * * | On the 1st of every month at midnight UTC |
0 */6 * * * | Every 6 hours |
The shorthands @hourly, @daily (or @midnight), @weekly, @monthly and @yearly (or @annually) work too.
A schedule that can never run, like 0 0 30 2 * (February 30th), is refused.
UTC doesn't follow daylight saving time: 0 7 * * * runs at 09:00 in Berlin in summer and at 08:00 in winter.
See runs and run a job now
The Runs tab lists the runs Cloider keeps, newest first: their status, whether the schedule or Run now started
them, when they started and how long they took. A failed run says why, for example Exited with code 1 or
Stopped after running for 12 hours, and a run stuck before it starts says The image could not be pulled. Click
Logs on a run to read its output.
Run now starts a run immediately, on the version the app has deployed, without changing the schedule. It is refused while a run is still going.
- Dashboard
- CLI
- API
- MCP
Open the app and the Runs tab. The tab also shows the schedule and the next run in your local time.
cloider app runs cleanup
NAME STATUS TRIGGER STARTED DURATION MESSAGE
app-manual-1793185200 Succeeded manual 2026-10-28 11:00 14s -
app-29885940 Failed schedule 2026-10-28 03:00 3s Exited with code 1
app-29884500 Succeeded schedule 2026-10-27 03:00 12s -
cloider app run cleanup # start a run, print its name
cloider app run cleanup -f # start a run, print its output, fail when the run fails
cloider app run -f exits with an error when the run fails, so a CI pipeline can run a job and check it worked.
curl https://cloider.com/api/apps/k3v9q/runs -H "Authorization: Bearer $CLOIDER_TOKEN"
curl -X POST https://cloider.com/api/apps/k3v9q/runs -H "Authorization: Bearer $CLOIDER_TOKEN"
See List a cron job's runs and Run a cron job now.
list_app_runs lists the runs and run_app starts one, with follow to wait for its output.
Logs
A cron job has no Logs tab: click Logs on a run in the Runs tab to read its output, followed live while the run is going.
cloider app logs cleanup # the latest run's output
cloider app logs cleanup -f # follow every run as it starts
cloider app logs cleanup --run app-29885940 # one run's output
Only the runs the Runs tab lists still have their output. See Logs.
Debug with the terminal
While a run is going, click Terminal on it in the Runs tab to open a shell inside that run's container, with its files and processes. The session ends when the run finishes.
Between runs, cloider app shell and cloider app exec open a container with the cron job's image and environment
variables, started for them and stopped again once it has been unused for 10 minutes. It is the quickest way to find
out why a run fails: run the job's command by hand and watch what it does.
cloider app shell cleanup
cloider app exec cleanup -- python cleanup.py --dry-run
The terminal needs a shell in the image: an image without sh, like a distroless image, has nothing to open. See
Terminal and exec.
Change the schedule
- Dashboard
- CLI
- API
- MCP
Open the app's Settings tab, pick or type the new Schedule, and click Save changes. The job is redeployed on the new schedule in a few seconds, on the same version.
cloider app update cleanup --schedule '0 4 * * *'
curl -X PUT https://cloider.com/api/apps/k3v9q/settings \
-H "Authorization: Bearer $CLOIDER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"schedule": "0 4 * * *", "start_command": "python cleanup.py"}'
update_app_settings with schedule.
A cron job stays a cron job: it can't be turned into a web service, or the other way around. Create a new app for that.
Pause, resume and delete
Pausing a cron job stops its schedule, and stops a run that is going at that moment. Its runs list, version and environment stay. Resuming it puts the schedule back, and the next run is the schedule's next time: runs missed while paused don't run. See Pause, resume and delete.
Billing
A cron job is billed at the same rates as any app, for the CPU and memory its runs use, and each run is measured on its own:
- For exactly as long as it ran, from the second its container started to the second it finished. Time spent waiting to start, such as pulling the image, isn't billed, and neither is anything between runs.
- For the CPU it used, as its container counted it, read every 5 seconds while it runs.
- For the memory it was using, averaged over those same readings.
A run that is over in a few seconds can end before its first reading. It is billed at what the job's last measured run used per second, and costs nothing while the job has no measured run yet.
Only runs are billed. The container cloider app shell and cloider app exec open between runs is free. See
pricing and Insights.
Troubleshooting
A run shows The image could not be pulled. The run is stuck before it starts. For an image app, check the image
still exists and the registry login on the Settings tab still works.
A scheduled run didn't happen. The previous run was still going (one run at a time), the app was paused, or the schedule is in UTC and you read it in your local time. The Runs tab and the Overview show the next run in your local time.
The terminal says the app isn't running. The image has no sh, or its shell container is still starting. Wait a few
seconds and reconnect.
A run's terminal says the run isn't running any more. The run finished. Read its output with Logs on the Runs tab.