Running Cron Jobs Inside Docker Containers — A Tutorial

Docker containers are designed to run one main process, but scheduled jobs don't disappear just because your app is containerized. There are three common approaches, each with different tradeoffs around logging, restart behavior, and how well they fit the "one process per container" philosophy.

Approach 1: cron daemon running inside the container

The most self-contained option — install cron in the image and start it as the container's main process. Works well for a dedicated "jobs" image that does nothing else:

# Dockerfile
FROM node:20-slim

RUN apt-get update && apt-get install -y cron && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

# Install the crontab
COPY crontab.txt /etc/cron.d/app-cron
RUN chmod 0644 /etc/cron.d/app-cron && crontab /etc/cron.d/app-cron

# Cron doesn't log to stdout by default — redirect to the container's
# stdout/stderr so "docker logs" actually shows job output
CMD cron -f
# crontab.txt — redirect output to the container's stdout (PID 1)
*/10 * * * * root /app/scripts/sync.sh >> /proc/1/fd/1 2>/proc/1/fd/2

Downsides: cron becomes PID 1 instead of your app, so Docker health checks need to target the cron process itself, and a crashed job doesn't naturally restart the container the way a crashed main process would.

Approach 2: host-level cron calling docker exec

Keep the container running only the application, and let the host machine's cron trigger work inside it. This keeps the image clean and puts scheduling under normal OS-level monitoring:

# Host crontab (crontab -e on the Docker host)
*/10 * * * * docker exec app-container node /app/scripts/sync.js \
  >> /var/log/app-cron.log 2>&1

Downside: this ties scheduling to a specific host, which doesn't fit well with orchestrators like Kubernetes or ECS where containers move between nodes.

Approach 3: a dedicated sidecar / jobs container

In Docker Compose or Kubernetes, run a second, separate container whose only job is scheduling. It calls the main service over its API or a shared volume, keeping concerns cleanly separated:

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"

  cron:
    build:
      context: .
      dockerfile: Dockerfile.cron
    depends_on:
      - app
    environment:
      - APP_URL=http://app:3000

In Kubernetes, this same idea is built in natively as a CronJob resource, which spins up a fresh pod on schedule, runs the job to completion, and tears the pod down — no long-running cron daemon required at all:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: sync-orders
spec:
  schedule: "*/10 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: sync-orders
              image: myregistry/app:latest
              command: ["node", "scripts/sync.js"]
          restartPolicy: OnFailure

Which approach should you pick

  • In-container cron daemon — simplest for a single-server deployment with a dedicated jobs image; avoid it for your main application container.
  • Host cron + docker exec — fine for a single Docker host you fully control, but doesn't scale to multi-node or orchestrated environments.
  • Sidecar / Kubernetes CronJob — the cleanest fit for orchestrated environments; each run gets a fresh, isolated container and normal container logs/monitoring apply.

Frequently Asked Questions

Should I run cron inside a Docker container?

It works, but it goes against the one-process-per-container principle and makes health checks and logging harder because cron itself becomes PID 1 instead of your application. For simple, self-contained jobs it is acceptable; for anything critical, a dedicated sidecar container or host-level scheduler is usually cleaner.

How do I see cron job output in Docker logs?

Cron does not write to stdout by default, so docker logs shows nothing even if the job runs. Redirect the job's output to stdout explicitly in the crontab entry, for example by appending > /proc/1/fd/1 2>/proc/1/fd/2, or have the script itself print to stdout/stderr.

What is a sidecar container for cron jobs in Kubernetes or Docker Compose?

A sidecar is a separate, dedicated container that runs only the scheduler and triggers work in the main application container (via a shared volume, an API call, or docker exec), keeping the main container's image focused on just the application process.

Try the Free Cron Expression Generator

Whichever approach you pick, get the schedule expression right first — describe it in plain English and drop the result straight into your crontab or CronJob spec.

Related articles