Comparison · vs Supercronic

RunWisp vs Supercronic: cron in Docker that remembers what ran.

Supercronic is the go-to answer to “how do I run cron in a container”: a crontab runner that logs to stdout and passes your env through. RunWisp also runs in the container, then keeps every run's exit code and output, retries the failures, and tells you about them.

Verdict

Should you switch?

The 10-second version. Skim, decide, scroll down for the receipts.

Switch to RunWisp if

  • “Did last night's job run?” means grepping docker logs that may have rotated away.
  • You want failures in Slack, Telegram, Discord or email, not only in Sentry.
  • You're wrapping crontab lines in timeout and retry loops by hand.
  • Someone needs a “run now” button that isn't docker exec.
  • You'd like one config that also runs on the host, a Pi or a Mac.
  • You also have long-running workers that need restarting.

Stay on Supercronic if

  • Your crontab already works and stdout logs into Loki or Datadog are enough.
  • You want the smallest possible thing in the image: one binary, no data volume, no port.
  • You run it as PID 1 and rely on it to reap zombies.
  • Sentry or Prometheus is already where your team looks for failures.
  • Your crontab uses the year field or Quartz-style L / W / # modifiers.
Head to head

Yes / no, at a glance

Yes / no — and both columns win on something.

Capability Supercronic RunWisp
Built for containers ✓its whole purpose ✓official image + static binary
Reads a standard crontab ✓ ~import to TOML
Container env passed to jobs ✓ ✓
Run history per firing ✗stdout only ✓SQLite rows + UI
Captured output per run ~interleaved in docker logs ✓per run, searchable
Retries with backoff ✗ ✓
Per-job timeout ✗wrap in timeout(1) ✓
Failure notifications ~Sentry only ✓Slack / Telegram / Discord / ntfy / email / webhook
Overlap policy ~skip, or -overlapping ✓skip / queue / kill
Catch-up after downtime ✗ ✓catch_up = N
Timezone per job ~one CRON_TZ per file ✓
Web UI / TUI ✗ ✓
Manual “run now” ✗ ✓UI, CLI, REST, hook token
Long-running services ✗ ✓[services.*]
Prometheus metrics ✓per job ✓opt-in /metrics
Sentry integration ✓ ✗webhook instead
Reaps zombies as PID 1 ✓ ✗use init: true
Stateless: no volume, no port ✓ ✗wants a data volume
Year field, L / W / # modifiers ✓ ✗
macOS / bare metal ✗Linux builds only ✓
Trade-offs

Pros & cons

What Supercronic does better

  • Smaller footprint: one ~14 MB binary, about 11 MB of RAM, no database, no port, no volume.
  • Takes the crontab you already have. No new config format to learn.
  • Built-in PID 1 handling: it forks itself, reaps zombies, and forwards signals.
  • Sentry DSN and per-job Prometheus histograms are one flag each.
  • -inotify reloads the crontab when a Kubernetes ConfigMap swaps under it.
  • Fully stateless. Kill the container, start another, nothing to migrate.

What RunWisp adds

  • Every firing is a row: status, duration, exit code, peak memory, and its own stdout/stderr, kept per task.
  • timeout, retry_attempts and retry_backoff as fields, not shell wrappers in every crontab line.
  • Failure alerts to Slack, Telegram, Discord, ntfy, Gotify, Pushover, email or a webhook, with repeats grouped during an outage.
  • on_overlap = "skip" | "queue" | "kill", and catch_up for firings missed while the container was down.
  • A web UI and a TUI with a Run button, live logs, and a phone-friendly layout.
  • failures = ['+output:ERROR'] fails a run that printed an error but exited 0.
  • Supervises long-running workers in the same file, so one container can hold the worker and its schedule.
  • Same binary and TOML on a VPS, a Pi, macOS or WSL, outside Docker too.
The simple case

The simple case

