Docker Cron Jobs: Complete Container Scheduling Guide
Master container scheduling with Docker cron jobs, Docker Compose automation, Kubernetes CronJobs, and production-ready container management strategies.
What You'll Learn
- Docker container cron job fundamentals
- Docker Compose scheduling strategies
- Kubernetes CronJobs and best practices
- Container maintenance and cleanup automation
- Multi-container orchestration patterns
- Production deployment and monitoring
Docker Container Cron Jobs Fundamentals
Docker containers provide isolated, portable environments for scheduled tasks. Understanding how to effectively run cron jobs in containers is essential for modern application deployment and maintenance.
Running Cron Inside Docker Containers
There are several approaches to implementing cron jobs with Docker:
# Method 1: Cron inside container
FROM ubuntu:20.04
# Install cron
RUN apt-get update && apt-get install -y cron
# Add crontab file
COPY mycron /etc/cron.d/mycron
RUN chmod 0644 /etc/cron.d/mycron
RUN crontab /etc/cron.d/mycron
# Create log file
RUN touch /var/log/cron.log
# Run cron in foreground
CMD cron && tail -f /var/log/cron.log
Example: Database Backup Container
# Dockerfile for backup container
FROM postgres:13
# Install cron and required tools
RUN apt-get update && apt-get install -y \
cron \
awscli \
gzip \
&& rm -rf /var/lib/apt/lists/*
# Copy backup script
COPY backup-script.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/backup-script.sh
# Setup crontab
RUN echo "0 2 * * * /usr/local/bin/backup-script.sh" > /etc/cron.d/backup-cron
RUN chmod 0644 /etc/cron.d/backup-cron
RUN crontab /etc/cron.d/backup-cron
# Create log directory
RUN mkdir -p /var/log/backup
# Start cron daemon
CMD ["cron", "-f"]
Backup Script Example
#!/bin/bash
# backup-script.sh
set -e
DB_HOST=${DB_HOST:-postgres}
DB_NAME=${DB_NAME:-myapp}
DB_USER=${DB_USER:-postgres}
S3_BUCKET=${S3_BUCKET:-my-backups}
BACKUP_DIR="/tmp/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Create backup directory
mkdir -p $BACKUP_DIR
# Create database backup
echo "Starting backup at $(date)"
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > $BACKUP_DIR/backup_$TIMESTAMP.sql
# Compress backup
gzip $BACKUP_DIR/backup_$TIMESTAMP.sql
# Upload to S3
aws s3 cp $BACKUP_DIR/backup_$TIMESTAMP.sql.gz s3://$S3_BUCKET/$(date +%Y/%m/%d)/
# Cleanup local backup
rm $BACKUP_DIR/backup_$TIMESTAMP.sql.gz
echo "Backup completed successfully at $(date)" >> /var/log/backup/backup.log
Host-based Cron with Docker Containers
Running cron on the host system and executing Docker containers provides better resource management and easier monitoring.
# Host crontab entries for Docker containers
# Database backup every night at 2 AM
0 2 * * * docker run --rm --name backup-job \
--network myapp_network \
-e DB_HOST=postgres \
-e DB_NAME=myapp \
-v /opt/backups:/backups \
myapp/backup:latest
# Log rotation every day at midnight
0 0 * * * docker run --rm \
-v /var/log/myapp:/logs \
logrotate:latest
# Cleanup old containers weekly
0 3 * * 0 docker system prune -f
# Health check every 5 minutes
*/5 * * * * docker run --rm \
--network myapp_network \
healthcheck:latest
Docker Compose Scheduling
Docker Compose enables complex multi-container scheduling scenarios with dependencies and shared networks.
Scheduled Services with Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build: .
networks:
- app-network
depends_on:
- postgres
- redis
postgres:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
redis:
image: redis:6
networks:
- app-network
# Backup service (runs on schedule)
backup:
build: ./backup
environment:
DB_HOST: postgres
DB_NAME: myapp
DB_USER: user
DB_PASSWORD: password
volumes:
- backup_data:/backups
networks:
- app-network
depends_on:
- postgres
profiles:
- backup
# Log processor (runs on schedule)
log-processor:
build: ./log-processor
volumes:
- /var/log/myapp:/logs:ro
- processed_logs:/processed
profiles:
- maintenance
# Cleanup service (runs on schedule)
cleanup:
build: ./cleanup
volumes:
- postgres_data:/var/lib/postgresql/data:ro
- backup_data:/backups
profiles:
- maintenance
volumes:
postgres_data:
backup_data:
processed_logs:
networks:
app-network:
Scheduled Execution with Host Cron
# Host crontab for Docker Compose services
# Daily backup at 2 AM
0 2 * * * cd /opt/myapp && docker-compose --profile backup run --rm backup
# Weekly maintenance at 3 AM Sunday
0 3 * * 0 cd /opt/myapp && docker-compose --profile maintenance run --rm cleanup
# Daily log processing at 1 AM
0 1 * * * cd /opt/myapp && docker-compose --profile maintenance run --rm log-processor
# Health check every 10 minutes
*/10 * * * * cd /opt/myapp && docker-compose exec app /health-check.sh || \
docker-compose restart app
Kubernetes CronJobs
Kubernetes provides native CronJob resources for container scheduling in cluster environments.
CronJob Manifest Example
# backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
namespace: production
spec:
schedule: "0 2 * * *" # Daily at 2 AM
timeZone: "America/New_York"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: myregistry/backup:v1.2.0
env:
- name: DB_HOST
value: "postgres.database.svc.cluster.local"
- name: DB_NAME
valueFrom:
secretKeyRef:
name: db-secrets
key: database-name
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secrets
key: password
- name: S3_BUCKET
value: "production-backups"
volumeMounts:
- name: backup-storage
mountPath: /backups
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc
CronJob Best Practices
# log-processing-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: log-processor
namespace: production
spec:
schedule: "*/15 * * * *" # Every 15 minutes
concurrencyPolicy: Replace
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 2
jobTemplate:
spec:
activeDeadlineSeconds: 600 # 10 minutes timeout
template:
metadata:
labels:
app: log-processor
version: v1.0.0
spec:
restartPolicy: Never
serviceAccountName: log-processor
containers:
- name: processor
image: myregistry/log-processor:v1.0.0
env:
- name: ELASTICSEARCH_URL
valueFrom:
configMapKeyRef:
name: log-config
key: elasticsearch-url
- name: LOG_LEVEL
value: "INFO"
- name: BATCH_SIZE
value: "1000"
volumeMounts:
- name: app-logs
mountPath: /logs
readOnly: true
- name: config
mountPath: /config
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: app-logs
hostPath:
path: /var/log/applications
- name: config
configMap:
name: log-processor-config
Multi-Container CronJob
# data-pipeline-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: data-pipeline
spec:
schedule: "0 4 * * *" # Daily at 4 AM
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
initContainers:
- name: data-validator
image: myregistry/validator:latest
command: ["/bin/sh"]
args:
- -c
- |
echo "Validating data sources..."
/validate-sources.sh
if [ $? -ne 0 ]; then
echo "Data validation failed"
exit 1
fi
env:
- name: DATA_SOURCE_URL
valueFrom:
secretKeyRef:
name: data-secrets
key: source-url
containers:
- name: data-processor
image: myregistry/processor:latest
command: ["/process-data.sh"]
env:
- name: OUTPUT_FORMAT
value: "parquet"
- name: COMPRESSION
value: "gzip"
volumeMounts:
- name: data-volume
mountPath: /data
- name: data-uploader
image: myregistry/uploader:latest
command: ["/upload-data.sh"]
env:
- name: S3_BUCKET
value: "data-lake-bucket"
- name: AWS_REGION
value: "us-west-2"
volumeMounts:
- name: data-volume
mountPath: /data
readOnly: true
volumes:
- name: data-volume
emptyDir: {}
restartPolicy: OnFailure
Container Maintenance and Cleanup
Automated Container Cleanup
#!/bin/bash
# container-cleanup.sh
# Remove stopped containers older than 24 hours
docker container prune --filter "until=24h" --force
# Remove unused images older than 7 days
docker image prune --all --filter "until=168h" --force
# Remove unused volumes
docker volume prune --force
# Remove unused networks
docker network prune --force
# Clean up build cache
docker builder prune --force
# Log cleanup results
echo "$(date): Docker cleanup completed" >> /var/log/docker-cleanup.log
# Send metrics to monitoring system
CONTAINERS_REMOVED=$(docker system df | grep "Containers" | awk '{print $4}')
IMAGES_REMOVED=$(docker system df | grep "Images" | awk '{print $4}')
curl -X POST "http://monitoring:8086/write?db=docker_metrics" \
--data-binary "docker_cleanup,host=$(hostname) containers_removed=$CONTAINERS_REMOVED,images_removed=$IMAGES_REMOVED $(date +%s)000000000"
Host Cron Entries for Container Maintenance
# Container maintenance cron jobs
# Daily cleanup at 3 AM
0 3 * * * /opt/scripts/container-cleanup.sh
# Weekly system prune (more aggressive)
0 4 * * 0 docker system prune --all --force
# Monitor disk usage every hour
0 * * * * /opt/scripts/check-docker-disk-usage.sh
# Restart unhealthy containers every 10 minutes
*/10 * * * * /opt/scripts/restart-unhealthy-containers.sh
# Update running containers (controlled rollout)
0 5 * * 1 /opt/scripts/update-containers.sh
Docker Image Management
Automated Image Updates
#!/bin/bash
# update-containers.sh
IMAGES=(
"nginx:latest"
"postgres:13"
"redis:6"
"myapp/backend:latest"
"myapp/frontend:latest"
)
UPDATE_LOG="/var/log/container-updates.log"
echo "$(date): Starting container update process" >> $UPDATE_LOG
for image in "${IMAGES[@]}"; do
echo "Checking for updates: $image"
# Pull latest image
docker pull $image
# Check if update is available
OLD_IMAGE_ID=$(docker images --format "{{.ID}}" $image | head -1)
docker pull $image
NEW_IMAGE_ID=$(docker images --format "{{.ID}}" $image | head -1)
if [ "$OLD_IMAGE_ID" != "$NEW_IMAGE_ID" ]; then
echo "$(date): Updated image $image" >> $UPDATE_LOG
# Find running containers using this image
CONTAINERS=$(docker ps --filter "ancestor=$image" --format "{{.Names}}")
for container in $CONTAINERS; do
echo "Restarting container: $container"
# Graceful restart with health check
docker restart $container
# Wait for health check
sleep 30
# Verify container is healthy
if docker ps --filter "name=$container" --filter "status=running" | grep -q $container; then
echo "$(date): Successfully restarted $container" >> $UPDATE_LOG
else
echo "$(date): FAILED to restart $container" >> $UPDATE_LOG
# Send alert
curl -X POST "https://hooks.slack.com/webhook" \
-d "{"text": "❌ Failed to restart container: $container"}"
fi
done
fi
done
# Cleanup old images
docker image prune --force
echo "$(date): Container update process completed" >> $UPDATE_LOG
Container Health Monitoring
Comprehensive Health Check Script
#!/bin/bash
# container-health-monitor.sh
UNHEALTHY_CONTAINERS=()
ALERT_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
# Check all running containers
docker ps --format "{{.Names}}" | while read container; do
echo "Checking health of container: $container"
# Get container health status
health_status=$(docker inspect --format='{{.State.Health.Status}}' $container 2>/dev/null)
container_status=$(docker inspect --format='{{.State.Status}}' $container)
# Check if container has health check configured
if [ "$health_status" == "unhealthy" ]; then
echo "❌ Container $container is unhealthy"
UNHEALTHY_CONTAINERS+=($container)
# Get health check logs
health_logs=$(docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' $container)
# Send alert
curl -X POST -H 'Content-type: application/json' \
--data "{
"text": "🚨 Container Health Alert",
"attachments": [{
"color": "danger",
"fields": [{
"title": "Container",
"value": "$container",
"short": true
}, {
"title": "Status",
"value": "$health_status",
"short": true
}, {
"title": "Health Logs",
"value": "$health_logs",
"short": false
}]
}]
}" $ALERT_WEBHOOK
elif [ "$container_status" != "running" ]; then
echo "❌ Container $container is not running (status: $container_status)"
# Attempt to restart
echo "Attempting to restart $container..."
docker restart $container
# Verify restart
sleep 10
new_status=$(docker inspect --format='{{.State.Status}}' $container)
if [ "$new_status" == "running" ]; then
echo "✅ Successfully restarted $container"
else
echo "❌ Failed to restart $container"
UNHEALTHY_CONTAINERS+=($container)
fi
else
echo "✅ Container $container is healthy"
fi
# Check resource usage
cpu_usage=$(docker stats --no-stream --format "{{.CPUPerc}}" $container | sed 's/%//')
memory_usage=$(docker stats --no-stream --format "{{.MemPerc}}" $container | sed 's/%//')
# Alert on high resource usage
if (( $(echo "$cpu_usage > 80" | bc -l) )); then
echo "⚠️ High CPU usage for $container: $cpu_usage%"
fi
if (( $(echo "$memory_usage > 80" | bc -l) )); then
echo "⚠️ High memory usage for $container: $memory_usage%"
fi
done
# Summary report
if [ ${#UNHEALTHY_CONTAINERS[@]} -gt 0 ]; then
echo "Unhealthy containers detected: ${UNHEALTHY_CONTAINERS[*]}"
exit 1
else
echo "All containers are healthy"
exit 0
fi
Production Best Practices
Resource Management
Container Resource Limits
Always set resource limits for containers running cron jobs to prevent resource exhaustion.
docker run --memory="512m" --cpus="0.5" --rm myapp/backup:latest
Logging and Monitoring
# docker-compose.yml with logging
version: '3.8'
services:
backup-job:
build: ./backup
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "service=backup,environment=production"
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
Security Considerations
- Non-root User: Run containers with non-privileged users
- Read-only Filesystem: Use read-only containers when possible
- Secret Management: Use Docker secrets or Kubernetes secrets
- Network Isolation: Use custom networks to isolate containers
# Secure Dockerfile example
FROM node:16-alpine
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# Set working directory
WORKDIR /app
# Copy and install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy application code
COPY . .
RUN chown -R nodejs:nodejs /app
# Switch to non-root user
USER nodejs
# Use read-only filesystem
VOLUME ["/tmp"]
# Run application
CMD ["node", "index.js"]
CI/CD Integration
GitHub Actions for Container Scheduling
# .github/workflows/scheduled-tasks.yml
name: Scheduled Container Tasks
on:
schedule:
# Run backup every day at 2 AM UTC
- cron: '0 2 * * *'
# Run maintenance weekly on Sunday at 3 AM UTC
- cron: '0 3 * * 0'
workflow_dispatch: # Allow manual trigger
jobs:
backup:
if: github.event.schedule == '0 2 * * *'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build backup container
run: docker build -t backup:latest ./backup
- name: Run backup
env:
DB_HOST: ${{ secrets.DB_HOST }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
run: |
docker run --rm \
-e DB_HOST=$DB_HOST \
-e DB_PASSWORD=$DB_PASSWORD \
-e S3_BUCKET=$S3_BUCKET \
backup:latest
- name: Notify on failure
if: failure()
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
-d '{"text": "❌ Backup job failed in GitHub Actions"}'
maintenance:
if: github.event.schedule == '0 3 * * 0'
runs-on: ubuntu-latest
steps:
- name: Run maintenance tasks
run: |
docker run --rm maintenance:latest
Troubleshooting Common Issues
Container Won't Start
Debug Steps
1. Check container logs: docker logs container-name
2. Inspect container configuration: docker inspect container-name
3. Test with interactive shell: docker run -it --entrypoint /bin/bash image-name
4. Verify environment variables and secrets
Performance Issues
# Monitor container performance
docker stats --no-stream
# Check container resource usage over time
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
netdata/netdata:latest
# Profile container resource usage
docker run --rm --pid container:target-container \
nicolaka/netshoot htop
Conclusion
Docker provides powerful capabilities for containerized cron jobs, from simple single-container tasks to complex Kubernetes-orchestrated workflows. By following these patterns and best practices, you can build reliable, scalable, and maintainable container scheduling systems.
Remember to always implement proper monitoring, logging, and error handling for production container workloads, and consider the trade-offs between different scheduling approaches based on your specific requirements.
Ready to Schedule Your Containers?
Start building your container scheduling solutions with our cron expression generator.