Build a dedicated cron container
Use a complete Debian cron example with foreground scheduling, valid system-crontab syntax and observable job output.
A dedicated cron container keeps scheduled work separate from the main application. Run cron -f as its foreground process, provide the actual job script, and use either a system cron file or a per-user crontab consistently.
A complete minimal example
Create a Dockerfile:
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends cron \
&& rm -rf /var/lib/apt/lists/*
COPY --chmod=755 myscript.sh /app/myscript.sh
COPY cronfile /etc/cron.d/app-jobs
RUN chmod 0644 /etc/cron.d/app-jobs
CMD ["cron", "-f"]
Create myscript.sh:
#!/bin/sh
set -eu
printf 'job=heartbeat outcome=success time=%s\n' "$(date -u +%FT%TZ)"
Create cronfile, including a newline after the final line:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * root /app/myscript.sh >> /proc/1/fd/1 2>&1
Then build and start it:
docker build -t my-cron:dev .
docker run -d --name my-cron my-cron:dev
docker logs -f my-cron
Expect a heartbeat at the next minute boundary, not immediately at startup. The demonstration writes to stdout; a real job should report its actual result rather than printing success unconditionally.
File format and output details
Debian reads /etc/cron.d/app-jobs directly. Its scheduled lines contain five time fields, a user, and a command. Do not also run crontab /etc/cron.d/app-jobs; a per-user crontab omits the user field, so that installs a malformed command and may duplicate schedules.
Keep the system file root-owned, not group/other writable, and use mode 0644 as a conventional choice. Exact 0644 is not the only acceptable mode. Use a valid filename such as app-jobs, Unix line endings and a final newline. Percent signs have special meaning in cron command fields; putting complex commands in scripts avoids many quoting mistakes.
/proc/1/fd/1 works here when the root job can access the main process's stdout descriptor. It is not a universal logging solution for arbitrary non-root or restricted containers. Test the actual logging driver and collection path.
Environment and operational behavior
Cron may build a limited job environment, so a value passed with docker run -e is not guaranteed to appear in every job. Declare simple nonsecret settings in the cron file, read a mounted configuration/secret file in the job, or use a carefully serialized allowlist. Never source an unescaped environment dump.
Separating jobs lets the web application scale independently, while a single scheduler reduces accidental duplication. It does not prevent overlapping executions when a job exceeds its interval, or duplicate work during deployment. Use locking, idempotency, or scheduler concurrency controls appropriate to the task. A failed child job normally does not kill the cron daemon, so a container restart policy is not a job retry policy.
Log scheduled time, start, completion and outcome. For an existing Guance integration, use log monitoring to distinguish failure records from missing completion records, and test a delayed job and an interrupted collection path.
FAQ
How does Alpine work? The usual BusyBox setup uses crond -f and /etc/crontabs/root, without a username field. Confirm the installed cron implementation and required tools.
Can one container have several schedules? Yes. Add multiple lines or separate valid files in /etc/cron.d/; account for overlap and shared resources.
Should Kubernetes use this container? A Kubernetes CronJob usually provides a better fit for discrete scheduled jobs. Set deadlines and concurrency behavior and make the job safe to repeat. It does not eliminate all missed or duplicate executions.
See Debian cron behavior, Debian crontab formats, Kubernetes CronJobs.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan