logo hsb.horse
← Back to blog index

Blog

Create Cron Jobs with PM2: As an Alternative to launchd

How to create cron-style jobs with PM2 on macOS instead of launchd. A quick summary of the config file and a scheduled task example using Bun.

Published:

I wanted to run scheduled jobs on macOS, but launchd is still hard to read, and I wanted an easier path, so I decided to use PM2 instead.

Configuration for running a Cron job with PM2

Terminal window
cd $PROJECT_ROOT
pm2 ecosystem

Manage the configuration in ecosystem.config.cjs.

module.exports = {
apps: [
{
name: "cronJob",
script: "main.ts",
interpreter: "~/.bun/bin/bun",
// number of instances is 1
instances: 1,
// run every 15 minutes
cron_restart: "*/15 * * * *",
exec_mode: "fork",
// restart automatically when target files change: no
watch: false,
// restart automatically: no
autorestart: false,
},
],
};

Points

  • cron_restart: set the interval in cron format
  • autorestart: false: since this is a cron-style job, automatic restart is disabled
  • interpreter: you can specify any runtime, including Bun

Start command:

Terminal window
pm2 start ecosystem.config.cjs
pm2 save

Sample job implementation

This sample periodically fetches the IP address and stores it in a database.

import { Database } from 'bun:sqlite';
const DDL = `CREATE TABLE IF NOT EXISTS ip_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
async function fetchIpConfig() {
const response = await fetch("https://ifconfig.io/ip").catch((err) => new Error(err));
if (response instanceof Error || !response.ok) {
return null;
}
const ip = await response.text();
return ip;
}
async function main() {
const ip = await fetchIpConfig();
if (ip == null) return;
using db = new Database('ip-monitor.sqlite', { create: true });
db.exec(DDL);
using query = db.query("INSERT INTO ip_history (ip) VALUES ($ip);");
query.run({ $ip: ip });
}
await main();

References

Summary

With PM2, cron-style jobs can be managed with a simpler setup than launchd.

Define the job in ecosystem.config.cjs, then use PM2’s process management features for logs and restarts when needed.