A Django job at 03:00, inside the app image. Both are one static binary added to your Dockerfile. RunWisp's binary is a COPY away from the official image.

Dockerfile + crontab (Supercronic)
# Dockerfile: the README stanza, pinned by SHA-1
ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.49/supercronic-linux-amd64 \
    SUPERCRONIC_SHA1SUM=e63c11a9726b775a6a11801e81af4f3fb926aa68 \
    SUPERCRONIC=supercronic-linux-amd64
RUN curl -fsSLO "$SUPERCRONIC_URL" \
 && echo "${SUPERCRONIC_SHA1SUM}  ${SUPERCRONIC}" | sha1sum -c - \
 && chmod +x "$SUPERCRONIC" \
 && mv "$SUPERCRONIC" /usr/local/bin/supercronic
COPY crontab /app/crontab
CMD ["supercronic", "/app/crontab"]

# crontab
0 3 * * *  python manage.py clearsessions
Dockerfile + runwisp.toml
# Dockerfile: copy the static binary out of the official image
COPY --from=runwisp/runwisp:1 /usr/local/bin/runwisp /usr/local/bin/runwisp
COPY runwisp.toml /etc/runwisp/runwisp.toml
ENV RUNWISP_CONFIG=/etc/runwisp/runwisp.toml \
    RUNWISP_DATA=/var/lib/runwisp
CMD ["runwisp", "daemon"]

# runwisp.toml
[tasks.clear-sessions]
cron = "0 3 * * *"
run  = "python manage.py clearsessions"
Production-grade

The version that runs in prod

Three jobs, hardened. Supercronic's answer is a second container from the app image with JSON logs, metrics and Sentry; timeouts live in the crontab line. RunWisp runs as its own service, execs into the live app container, and keeps history, retries and alerts per task.

compose.yaml + crontab (Supercronic, production)
# compose.yaml: a second copy of the app image runs the crontab
services:
  cron:
    image: myapp:latest
    command: >
      supercronic -json -split-logs
        -prometheus-listen-address 0.0.0.0
        -sentry-dsn ${SENTRY_DSN}
        /app/crontab

# crontab: no timeout or retry in Supercronic, so wrap each line
0 3 * * *     timeout 55m /app/bin/backup.sh
*/5 * * * *   curl -fsS http://app:8080/health
0 4 * * 0     find /tmp -mtime +7 -delete
runwisp.toml (production)
# compose.yaml: one runwisp service execs into the live app container
#   runwisp:
#     image: runwisp/runwisp:latest-docker
#     volumes: [/var/run/docker.sock:/var/run/docker.sock, ...]

# runwisp.toml
[defaults]
compose_file    = "/srv/app/compose.yaml"
compose_service = "app"
notify          = ["slack-ops"]

[tasks.backup]
cron           = "0 3 * * *"
on_overlap     = "skip"
timeout        = "55m"
retry_attempts = 2
retry_backoff  = "exponential"
keep_runs      = 60
run            = "/app/bin/backup.sh"

[tasks.healthcheck]
cron     = "*/5 * * * *"
catch_up = 0
timeout  = "30s"
run      = "curl -fsS http://app:8080/health"
Migration

Migration cheat sheet

Supercronic flag or crontab habit on the left, runwisp.toml on the right.

Supercronic RunWisp
supercronic /app/crontab runwisp import cron /app/crontab -o runwisp.toml, then runwisp daemon
A crontab line [tasks.<name>] with cron = and run =
KEY=value lines / container env Container env is inherited; per-task env, env_file, secrets_file
CRON_TZ=Europe/Berlin timezone = "Europe/Berlin", per task or global
Default no-overlap / -overlapping on_overlap = "skip" / max_concurrent
timeout 55m cmd in the line timeout = "55m"
-sentry-dsn notify = ["slack-ops"], or a webhook notifier
-prometheus-listen-address [daemon] metrics_enabled = true
-json logs in docker logs Per-run logs in the Web UI and TUI; daemon logs stay on stdout
SIGUSR2 / -inotify reload runwisp reload or SIGHUP, validated before applying
-test runwisp validate
Gotchas

