Skip to content

Instantly share code, notes, and snippets.

@andyshinn
Created December 24, 2015 19:07
Show Gist options
  • Star 80 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save andyshinn/3ae01fa13cb64c9d36e7 to your computer and use it in GitHub Desktop.
Save andyshinn/3ae01fa13cb64c9d36e7 to your computer and use it in GitHub Desktop.
BusyBox cron container example
FROM gliderlabs/alpine:3.3
COPY myawesomescript /bin/myawesomescript
COPY root /var/spool/cron/crontabs/root
RUN chmod +x /bin/myawesomescript
CMD crond -l 2 -f
#!/bin/sh
echo "Hi Andy" >> /dev/stdout
* * * * * /bin/myawesomescript
@desprit
Copy link

desprit commented Sep 29, 2016

It is very hard to find the meaning on crond arguments. Could you please tell what do -l 2 -f mean?

@jaimewyant
Copy link

Try crond --help:

/ # crond --help
BusyBox v1.24.2 (2016-06-22 17:51:28 GMT) multi-call binary.

Usage: crond -fbS -l N -d N -L LOGFILE -c DIR

    -f  Foreground
    -b  Background (default)
    -S  Log to syslog (default)
    -l N    Set log level. Most verbose:0, default:8
    -d N    Set log level, log to stderr
    -L FILE Log to FILE
    -c DIR  Cron dir. Default:/var/spool/cron/crontabs

@niondir
Copy link

niondir commented Oct 24, 2016

crond[5]: wakeup dt=10
crond[5]: wakeup dt=50
crond[5]: file root:
crond[5]:  line /bin/myawesomescript
crond[5]:  job: 0 /bin/myawesomescript
crond[15]: child running /bin/sh
crond[5]: USER root pid  15 cmd /bin/myawesomescript
/bin/sh: /bin/myawesomescript: not found

Any idea? When I enter the container the script exists. How can I debug this further?

@niondir
Copy link

niondir commented Oct 24, 2016

Wow I had #!bin/sh instead of #!/bin/sh in my script ...

@eduncan911
Copy link

eduncan911 commented Apr 2, 2017

BusyBox crond from a stock AlpineLinux docker image has the pre-configured cron schedules:

bash-4.3# cat /var/spool/cron/crontabs/root
# do daily/weekly/monthly maintenance
# min   hour    day     month   weekday command
*/15    *       *       *       *       run-parts /etc/periodic/15min
0       *       *       *       *       run-parts /etc/periodic/hourly
0       2       *       *       *       run-parts /etc/periodic/daily
0       3       *       *       6       run-parts /etc/periodic/weekly
0       5       1       *       *       run-parts /etc/periodic/monthly

NOTE: following the directions in the original post above will override these schedules, effectively disabling it with your custom root cron config file.

But you don't have to do that...

If one of the above schedules works for you, you can simply place your script in one of these directories as needed:

FROM alpine:3.5
COPY myawesomescript1 /etc/periodic/15min/myawesomescript1
COPY myawesomescript2 /etc/periodic/15min/myawesomescript2
COPY myawesomescript3 /etc/periodic/hourly/myawesomescript3
CMD crond -l 2 -f

It is important to know that your script cannot be suffixed with any extension name. E.g. no "." period can exist. It cannot end in .sh. That was the gotcha I ran into this weekend.

@mfurlend
Copy link

mfurlend commented Mar 1, 2018

@eduncan911

E.g. no "." period can exist. It cannot end in .sh

Thanks for the tip! I was getting pretty frustrated.

@shadiakiki1986
Copy link

+1 for cannot end in .sh

@ofekp
Copy link

ofekp commented Jan 7, 2019

Another +1 for cannot end in .sh

@mskyttner
Copy link

+1

@peabnuts123
Copy link

+1 for cannot end in .sh … just wasted a few hours on that …

@setop
Copy link

setop commented Oct 28, 2020

