Run cron job free with GitHub Action 💸
Published: 2024-01-07 08:04:46

Cron jobs are useful for setting up automated tasks that run periodically. While several services offer cron job functionality, they often have limitations.

For instance, Vercel's Cron Job in its free tier offers:
- 2 cron jobs total, each triggered once per day
- 10 seconds maximum duration for each cron job

This 10-second limit can be restrictive. During my work on [Repohistory](https://github.com/repohistory/repohistory), I needed a cron job to fetch users' repo traffic data daily. As the user base grew, completing this task within 10 seconds became unfeasible.

That's why I explored an alternative solution for cron jobs.

## GitHub Action

GitHub Actions is a CI/CD platform that allows you to execute workflows triggered by any GitHub events. This feature is particularly useful for tasks like building and deploying your website following a new commit.

A workflow in GitHub Actions is an automated process defined in YAML. These workflows are stored in the `.github/workflows` directory of your repository and follow a specific structure.

Here's an example of a workflow file that prints the content of `hello_world.txt` when a new commit is pushed to the repository:

```yaml
name: hello-world
on: push
jobs: 
  hello-world-job:
    runs-on: ubuntu-latest
    steps: 
      - name: Check out repository code
        uses: actions/checkout@v3
      - run: echo "$(cat hello_world.txt)"
```

You can learn more about the detail on [GitHub Resources](https://resources.github.com/learn/pathways/automation/essentials/building-a-workflow-with-github-actions/).

The key part of a workflow is the `on`, which specifies the GitHub events that will activate the workflow. In the above example, the event is `push`, meaning the workflow runs on any commit push.

An additional event type is `schedule`, which is ideal for cron jobs.

## Cron job on GitHub Action

The `schedule` event allows workflows to run at scheduled times, defined using cron syntax. For example, this configuration triggers a workflow every day at 5:30 AM and 5:30 PM:
```yaml
on:
  schedule:
    - cron:  '30 5,17 * * *'
```

The limits for GitHub Actions are generous:
- 20 concurrent jobs
- 6 hours maximum duration per job

These limits are typically more than sufficient for most tasks.

## A practical example

For Repohistory, I implemented a GitHub Action to run a cron job. The `src/cron.ts` script fetches and saves users' repo traffic data to a database. To keep the data up to date, it runs three times a day:
```typescript
// src/cron.ts
import updateTraffic from './services/updateTraffic';
import { app } from './utils/octokit';

app.eachInstallation(({ installation }) => {
  updateTraffic(installation.id);
});
```

Let's go through the workflow file step by step. I first set the cron to `'0 */8 * * *'` so the workflow runs 3 times everyday ( on 00:00, 08:00, 16:00 ):

```yaml
name: Cron Job

on:
  schedule:
    - cron: '0 */8 * * *'
```

This workflow only has one job that is executing `src/cron.ts`, so the first few steps is to check out the repo and set up Node.js:

```yaml
jobs:
  execute-script:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
```

Then install the dependencies of my project:

```yaml
      - name: Install dependencies
        run: npm install
```

Finally compiles the TypeScript file and then execute it. Because the job use GitHub and Supabase API so I also pass some environment variables into it:

```yaml
      - name: Compile TypeScript file
        run: npx tsc src/cron.ts --outDir dist

      - name: Run the compiled script
        env:
          NEXT_PUBLIC_APP_ID: ${{ secrets.NEXT_PUBLIC_APP_ID }}
          NEXT_PUBLIC_PRIVATE_KEY: ${{ secrets.NEXT_PUBLIC_PRIVATE_KEY }}
          NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
          NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
        run: node dist/cron.js
```

You can see the complete workflow file [here](https://github.com/repohistory/repohistory/blob/main/.github/workflows/cron.yaml).

---

That's pretty much about how I run free cron job on GitHub Action when building Repohistory! Feel free to check this project out on [GitHub](https://github.com/repohistory/repohistory)!