How to Set Up Cron Jobs in Docker Containers
Learn how to run cron jobs in Docker containers using system crontab, Supercronic, and Ofelia. Covers environment variables, logging, and Docker Compose examples.
Prerequisites
- Docker 20+
- Basic Dockerfile knowledge
- Understanding of cron syntax
In this guide
Why Cron in Docker Is Tricky
Running cron in Docker containers presents unique challenges:
- Cron runs as a separate daemon that doesn't inherit your container's environment variables
- Log output goes to the cron daemon, not to
docker logs - Cron expects a full init system, but Docker containers typically run a single process
- Signal handling is different —
docker stopmay not gracefully stop cron jobs
There are three main approaches, each with trade-offs.
Approach 1: System Crontab in Container
The simplest but least Docker-native approach:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
# Install cron and set up the crontab
RUN apt-get update && apt-get install -y cron && rm -rf /var/lib/apt/lists/*
COPY crontab /etc/cron.d/my-cron
RUN chmod 0644 /etc/cron.d/my-cron && crontab /etc/cron.d/my-cron
# Forward cron logs to Docker logs
CMD ["cron", "-f"]# crontab file
*/5 * * * * cd /app && python task.py >> /proc/1/fd/1 2>&1Key: Redirect output to /proc/1/fd/1 (PID 1's stdout) so logs appear in docker logs.
Approach 2: Supercronic (Recommended)
Supercronic is a cron replacement designed for containers. It runs in the foreground, logs to stdout/stderr, and handles signals properly.
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
# Install Supercronic
ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.33/supercronic-linux-amd64
RUN curl -fsSL "$SUPERCRONIC_URL" -o /usr/local/bin/supercronic \
&& chmod +x /usr/local/bin/supercronic
COPY crontab /app/crontab
CMD ["supercronic", "/app/crontab"]# crontab
*/5 * * * * cd /app && python task.py
0 0 * * * cd /app && python cleanup.pyAdvantages over system cron:
- Logs directly to stdout/stderr (works with
docker logs) - Handles
SIGTERMfor graceful shutdown - No init system required
- Passes environment variables to jobs
Approach 3: Ofelia (Docker-Native Scheduler)
Ofelia is a Docker job scheduler that runs as a separate container and triggers jobs in other containers:
# docker-compose.yml
services:
ofelia:
image: mcuadros/ofelia:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
ofelia.enabled: "true"
app:
build: .
labels:
ofelia.enabled: "true"
ofelia.job-exec.cleanup.schedule: "0 */5 * * * *"
ofelia.job-exec.cleanup.command: "python cleanup.py"Ofelia executes commands inside running containers using Docker exec, keeping your app container clean.
Environment Variables in Docker Cron
System cron doesn't inherit Docker environment variables. Two solutions:
1. Export env vars to a file:
CMD printenv > /etc/environment && cron -f# crontab
*/5 * * * * . /etc/environment; cd /app && python task.py >> /proc/1/fd/1 2>&12. Use an entrypoint script:
#!/bin/bash
# entrypoint.sh
printenv | grep -v "no_proxy" >> /etc/environment
exec "$@"With Supercronic, this is not an issue — it passes the container's environment to all jobs automatically.
Docker Compose Example
A common pattern: run your web app and cron worker as separate services from the same image:
services:
web:
build: .
command: gunicorn app:app --bind 0.0.0.0:8000
ports:
- "8000:8000"
env_file: .env
cron:
build: .
command: supercronic /app/crontab
env_file: .env
depends_on:
- db
- redis
db:
image: postgres:16
redis:
image: redis:7Both web and cron share the same Dockerfile but run different commands.
Common Cron Expressions
Standard 5-field expressions:
*/5 * * * *— Every 5 minutes0 * * * *— Every hour0 0 * * *— Daily at midnight0 2 * * *— Daily at 2 AM (common for cleanup tasks)0 0 * * 0— Every Sunday at midnight
Every weekday at 9:00 AM
Next runs (UTC):
Mon, May 18, 2026 09:00
Tue, May 19, 2026 09:00
Wed, May 20, 2026 09:00
Production Tips
Health checks:
HEALTHCHECK --interval=60s --timeout=10s \
CMD test -f /tmp/cron-heartbeat && \
find /tmp/cron-heartbeat -mmin -5 | grep -q .Have your most frequent cron job touch /tmp/cron-heartbeat on success.
Resource limits:
# docker-compose.yml
cron:
deploy:
resources:
limits:
cpus: '0.5'
memory: 256MGraceful shutdown: Supercronic handles SIGTERM and waits for running jobs to complete. With system cron, jobs are killed immediately on docker stop.