Gotchas

Six fields mean different things

In Supercronic a 6-field line adds a year at the end; seconds need all 7 fields. In RunWisp 6 fields means a leading seconds field. Year fields and the L, W, # modifiers have no RunWisp equivalent. Check any line with more than five fields by hand after runwisp import cron.

RunWisp doesn't reap zombies as PID 1

Supercronic forks itself when it is PID 1 so the parent can reap orphaned processes. RunWisp doesn't. If your jobs spawn background processes, run the container with init: true in compose (or docker run --init) so tini does it.

Give it a volume and an auth setting

Run history lives in SQLite under RUNWISP_DATA. Without a volume it is wiped on every redeploy, which puts you back at Supercronic's level. The daemon also wants RUNWISP_PASSWORD, or RUNWISP_AUTH=off if the UI is never exposed.

You may not need a binary in the app image at all

Supercronic has to live in the image whose code it runs. RunWisp can run as its own compose service and exec into your app's running container with compose_file + compose_service, so the app image stays untouched and one RunWisp covers every service in the stack.

FAQ

FAQ

What is the best Supercronic alternative for Docker?
If Supercronic's gap for you is visibility, RunWisp: an open-source cron replacement that ships as a single binary, runs in a container, and keeps every run's exit code, duration and output, with retries, timeouts, failure alerts and a web UI. If you only need a crontab in a container and stdout logs are enough, Supercronic is already the right tool. If your stack is all Compose and you want labels instead of a file, look at Ofelia.
Can RunWisp run inside my app's container like Supercronic?
Yes. Copy the static binary from the official image with COPY --from=runwisp/runwisp:1 /usr/local/bin/runwisp /usr/local/bin/runwisp, add a runwisp.toml, and run runwisp daemon. Jobs see the container's env like they do under Supercronic. Mount a volume at RUNWISP_DATA to keep history across redeploys, and set init: true if jobs leave orphan processes behind.
Can I keep my crontab file?
Not as the live config. RunWisp reads TOML. runwisp import cron /app/crontab converts it once, one [tasks.*] stanza per line, and marks anything it can't map with a # TODO comment. Supercronic's year field and L / W / # modifiers are among those.
Does Supercronic retry failed jobs or send alerts?
No retries, no per-job timeout, and no built-in email or webhook notifications; MAILTO isn't supported. Failures are logged at error level, which reaches Sentry if you set -sentry-dsn, and they count in the Prometheus metrics. Everything else is up to whatever reads the container's logs.
Is RunWisp heavier than Supercronic?
Yes, a bit. Supercronic idles around 11 MB of RAM with no state on disk. RunWisp idles around 30 MB and writes run history to an embedded SQLite file. Both are single Go binaries with no runtime to install. On anything bigger than a microcontroller the difference won't matter; on a 64 MB container limit it might.
Does RunWisp work in Kubernetes?
It runs fine in a pod, but it doesn't watch ConfigMaps the way Supercronic's -inotify flag does. Reloads are explicit: runwisp reload or SIGHUP, validated before anything changes. For cluster-wide scheduling, a Kubernetes CronJob is usually the better fit than either tool.
How do I see what a Supercronic job printed last Tuesday?
Search your log pipeline for the job.command field, if the logs were shipped somewhere and haven't rotated. Supercronic keeps nothing itself. In RunWisp you open the task, pick Tuesday's run, and read its output; retention is per task with keep_runs or keep_for.
Get started
GPL 3.0 · no accounts · free to self-host

Keep the crontab habit. Lose the guessing.

Import your crontab, drop the binary into the image or run it as a compose service, and watch the first firing land with its exit code and output.

Install in 30 seconds
curl -fsSL https://get.runwisp.com | sh