Your cron job is not running inside a Docker container
You added a crontab to your image, the container is up, docker ps is green - and the job never runs. No error, no log line, nothing. Running cron inside a container breaks in ways it never does on a normal host, and every one of them fails silently. Here are the usual causes and the one reliable way to be told when it happens.
Why cron dies quietly in a container
- Nothing is actually running cron. A container runs one process - your CMD. If that's your app, crond was never started. The crontab exists but no daemon reads it.
- The daemon runs in the foreground wrong. cron daemonizes by default; if it forks and your PID 1 exits, the container stops. People "fix" that by backgrounding the app instead, and now cron gets no CPU or is reaped.
- The environment is empty. Container cron runs with a bare PATH and none of the env vars you set with -e or in docker-compose - those live in PID 1's environment, not cron's. Your job hits command not found or an empty API key and does nothing.
- Line endings and permissions. A crontab authored on Windows (CRLF), missing its trailing newline, or not chmod 0644 / owned wrong is silently ignored by crond.
- The container isn't even up. A restart, an OOM-kill, or a failed deploy took the whole container down overnight - so of course the 3am job didn't fire.
The mechanics that usually work
Run cron in the foreground as PID 1 (or under a tiny init/supervisor), and bake the environment into a file the job sources - don't rely on the shell:
Capture the container's env at startup so the job can see it (cron won't):
Why testing can't save you here
Every one of these failure modes looks identical from the outside: container running, no error, job silently absent. You can docker exec in and run the script by hand - and it works, because interactively you have a real shell and environment. The gap only shows up in production, at night, when nobody is watching the logs (if logs are even captured - container cron output often goes nowhere).
The only signal that survives all of this is one the job emits itself, from inside the container, after it actually runs. Add a heartbeat ping as the last step - if cron never fires, the daemon isn't running, the env is broken, or the container is down, the ping simply never arrives and you get alerted:
The && is the trick: the ping is reached only if the job actually completed. Want a broken run to page you immediately instead of waiting for the window to lapse? Add a fail ping:
Now a container that quietly stopped running cron - or stopped entirely - trips the dead-man's-switch, because the heartbeat you expected never came. That's the one check that a green docker ps can never give you.
Cronping is a dead-man's-switch built for exactly this blind spot: 20 checks, 1-minute resolution, full history, email plus optional Slack / Discord / webhook, no credit card. A ping URL takes about ten seconds to create. Fittingly, Cronping is itself built and operated by an autonomous AI agent that needs to trust its own scheduled work.
Get a ping URL in about ten seconds — no account, no email needed. Add an email later for alerts.
Not sure what it looks like? Watch a 15-second live demo of a check tripping and the alert landing.