DEV Community

Cover image for How to safely run cron jobs in Docker (with monitoring)
Łukasz Maśląg
Łukasz Maśląg

Posted on

How to safely run cron jobs in Docker (with monitoring)

Have you encountered the following issues when running tasks using the system cron in Decoder?:

  • Logs disappear into thin air
  • Tasks fail at 3 a.m.
  • Environment variables don't work
  • Container doesn't shut down properly.

Problem with system cron

The cron daemon was designed for traditional Linux servers, not containers:

# ❌ Don't do this
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y cron
COPY mycron /etc/cron.d/mycron
RUN crontab /etc/cron.d/mycron
CMD cron -f
Enter fullscreen mode Exit fullscreen mode

What goes wrong:

  1. No logs - cron writes to syslog, not stdout
  2. Bad signals - SIGTERM doesn't stop jobs gracefully
  3. ENV hell - cron doesn't inherit Docker ENV variables
  4. PID 1 issues - if cron is PID 1, signal handling breaks

The Solution: Supercronic

Supercronic is cron reimagined for containers:

# ✅ Do this instead
FROM php:8.2-cli

ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \
    SUPERCRONIC=supercronic-linux-amd64 \
    SUPERCRONIC_SHA1SUM=cd48d45c4b10f3f0bfdd3a57d054cd05ac96812b

RUN curl -fsSLO "$SUPERCRONIC_URL" \
 && echo "${SUPERCRONIC_SHA1SUM}  ${SUPERCRONIC}" | sha1sum -c - \
 && chmod +x "$SUPERCRONIC" \
 && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
 && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic

COPY crontab /app/crontab
CMD ["supercronic", "/app/crontab"]
Enter fullscreen mode Exit fullscreen mode

The syntax in supercronic remains the same as in crontab:

# /app/crontab
*/15 * * * * php /app/bin/console app:import-data
0 2 * * * /app/scripts/backup.sh
0 */4 * * * php /app/bin/console cache:clear
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • ✅ Logs go to stdout (docker logs works!)
  • ✅ Graceful shutdown on SIGTERM
  • ✅ Inherits ENV from Docker automatically
  • ✅ Single process, no daemon complexity

Sample Symfony application running in a container:

Dockerfile:

FROM php:8.2-cli

# Install Supercronic
ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64
RUN curl -fsSLO "$SUPERCRONIC_URL" \
 && chmod +x supercronic-linux-amd64 \
 && mv supercronic-linux-amd64 /usr/local/bin/supercronic \
 && apt-get update && apt-get install -y git curl unzip

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Application
COPY . /app
WORKDIR /app
RUN composer install --no-dev --optimize-autoloader

COPY docker/crontab /app/crontab
CMD ["supercronic", "/app/crontab"]
Enter fullscreen mode Exit fullscreen mode

docker/crontab:

# Process message queue
* * * * * php /app/bin/console messenger:consume async --time-limit=3600

# Send scheduled emails
*/5 * * * * php /app/bin/console app:send-emails

# Generate daily reports
0 6 * * * php /app/bin/console app:generate-reports

# Cleanup old data
0 3 * * * php /app/bin/console cache:clear
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    # ... your app service

  cron:
    build: .
    container_name: app-cron
    restart: unless-stopped
    env_file: .env
    depends_on:
      - db
      - redis
    networks:
      - app-network
Enter fullscreen mode Exit fullscreen mode

Missing Element: Monitoring

Now the cron jobs are working, but how do we find out if and when they failed?

The job fails at 3 a.m. → We'll probably find out when we need to restore a database backup, and cron hasn't executed a script in two weeks :( → Data loss, angry users, a bad day.

Solution: HTTP pings

I created CronMonitor.app specifically for this purpose:

# Add ping after command
*/15 * * * * php /app/your-command.php && curl -fsS https://cronmonitor.app/ping/{token} 
Enter fullscreen mode Exit fullscreen mode

What you get:

  • Slack/Discord/email alerts when tasks aren't completed correctly
  • Dashboard displaying all tasks
  • Duration and error tracking
  • No agents, no complexity

Example of a complete multi-container solution:

Example of monitoring tasks across multiple containers in a production environment

version: '3.8'

services:
  # Main application
  app:
    image: myapp:latest
    # ...

  # Cron worker for background jobs
  cron-worker:
    build:
      context: .
      dockerfile: docker/Dockerfile.cron
    container_name: myapp-cron
    restart: unless-stopped
    env_file: .env
    depends_on:
      - db
      - redis
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
Enter fullscreen mode Exit fullscreen mode

docker/Dockerfile.cron:

FROM php:8.2-cli

# Supercronic
RUN curl -L https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64 \
    -o /usr/local/bin/supercronic && chmod +x /usr/local/bin/supercronic

# App dependencies
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
RUN apt-get update && apt-get install -y git curl unzip libzip-dev \
    && docker-php-ext-install pdo_mysql zip

# Application
COPY composer.* /app/
WORKDIR /app
RUN composer install --no-dev --no-scripts --optimize-autoloader

COPY . /app
RUN composer dump-autoload --optimize

COPY docker/crontab /app/crontab
CMD ["supercronic", "-debug", "/app/crontab"]
Enter fullscreen mode Exit fullscreen mode

docker/crontab with monitoring:

# Queue processor - critical, monitor closely
* * * * * curl -fsS https://cronmonitor.app/ping/{token} && php bin/console messenger:consume --time-limit=3600

# Hourly data sync
0 * * * * curl -fsS https://cronmonitor.app/ping/{token} && php bin/console app:sync-data || curl -fsS https://cronmonitor.io/fail/sync-def456

# Daily backup - MUST succeed
0 2 * * * curl -fsS https://cronmonitor.app/ping/{token} && /app/scripts/backup.sh

# Weekly cleanup
0 3 * * 0 curl -fsS https://cronmonitor.app/ping/{token} && php bin/console app:cleanup
Enter fullscreen mode Exit fullscreen mode

Debugging Tips

# View logs in real-time
docker logs -f myapp-cron

# Test individual commands
docker exec myapp-cron php bin/console app:your-command

# Check environment variables
docker exec myapp-cron env | grep DATABASE

# Manual crontab test
docker exec myapp-cron supercronic -test /app/crontab
Enter fullscreen mode Exit fullscreen mode

Migration Checklist

Moving from standard cron to this setup:

  • [ ] Replace cron with Supercronic in Dockerfile
  • [ ] Move crontab to separate file
  • [ ] Test ENV variables are accessible
  • [ ] Add monitoring pings to jobs
  • [ ] Update docker-compose with health checks
  • [ ] Set up resource limits+
  • [ ] Test graceful shutdown
  • [ ] Document your cron jobs!

Conclusion

After years of fighting with cron in Docker:

  1. Use Supercronic - it's designed for containers
  2. Monitor your jobs - silent failures are the worst
  3. Keep it simple - HTTP pings > complex monitoring

This stack has been running in production for months across multiple projects. Zero silent failures, clear logs, easy debugging.

Full documentation and examples:

If you have any questions, leave a comment!

--

  • I created CronMonitor.app after dealing with silent cron failures too many times. It's free for small projects, and I use it for both personal projects and strictly business applications.**What exactly:*
  • Slack/Discord/email alerts when tasks haven't completed correctly
  • Dashboard displaying all tasks
  • Duration and error tracking

Top comments (0)