It is important to know that your script cannot be suffixed with any extension name. E.g. no "." period can exist. It cannot end in .sh. That was the gotcha I ran into this weekend.

How do you get this information ? Where does this limitation comes from ? I've browsed the source code, nothing obvious ...

@jsarenik
Copy link

jsarenik commented Feb 2, 2021

It is important to know that your script cannot be suffixed with any extension name. E.g. no "." period can exist. It cannot end in .sh. That was the gotcha I ran into this weekend.

How do you get this information ? Where does this limitation comes from ? I've browsed the source code, nothing obvious ...

Is this question related to the Dockerfile or busybox crond? The latter does not care if the file ends with .sh or not.

Following crontab line works:

* * * * * $HOME/testcrond.sh
~$ /busybox/crond -h
/busybox/crond: invalid option -- 'h'
BusyBox v1.33.0.git (2020-10-14 15:27:26 CEST) multi-call binary.

Usage: crond -fbS -l N -d N -L LOGFILE -c DIR

	-f	Foreground
	-b	Background (default)
	-S	Log to syslog (default)
	-l N	Set log level. Most verbose 0, default 8
	-d N	Set log level, log to stderr
	-L FILE	Log to FILE
	-c DIR	Cron dir. Default:/var/spool/cron/crontabs

@peabnuts123
Copy link

@jsarenik I believe the "no . in your file name" logic is part of run-parts, not crond or docker.

@setop
Copy link

setop commented Feb 11, 2021

Is this question related to the Dockerfile or busybox crond? The latter does not care if the file ends with .sh or not.

I was speaking of crond. It is definitely a limitation of busybox crond. I have made cronjob on Debian based image and did not faced the issue.
I literally spent hours getting why it was not working on alpine...

@peabnuts123
Copy link

run-parts runs all the executable files named within constraints described below, found in directory directory. Other files and directories are silently ignored.

If neither the --lsbsysinit option nor the --regex option is given then the names must consist entirely of ASCII upper- and lower-case letters, ASCII digits, ASCII underscores, and ASCII minus-hyphens.

@mskyttner
Copy link

mskyttner commented Feb 12, 2021

So it seems that for executable scripts that get triggered from locations like /etc/periodic/hourly it would be nice to have the --lsbsysinit option active as the default for these kinds of use cases, as outlined in https://gist.github.com/andyshinn/3ae01fa13cb64c9d36e7#gistcomment-2044506?

This article provides an example of usage from docker-compose:

version: '3'

services:
  cron:
    image: alpine:3.11
    command: /usr/local/startup.sh && crond -f -l 8
    volumes:
      - ./cron_tasks_folder/15min:/etc/periodic/15min/:ro
      - ./cron_tasks_folder/hourly:/etc/periodic/hourly/:ro
      - ./cron_tasks_folder/1min:/etc/periodic/1min/:ro
      - ./scripts/startup.sh:/usr/local/startup.sh:ro

How can run-parts somehow be configured to use the --lsbsysinit option by default, which should allow mounting /etc/periodic/hourly/myawesomescript3.sh?

If the --lsbsysinit option is given,  then  the  names  must  not  end  in  .dpkg-old   or
       .dpkg-dist  or  .dpkg-new  or  .dpkg-tmp,  and must belong to one or more of the following
       namespaces: the LANANA-assigned namespace (^[a-z0-9]+$); the LSB hierarchical and reserved
       namespaces  (^_?([a-z0-9_.]+-)+[a-z0-9]+$);  and the Debian cron script namespace (^[a-zA-
       Z0-9_-]+$).

It seems the "Debian cron script namespace" does not include a "." (not sure why?) but the "LSB hierarchical and reserved namespaces" does?

@peabnuts123
Copy link

Just mount your own file over the top of /var/spool/cron/crontabs/root (be VERY careful with file permissions here - must be owned by root and chmod 600 on your HOST) and replace e.g. run-parts /etc/periodic/15min with run-parts --lsbsysinit /etc/periodic/15min

@jsarenik
Copy link

