Skip to content

Instantly share code, notes, and snippets.

@h0tw1r3
Created January 30, 2026 11:23
Show Gist options
  • Select an option

  • Save h0tw1r3/0c26ca958600a87f63f6cf947306e7ef to your computer and use it in GitHub Desktop.

Select an option

Save h0tw1r3/0c26ca958600a87f63f6cf947306e7ef to your computer and use it in GitHub Desktop.
Running cron in docker... what could go wrong?
SHELL=/bin/bash
BASH_ENV=/etc/environment
@reboot root echo "yes, it's working" > >(logger -t CRON[$$]) 2> >(logger -p user.err -t CRON[$$])
* * * * * root echo "this happened" > >(logger -t CRON[$$]) 2> >(logger -p user.err -t CRON[$$])
FROM debian:bookworm-slim AS install
ENV DEBIAN_FRONTEND=noninteractive
ENV LANG=C.UTF-8
SHELL ["/bin/bash", "-euxo", "pipefail", "-c"]
# Install cron and backup requirements
RUN <<EOF
echo "LANG=${LANG}" > /etc/default/locale
apt-get -qq update
apt-get -qq install --no-install-recommends -y cron rsyslog
### Install any packages required to support jobs here ###
# remove any default cron jobs
rm -rf /etc/cron.*/*
# cleanup
apt-get purge -y --allow-remove-essential --auto-remove apt
rm -rf /var/lib/apt /var/cache/apt
EOF
FROM scratch
COPY --from=install / /
# needed to log cron service output
COPY rsyslog.conf /etc/rsyslog.conf
COPY entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY cron.d/ /etc/cron.d/
COPY healthcheck.sh /healthcheck
HEALTHCHECK --start-period=5s --timeout=5s --retries=2 CMD /healthcheck
STOPSIGNAL SIGINT
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD []
#!/usr/bin/env bash
# if command is specified, run it and exit
if [ $# -gt 0 ] ; then
exec "$@"
exit $?
fi
# check required environment variables
required_vars=()
missing_vars=""
for var in "${required_vars[@]}"; do
if [ -z "${!var:-}" ]; then
missing_vars+=", $var"
fi
done
if [ -n "${missing_vars}" ] ; then
echo >&2 "ERROR: required environment variables are not set: ${missing_vars:2}"
exit 1
fi
unset missing_vars
# boilerplate entrypoint code
set -o nounset -o pipefail -o errtrace -o errexit -o functrace
exec 0<&-
# trap all errors and report line number, error code and command
error_trap() {
local el=${1:=??} ec=${2:=??} lc="$BASH_COMMAND"
echo >&2 "ERROR in $(basename "$0") : $el error $ec : $lc"
exit "${2:=1}"
}
trap 'error_trap ${LINENO} ${?}' ERR
# support for graceful shutdown
shutdown() {
sleep 1
exec >/dev/fd/1 2>/dev/fd/2
kill "${LOG_PID:-1}" 2>/dev/null
exit
}
interrupt() { echo "received shutdown signal: $*"; shutdown ; }
trap "interrupt int" INT
trap "interrupt term" SIGTERM
# start rsyslog to capture logs
rsyslogd -n &
LOG_PID=$!
# wait for rsyslogd to start
timeout 10s bash -c 'while [ ! -S /dev/log ] ; do sleep 0.2; done' || {
echo >&2 "rsyslogd failed to start"
exit 1
}
# redirect stdout/stderr to logger
exec 1> >(logger -t "entrypoint") 2>&1
# write required vars so they are available to cron jobs
echo "writing required environment variables to /etc/environment"
for var in "${required_vars[@]}"; do
echo "$var='${!var}'" >> /etc/environment
done
cron -f &
# keep waiting for rsyslogd
while kill -0 $LOG_PID 2>/dev/null; do
wait $LOG_PID || sleep 1
done
#!/bin/bash
set -e -o pipefail -o nounset -o errexit
shopt -s extglob
# update interval counter
intervalfile="/dev/shm/healthcheck.interval"
count=0
if read -r count < "$intervalfile" ; then
count=$((count + 1))
fi
echo "$count" >"$intervalfile"
only_every() {
local interval=$1
shift
(( count % interval == 0 )) || return
"$@"
}
fail_pid() {
echo >&2 "failed to validate PID of $*"
exit 1
}
verify_pidfile() { # $1: pidfile, $2: pattern
pid=$(<"$1")
verify_pid "${pid##*( )}" "$2"
}
verify_pid() {
if ! grep -aq "$2" "/proc/$1/cmdline" 2>/dev/null ; then
if [ -z "${3:-}" ] ; then
# sometimes start-stop-daemon causes pidfile to be -1
# of the real process...
pid=$(($1 + 1))
verify_pid $pid "$2" stop
else
return 1
fi
fi
}
# expected processes running
verify_pidfile /run/crond.pid ^cron || fail_pid cron
verify_pidfile /run/rsyslogd.pid ^rsyslogd || fail_pid rsyslogd
global(workDirectory="/tmp")
template(name="docker" type="string" string="%$.syslogtag% <%syslogpriority-text%> %$.msg%\r\n")
# local logging (e.g. via logger command)
module(load="imuxsock" SysSock.Use="on")
# messages may not well formatted, clean them up here
ruleset(name="rewrite_logs") {
set $.syslogtag = $syslogtag;
set $.msg = rtrim(ltrim($msg));
if $programname == 'CRON' then {
if re_match($.msg, 'pam_unix\\(cron:session\\): session (opened|closed) for user root') then { stop }
set $.found = re_extract($.msg, '( ?> >\\(logger .*CRON\\[..\\]\\) 2> >\\(logger .*CRON\\[..\\]\\))', 0, 1, "");
if ($.found != "") then {
set $.msg = replace($.msg, $.found, "");
}
}
action(type="omfile" file="/proc/1/fd/1" template="docker")
}
# attach the ruleset
input(type="imuxsock" socket="/dev/log" ruleset="rewrite_logs")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment