0

I'm trying to run a cron job in a selenium container in Docker Desktop for windows. Because I think I'm running into several problems, it's hard for me to figure out which details matter so I'll try to be as thorough as possible.

Environment:

  1. Docker Desktop for Windows (to avoid line ending problems I make the cron string in the Dockerfile)
  2. Selenium-Chrome (one thing to note is that most things are run under seluser instead of root here. I say that because some of the other solutions don't work because of this)

Problem:

I cannot run python in my cron job

Related stack overflow links I've checked:

There are a lot but this is the main one.

For example, this snippet logs to the log file shown appropriately:

FROM selenium/standalone-chrome

COPY . /home/seluser/

# # install selenium
RUN echo "**** install packages ****" && \
    sudo apt-get update && \
    sudo apt-get install -y cron && \
    echo "**** cleanup ****" && \
    sudo apt-get clean && \
    sudo rm -rf \
    /tmp/* \
    /var/lib/apt/lists/* \
    /var/tmp/*

# Create the log file to be able to run tail
RUN touch /home/seluser/cron.log

# Setup cron job
RUN echo "* * * * * echo "Hello, World!" >> /home/seluser/cron.log" | sudo crontab

# Run the command on container startup
CMD sudo cron && tail -f /home/seluser/cron.log

But this doesn't:

FROM selenium/standalone-chrome

COPY . /home/seluser/

# # install selenium
RUN echo "**** install packages ****" && \
    sudo apt-get update && \
    sudo apt-get install -y cron && \
    echo "**** cleanup ****" && \
    sudo apt-get clean && \
    sudo rm -rf \
    /tmp/* \
    /var/lib/apt/lists/* \
    /var/tmp/*

# Create the log file to be able to run tail
RUN touch /home/seluser/cron.log

# Setup cron job
RUN echo "* * * * * /usr/bin/python3 -c print("Hello world") >> /home/seluser/cron.log" | sudo crontab

# Run the command on container startup
CMD sudo cron && tail -f /home/seluser/cron.log
Jaron
  • 117
  • 8
  • The double quote after `print(` and before `Hello` matches the double quote at the start of the `echo` line, so it gets lost; you don't have a complete valid single-shell-word Python script. It'd be better to `COPY` a self-contained runnable script and run that than try to make the quoting work. (You also shouldn't need `sudo` at all; you can switch to `USER root` where you need to.) – David Maze Sep 28 '20 at 00:29
  • For your first part, why would the echo script work then? It has the same double quote problem. As for the COPY solution, I wasn't able to figure out a way of getting it to work, even with just echo statements. The linked solution doesn't work on windows. – Jaron Sep 28 '20 at 00:48
  • The quoting happens to work out with `echo`; it doesn't really care how many arguments it has. In the `echo` case you might look at the crontab that's actually getting created; I wouldn't expect it to have any `"..."`. – David Maze Sep 28 '20 at 12:25
  • @DavidMaze I know this a year later... But if you want, you can create an answer out of your comments and I'll mark it as accepted as it helped me figure out my problem. – Jaron Sep 05 '21 at 13:53

1 Answers1

1

In your RUN command, you have both double quotes surrounding the entire command and also double quotes in the embedded Python script. The shell consumes these pairs of quotes, and so there are no quotes in the Python fragment at all.

You can work around this by using single quotes for either pair of quotes, or by backslash-escaping the double quotes within the double-quoted string. All of these will work:

RUN echo "... print(\"Hello world\") ..." | crontab
RUN echo "... print('Hello world') ..." | crontab
# NB: will not expand environment variables inside the string
RUN echo '... print("Hello world") ...' | crontab

The first form with echo appears to work because echo "Hello world", echo Hello world, and echo "Hello" "world" all print out the same string; it doesn't matter whether it's one or two parameters, and echo(1) itself never sees the double quotes. In the Python case, though, you need to pass the double quotes into the interpreter.

If you're facing challenges like this, often the best approach is to break out the embedded script into a separate file.

#!/usr/bin/env python3
# greet.py
if __name__ == '__main__':
  print('Hello world')

Then your cron job can just run the script.

RUN echo '* * * * * /home/seluser/greet.py >> /home/seluser/cron.log' | crontab

I've omitted sudo in my examples. In a Dockerfile you are generally running as root already, or you can specify USER root to switch to root. sudo is tricky to use in scripts and tends to lead to unnecessary and insecure configuration in Docker.

A final Dockerfile for this might look like:

FROM selenium/standalone-chrome

# Install OS packages _before_ copying any application code in.
# This avoids an expensive reinstallation on rebuild.
RUN apt-get update \
 && DEBIAN_FRONTEND=noninteractive \
    apt-get install --assume-yes --no-install-recommends \
      cron

# Now copy your own code in.  (Rebuilds will start here, if anything
# in the source tree has changed.)
COPY . /home/seluser

# Set up the cron environment.
RUN echo '* * * * * /home/seluser/greet.py >> /home/seluser/cron.log' | crontab \
 && touch /home/seluser/cron.log

# Declare the main container command.  (Actually run the cron daemon;
# don't make the container process be tail(1) with cron being an
# unmonitored side effect.)
CMD ["cron", "-f"]
David Maze
  • 130,717
  • 29
  • 175
  • 215