jsarenik commented May 13, 2021

@jsarenik I believe the "no . in your file name" logic is part of run-parts, not crond or docker.

Ah yes, totally in run-parts. Here is a patch:

diff --git a/debianutils/run_parts.c b/debianutils/run_parts.c
index 585a4b58f..7677ab894 100644
--- a/debianutils/run_parts.c
+++ b/debianutils/run_parts.c
@@ -119,7 +119,10 @@ static bool invalid_name(const char *c)
 {
 	c = bb_basename(c);
 
-	while (*c && (isalnum(*c) || *c == '_' || *c == '-'))
+	if (*c == '.')
+		return *c;
+
+	while (*c && (isalnum(*c) || *c == '_' || *c == '-' || *c == '.'))
 		c++;
 
 	return *c; /* TRUE (!0) if terminating NUL is not reached */

To test:

mkdir /tmp/testrp
printf "#!/bin/sh\necho test\n" > /tmp/testrp/test.sh
chmod a+x /tmp/testrp/*
busybox run-parts /tmp/testrp
# "test\n" on stdout
mv /tmp/testrp/test.sh /tmp/testrp/.test.sh
busybox run-parts /tmp/testrp
# no output

Planning to send it upstream as soon as I refresh memories of how things are properly done on the mailing list.

UPDATE: Patch sent to the busybox mailing-list and I used exactly this original git documentation.

UPDATE: Patch merged in 8c1f8aa01.

This patch is tested (with busybox@7de0ab21d) and works.

@peabnuts123
Copy link

Lol wow. Wonder how many people's workflows are going to break because of this 😂

@jsarenik
Copy link

Lol wow. Wonder how many people's workflows are going to break because of this

Haha :) Should be pretty backward-compatible since the hidden files are still ignored (though were not in my originally proposed patch, but luckily we have peer-review :-)

@peabnuts123
Copy link

But I assume lots of people's workflows are based on the fact that files with a . in the filename are not executed 😅 Whether they know that or not

@jsarenik
Copy link

jsarenik commented Sep 1, 2021

But I assume lots of people's workflows are based on the fact that files with a . in the filename are not executed Whether they know that or not

Yes. And with the patch it works exactly like you say. Just that also file.sh gets executed now (despite the dot).

@peabnuts123
Copy link

Yeah bud, file.sh is exactly what I was talking about

@setop
Copy link

setop commented Sep 14, 2021

Yeah bud, file.sh is exactly what I was talking about

Do you really think people know file.sh will not be executed ? Moreover not executed on Alpine but executed on Debian ?
I'm unfortunately for me - and the time I spent - not part of these people.

@LostOnTheLine
Copy link

Try crond --help:

/ # crond --help
BusyBox v1.24.2 (2016-06-22 17:51:28 GMT) multi-call binary.

Usage: crond -fbS -l N -d N -L LOGFILE -c DIR

    -f  Foreground
    -b  Background (default)
    -S  Log to syslog (default)
    -l N    Set log level. Most verbose:0, default:8
    -d N    Set log level, log to stderr
    -L FILE Log to FILE
    -c DIR  Cron dir. Default:/var/spool/cron/crontabs

I've been looking & this is the most information I can find about this...

for the crond -fbS -l N -d N -L LOGFILE -c DIR it says -l has options from 0 to at least 8 with 0 giving the most detail, it then has -d that maybe has the same scale, but is it -d=log to stderr? so, for example if I ran cron -fbS -l 2 -d 0 -L /var/log/cron.log would it log to the foreground, background, & syslog with level 2 & log to STDERR with verbose log (0) while logging what to /var/log/cron.log? Would it be the level set as -l?

@eduncan911
Copy link

eduncan911 commented Dec 6, 2022

Whoa... You mean this thread, my comment, spawned a patch upstream to BusyBox?! Niiiiiiice. Thanks @jsarenik !

@jsarenik
Copy link

jsarenik commented Dec 6, 2022

@eduncan911 My pleasure :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment