From scratch or from a base image of your choice.


Prerequisite: if you start from a base image, is it based on alpine or debian?

The syntax is not the same because debian uses cron 1 while alpine uses crond 2.

Check with:

$ docker run --rm ghcr.io/borgmatic-collective/borgmatic:latest cat /etc/os-release

Output:
NAME="Alpine Linux"

1. From a fresh Debian/Ubuntu base (crontab)

Dockerfile

FROM debian:bookworm-slim

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

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh

#!/bin/sh
set -e

(printenv | grep -v "^_="; echo "0 2 * * * /my/script.sh >> /var/log/cron.log 2>&1") | crontab -

exec cron -f

Unlike with alpine, the environment variables are not available to the script if you miss the printenv part 3

2. From an existing image, based on Alpine: example of borgmatic (crond)

compose.yaml

services:
  borgmatic:
    build: ./borgmatic

borgmatic/Dockerfile

FROM ghcr.io/borgmatic-collective/borgmatic:latest

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"] 
CMD []

borgmatic/entrypoint.sh

#!/bin/sh
set -e

borgmatic init ... # Because we override the entrypoint, we need to insert the commands that it would have ran

# 2 AM
echo "0 2 * * * borgmatic 2>&1 | tee -a /var/log/cron.log" > /etc/crontabs/root

exec crond -f

Unlike with Debian, environment variables are inherited. No tricks needed 4

Troubleshooting

Does the script work inside the container?

  • Trigger the script manually: docker exec <container> /my/script.sh

Does the cron trigger?

  • Check if the cron is firing: run it every minute using * * * * * and add a log: echo "$(date) cron fired" >> /var/log/cron.log; /my/script.sh
⚠️
Note: output goes to /var/log/cron.log only, and will not show up in docker logs <container>. Instead use docker exec <container> tail -n 500 -f /var/log/cron.log