Scheduled workflows, polling triggers, connector syncs, and maintenance jobs depend on authenticated HTTP endpoints called by an external scheduler.
Helm enables CronJobs by default; Docker Compose includes a cron service. Both use CRON_SECRET and the same shipped schedules. Upload-session cleanup needs an additional operator-configured job, described below.
Authentication
Every endpoint is protected by CRON_SECRET and expects it as a bearer token:
Generate it like the other secrets:
Point cron at an internal address where possible (the in-cluster Service, or localhost on a single node). These endpoints should not be reachable from the internet; if they are, CRON_SECRET is the only thing protecting them.
Kubernetes
CronJobs are enabled by default. Supply CRON_SECRET through the app Secret and ensure cron pods can reach the internal app Service.
Each curlimages/curl pod calls the internal app Service, which routes the request to one replica. concurrencyPolicy: Forbid prevents overlapping Jobs for that CronJob; the app also handles overlap for detached work.
The Job deadline includes attempts and controller backoff. It can end a Job before the retry limit is reached and also stops a hung request.
Disable individual jobs you do not need — billing reconciliation is the obvious one on a self-hosted install:
Do not disable schedule execution, outbox processing, or subscription renewal unless you know you do not use the features behind them.
Check they are running:
Inspect stale schedules and failed Jobs. A stale LAST SCHEDULE points to controller, suspension, or deadline settings; a 401 in Job logs points to authentication. Forbid can skip a tick while an earlier Job remains active. Alert on successful completion as described in Observability.
Docker Compose
Generate a secret, then save it as CRON_SECRET in the .env file next to your Compose file. Replace an existing value rather than adding a duplicate. The app and scheduler must receive the same value.
Recreate the services after updating .env:
Without it the cron service logs exactly what to set — including a freshly generated value — and exits, leaving the rest of the stack running. Schedules live in docker/crontab and mirror helm/sim/values.yaml cronjobs.jobs one-for-one.
An older install may lack CRON_SECRET; add it to .env and run up -d to enable the scheduler.
The service runs supercronic rather than the app image: it logs each job's output to the container log, forwards SIGTERM so docker compose stop is graceful, and will not start an iteration while the previous one is still running.
A healthy log line looks like:
The published cron image contains its crontab. Editing the repository copy and restarting that image does not change its jobs. To customize them, copy docker/crontab to an operator-owned file, edit it, and mount it with a Compose override:
Alternatively, build a custom image from docker/cron.Dockerfile with your crontab and configure Compose to use it.
Schedule upload-session cleanup
The cleanup endpoint exists, but neither shipped scheduler calls it. Configure your own authenticated schedule for /api/cron/cleanup-tasks to expire upload sessions and reclaim abandoned upload state. It also dispatches task-retention cleanup according to configured policy.
For Helm, add an operator-owned job to your values file. This example runs hourly; choose a cadence and timeout for your workload:
For Compose, add this line to the mounted or custom-image crontab described above:
Use the internal app address and the same CRON_SECRET. Check job logs for a successful response. Provider lifecycle rules remain a second layer for incomplete multipart state; see Object storage.
Verifying
Create a workflow with a Schedule trigger set to every minute, deploy it, and watch the Logs view. An execution should appear within ~2 minutes. If nothing appears:
- Check the scheduler's own logs —
docker compose -f docker-compose.prod.yml logs cron, orkubectl get cronjobs -n simstudiofor a recentLAST SCHEDULE. - Confirm the app and the scheduler share the same
CRON_SECRET. A mismatch shows up as401in the scheduler log. - A
202means the endpoint accepted the run; it does not confirm a schedule was due, so check the Logs view.
Shipped job reference
Subscription renewal covers Microsoft Teams chat triggers. Their Graph subscriptions last at most three days; rich notifications have a shorter limit. Gmail, Outlook, Drive, Calendar, and Sheets use the per-minute polling jobs instead. See Microsoft Graph subscription lifetimes. SCIM reconciliation calls its authenticated endpoint directly each hour; it does not require Trigger.dev.
Concurrency
On the default database-backed queue, scheduled execution capacity is shared across the deployment:
Adding replicas does not multiply the database queue's schedule cap. Raise it only after measuring memory and database headroom. The app orchestrates runs; local JavaScript uses Node child workers inside its container.