TutorialsTutorialWorkers

Cron jobs and background workers on StackBlaze

Scheduled tasks and queue consumers are first-class services, not hacks on top of a web dyno. Here is how to run them reliably.

TW

Tom Walsh

Developer Advocate

May 2, 20268 min read

A common anti-pattern on PaaS platforms is running cron inside your web process or using an external scheduler that HTTP-calls your API. Both approaches break under load, duplicate work across replicas, or wake your entire app just to send one email.

On StackBlaze, cron jobs and workers are separate service types with their own lifecycle, scaling rules, and deploy semantics. This tutorial shows how to set them up correctly.

Cron jobs: short-lived, scheduled

A cron service runs as a Kubernetes Job on your schedule. Only one instance runs per trigger, even if your web tier has ten replicas, you will not get ten copies of the nightly cleanup script.

blueprint.yaml
services:
  cleanup-stale-sessions:
    type: cron
    schedule: "*/15 * * * *"
    build: .
    start: node scripts/cleanup-sessions.js
    timeout_seconds: 300
    env:
      DATABASE_URL:
        from_service: db
        property: connection_string

Cron jobs must finish

If a job runs longer than timeout_seconds, it is killed and marked failed. Design idempotent jobs and log how many rows you processed so a retry does not double-apply changes.

Background workers: long-lived consumers

Workers are Deployments that do not receive HTTP traffic. They pull from Redis, SQS-compatible queues, or whatever broker you configure. Scale them on queue depth or CPU depending on your workload.

worker/index.ts
import { Worker } from 'bullmq';
import Redis from 'ioredis';

const connection = new Redis(process.env.REDIS_URL!);

const worker = new Worker(
  'emails',
  async (job) => {
    await sendEmail(job.data);
  },
  { connection, concurrency: 5 },
);

process.on('SIGTERM', async () => {
  await worker.close();
  process.exit(0);
});
blueprint.yaml
  email-worker:
    type: worker
    build: .
    start: node dist/worker.js
    scaling:
      min_instances: 1
      max_instances: 8
      target_cpu_percent: 70
    env:
      REDIS_URL:
        from_service: cache
        property: connection_string

Deploy behavior differences

Service typeOn deployGrace period
webRolling update, zero-downtime60s default
workerRolling update, finishes in-flight jobs300s default
cronNext run uses new imageN/A (job timeout applies)

Observability

Cron run history appears in the dashboard: start time, duration, exit code, and logs for that execution. Workers share the same metrics and tracing as web services, filter by service name to separate worker noise from API traffic.

Treat background work as production code with the same tests, deploy process, and on-call ownership as your API. StackBlaze gives you the primitives; discipline is still on your team.

TW

Tom Walsh

Developer Advocate at StackBlaze

Member of the founding team at StackBlaze. Writes about infrastructure, engineering culture, and the systems that keep production running.

More from the blog