# Cronaute — a container cron daemon in Crystal

**URL:** https://forum.crystal-lang.org/t/cronaute-a-container-cron-daemon-in-crystal/9092
**Category:** News
**Created:** [August 9, 2026, 4:51pm UTC](https://forum.crystal-lang.org/t/cronaute-a-container-cron-daemon-in-crystal/9092 "2026-08-09T16:51:27Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![n-rodriguez](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/n-rodriguez/32/1990_2.png) [@n-rodriguez](https://forum.crystal-lang.org/u/n-rodriguez)
#### Post date: [August 9, 2026, 4:51pm UTC](https://forum.crystal-lang.org/t/cronaute-a-container-cron-daemon-in-crystal/9092/1 "2026-08-09T16:51:27Z")

</div>

**[cronaute](https://github.com/jbox-web/cronaute)** — a cron daemon for containers: one static binary, one user, no spool, and a read-only dashboard. A reimplementation of [go-crond](https://github.com/webdevops/go-crond) in Crystal, cut down to what a container actually needs.

The omissions are the design: no multi-user (no user column, no spool, no `setuid`), no reaping (it runs under tini as PID 1), no editing from the UI (any method but `GET` is `405`), no persistence (state is in memory, history is in the JSON log). The crontab is edited out of band — mounted volume or gitops — and reloaded on change, atomically: a broken file leaves the daemon running exactly what it was running.

Standard five fields, optional leading seconds field, Vixie shorthands, `@every` with Go-style durations, and `run-parts` directories on named periods. Schedules run on the wall clock in `TZ`, falling back to **UTC** rather than the host zone so the same image schedules identically everywhere; DST edge cases live in a pinned [`cron_parser`](https://github.com/n-rodriguez/cron_parser) fork.

Two decisions I’d happily argue about:

- **`/health` and `/live` are separate routes** because they trigger opposite reactions. Readiness goes red on a failed crontab reload and takes the instance out of service — restarting wouldn’t fix the file and would destroy the working schedule still running jobs. Liveness goes red on a tick loop that hasn’t turned in 30 s, which a restart _does_ fix. Docker has a single health state, so the image’s `HEALTHCHECK` asks `/live`.
- **An occurrence that comes due with no free slot is dropped, not queued.** Queuing piles one waiting fiber per occurrence behind a job that may never return its slot, then releases them in a burst if it ever does.

Prometheus metrics on `/metrics`, one JSON object per line on stdout, static amd64/arm64 binaries.

Source: **[GitHub - jbox-web/cronaute · GitHub](https://github.com/jbox-web/cronaute)** (MIT).
