How to run cron jobs in a Docker container
Updated 16 October 2026
Running cron inside Docker takes adjusting how the process starts, where the output goes and which environment the jobs get. This guide covers those steps on images based on Debian, Ubuntu and Alpine.
A complete example on Debian or Ubuntu
Four files in one directory: the Dockerfile, the crontab, an entrypoint.sh that starts cron, and the job, backup.sh.
Dockerfile:
FROM debian:12-slim
RUN apt-get update && apt-get install -y --no-install-recommends cron tzdata \
&& rm -rf /var/lib/apt/lists/*
COPY backup.sh entrypoint.sh /app/
COPY crontab /etc/cron.d/app
RUN chmod 0755 /app/backup.sh /app/entrypoint.sh && chmod 0644 /etc/cron.d/app
ENTRYPOINT ["/app/entrypoint.sh"]
crontab, with a temporary line that runs every minute to check the setup:
# Temporary, to check the setup: every minute.
* * * * * root . /app/env.sh; date > /proc/1/fd/1 2>&1
0 3 * * * root . /app/env.sh; /app/backup.sh > /proc/1/fd/1 2>&1
entrypoint.sh:
#!/bin/sh
# The container's variables, for the jobs: cron starts them without these.
export -p > /app/env.sh
chmod 600 /app/env.sh
exec cron -f
backup.sh, your job:
#!/bin/sh
echo "Backup started: $(date)"
Build it, start it and watch its output:
docker build -t cron-app .
docker run -d --name cron-app -e TZ=America/Santiago cron-app
docker logs -f cron-app
Within a minute, docker logs shows a date in the time zone you passed in TZ. Then remove the temporary line from the crontab and rebuild the image.
The same on Alpine
Alpine already has BusyBox’s crond. Its crontab is /etc/crontabs/root, and its lines have no user after the schedule. It passes the container’s variables to the jobs, so it needs no entrypoint.sh:
FROM alpine:3.20
RUN apk add --no-cache tzdata
COPY backup.sh /app/
COPY crontab /etc/crontabs/root
RUN chmod 0755 /app/backup.sh
CMD ["crond", "-f", "-l", "2"]
# Temporary, to check the setup: every minute.
* * * * * date > /proc/1/fd/1 2>&1
0 3 * * * /app/backup.sh > /proc/1/fd/1 2>&1
Why each part
-
cron -fkeeps cron in the foreground. Without-f, cron goes to the background, the container’s main process ends, and the container stops. -
The file in
/etc/cron.d/must not be writable by the group or others: with0664, cron ignores it, and in a container there’s no syslog to say so. Its lines have the user (root) after the schedule, its name can’t have dots (app, notapp.cron), and it must end with a newline. -
> /proc/1/fd/1 2>&1sends the job’s output to the container’s, whichdocker logsshows. The cron of Debian and Ubuntu only logs to syslog, and doesn’t send the jobs’ output anywhere you can see. It works for root’s jobs: the jobs of another user can’t write to the output of process 1, so give them a file. -
env.shgives the jobs the container’s variables. The cron of Debian and Ubuntu starts each job with almost none:HOME,LOGNAME,SHELLand aPATHof/usr/bin:/bin. What you pass withdocker run -e,env_fileor Kubernetes isn’t there, so a job that needsDATABASE_URLor an API key fails. The file has the container’s secrets, so only root can read it. -
tzdataandTZset the time zone. Containers are usually in UTC. WithTZ, cron follows the schedule in that time zone; on Debian and Ubuntu the jobs only getTZthroughenv.sh, so without it adatein a job shows UTC.
If the jobs still don’t run
-
Is the container still running?
docker ps. If it stops, checkcron -f. -
Is cron the main process?
docker top cron-app. -
Escape
%in the crontab:date +\%F. A%without\ends the command, and the job doesn’t run as written. - The script must be executable, with Unix line endings. The rest of the usual causes are in why your cron job isn’t running.
Do you need cron inside the container?
Sometimes another option is simpler:
-
The host’s cron, running the job in the container with
docker exec. The host’s cron decides when, in the host’s time zone. The command runs inside the container, with the container’s environment. Its output goes where you redirect it on the host:0 3 * * * docker exec cron-app /app/backup.sh >> "$HOME/backup.log" 2>&1 -
A Kubernetes
CronJob, which starts a pod for each run. TheCronJobresource has its own time zone inspec.timeZone, since Kubernetes 1.27:spec: schedule: "0 3 * * *" timeZone: "America/Santiago" -
supercronic, a cron made for containers, that logs to the output and keeps the environment.
Know the next time it doesn’t run
A restarted container, a missing variable or a wrong time zone can stop a job, and these problems can go unnoticed until someone reads the logs. Monitoring tells you when a run doesn’t start, takes too long or fails. If you use CronDrive’s line in /etc/cron.d/, add the user after the schedule, and install curl in the image.