Troubleshooting · Docker
Docker container cron not running? Fixes that actually work
Why cron inside a Docker container silently fails — PID 1, missing env vars, no mail server — and how to fix each.
Running cron inside a container trips people up because a container isn’t a normal VM. Here are the usual culprits, fix-first.
1. Cron isn’t running as PID 1
A container runs one foreground process. If your CMD starts your app, cron
never starts — and if your CMD is cron, it may exit immediately because it
daemonizes. Run it in the foreground:
# Debian/Ubuntu base
CMD ["cron", "-f"]
To run cron and your app, use a small supervisor (supervisord, s6) or a
process manager — don’t rely on backgrounding in a shell CMD.
2. Environment variables vanish under cron
This is the big one. Docker injects env vars into PID 1, but cron starts jobs with
a clean environment, so your DATABASE_URL and API keys are gone. Dump the
container env to a file at startup and source it in the job:
CMD printenv | sed 's/^\(.*\)$/export \1/' > /etc/container.env && cron -f
# in the crontab
0 * * * * . /etc/container.env && /app/run.sh
3. The crontab needs the right format and a newline
A system crontab line needs a user column, and the file must end with a newline or the last job is ignored:
# /etc/cron.d/app — note the "root" user field
0 2 * * * root /app/run.sh >> /var/log/cron.log 2>&1
COPY app-cron /etc/cron.d/app
RUN chmod 0644 /etc/cron.d/app && crontab /etc/cron.d/app
4. “Cron can’t send email” — there’s no mail server
A classic: a container crontab tries to email output, but there’s no MTA, so jobs
appear to “fail.” Don’t depend on local mail. Redirect output to stdout so it lands
in docker logs:
0 2 * * * root /app/run.sh > /proc/1/fd/1 2>/proc/1/fd/2
5. See the output
docker logs -f <container>
docker exec -it <container> sh -c "crontab -l"
The deeper problem: containers restart, and cron forgets
A redeploy, an OOM kill, a crash loop — any of these can stop your in-container cron, and nothing tells you. Monitoring from outside the container is the only reliable signal.
Have the job ping a heartbeat URL on success. If the container is down or the job stopped firing, the ping goes missing and you’re alerted:
HEALTHCHECK --interval=5m --timeout=10s \
CMD curl -fsS https://ping.steadycron.com/<your-ping-token> || exit 1