CronDrive
Guías

Cómo ejecutar cron jobs en un contenedor de Docker

Actualizada el 16 de octubre de 2026

Ejecutar cron dentro de Docker requiere ajustar cómo se inicia el proceso, dónde se guarda la salida y qué entorno reciben los jobs. Esta guía cubre esos pasos en imágenes basadas en Debian, Ubuntu y Alpine.

Un ejemplo completo en Debian o Ubuntu

Cuatro archivos en un directorio: el Dockerfile, el crontab, un entrypoint.sh que inicia cron, y el 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, con una línea temporal que se ejecuta cada minuto para comprobar la instalación:

# Temporal, para comprobar la instalación: cada minuto.
* * * * * 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
# Las variables del contenedor, para los jobs: cron los inicia sin ellas.
export -p > /app/env.sh
chmod 600 /app/env.sh
exec cron -f

backup.sh, tu job:

#!/bin/sh
echo "Respaldo iniciado: $(date)"

Constrúyela, iníciala y mira su salida:

docker build -t cron-app .
docker run -d --name cron-app -e TZ=America/Santiago cron-app
docker logs -f cron-app

Antes de un minuto, docker logs muestra una fecha en la zona horaria que pasaste en TZ. Después, quita la línea temporal del crontab y vuelve a construir la imagen.

Lo mismo en Alpine

Alpine ya trae el crond de BusyBox. Su crontab es /etc/crontabs/root, y sus líneas no llevan usuario después del horario. Pasa las variables del contenedor a los jobs, así que no necesita 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"]
# Temporal, para comprobar la instalación: cada minuto.
* * * * * date > /proc/1/fd/1 2>&1
0 3 * * * /app/backup.sh > /proc/1/fd/1 2>&1

Por qué cada parte

  • cron -f mantiene cron en primer plano. Sin -f, cron pasa a segundo plano, el proceso principal del contenedor termina y el contenedor se detiene.
  • El archivo de /etc/cron.d/ no puede tener permiso de escritura para el grupo ni para otros: con 0664, cron lo ignora, y en un contenedor no hay syslog que lo avise. Sus líneas llevan el usuario (root) después del horario, su nombre no puede tener puntos (app, no app.cron) y tiene que terminar con un salto de línea.
  • > /proc/1/fd/1 2>&1 envía la salida del job a la del contenedor, que docker logs muestra. El cron de Debian y Ubuntu solo registra en syslog, y no envía la salida de los jobs a ningún lugar que puedas ver. Funciona con los jobs de root: los jobs de otro usuario no pueden escribir en la salida del proceso 1, así que dales un archivo.
  • env.sh les da a los jobs las variables del contenedor. El cron de Debian y Ubuntu inicia cada job casi sin variables: HOME, LOGNAME, SHELL y un PATH de /usr/bin:/bin. Lo que pasas con docker run -e, env_file o Kubernetes no está, así que un job que necesita DATABASE_URL o una clave de API falla. El archivo tiene los secretos del contenedor, por eso solo root puede leerlo.
  • tzdata y TZ definen la zona horaria. Los contenedores normalmente están en UTC. Con TZ, cron sigue el horario en esa zona; en Debian y Ubuntu los jobs solo reciben TZ a través de env.sh, así que sin él un date en un job muestra UTC.

Si los jobs siguen sin ejecutarse

  • ¿Sigue activo el contenedor? docker ps. Si se detiene, revisa cron -f.
  • ¿Es cron el proceso principal? docker top cron-app.
  • Escapa los % del crontab: date +\%F. Un % sin \ termina el comando, y el job no se ejecuta como está escrito.
  • El script tiene que ser ejecutable, con saltos de línea de Unix. El resto de las causas comunes están en por qué tu cron job no se ejecuta.

¿Necesitas cron dentro del contenedor?

A veces otra opción es más simple:

  • El cron del host, ejecutando el job en el contenedor con docker exec. El cron del host decide cuándo, en la zona horaria del host. El comando se ejecuta dentro del contenedor, con el entorno del contenedor. Su salida va adonde la redirijas en el host:

    0 3 * * * docker exec cron-app /app/backup.sh >> "$HOME/backup.log" 2>&1
  • Un CronJob de Kubernetes, que inicia un pod para cada ejecución. El recurso CronJob tiene su propia zona horaria en spec.timeZone, desde Kubernetes 1.27:

    spec:
      schedule: "0 3 * * *"
      timeZone: "America/Santiago"
  • supercronic, un cron hecho para contenedores, que registra en la salida y conserva el entorno.

Entérate la próxima vez que no se ejecute

Un contenedor que se reinició, una variable que faltó o una zona horaria mal puesta pueden detener un job, y estos problemas pueden pasar inadvertidos hasta que alguien revisa los logs. El monitoreo te avisa cuando una ejecución no empieza, tarda demasiado o falla. Si usas la línea de CronDrive en /etc/cron.d/, agrega el usuario después del horario, e instala curl en la imagen.

CronDrive te avisa por email, o en Slack, cuando un job con este horario falla, o no se inicia o no termina a tiempo. Gratis hasta 15 jobs.

Lo que verías con este horario

Un ejemplo con las últimas ejecuciones de este horario y resultados inventados: una falló y otra no se ejecutó.

Horas en UTC.

Hora
jue 15 oct, 03:00 A tiempo Inició 03:00, terminó 03:13, duró 13 min.
mié 14 oct, 03:00 No se ejecutó Alertas enviadas a las 03:05 y 03:30.
mar 13 oct, 03:00 A tiempo Inició 03:00, terminó 03:14, duró 14 min.
lun 12 oct, 03:00 Falló con código 1 Alerta enviada a las 03:04. Inició 03:00, terminó 03:04, duró 4 min.
dom 11 oct, 03:00 A tiempo Inició 03:00, terminó 03:13, duró 13 min.
sáb 10 oct, 03:00 A tiempo Inició 03:00, terminó 03:14, duró 14 min.

La alerta de la ejecución que falló

Email de CronDrive, lun 12 oct, 03:04

Falló (código 1): "Tu job"

El job "Tu job" falló con el código de salida 1.

Si conectas Slack, el mismo mensaje llega a tu canal.

El cambio en tu crontab

0 3 * * * curl -fsS -m 10 https://job.crondrive.com/uG2Exw/start/$$; /path/to/job.sh; curl -fsS -m 10 https://job.crondrive.com/uG2Exw/end/$?/$$

Dos llamadas alrededor de tu comando, en la misma línea. La línea de tu job trae su propio token.

Monitorear este horario