Iterate in RUN command in Dockerfile - docker

I have a line that looks something like:
RUN for i in `x y z`; do echo "$i"; done
...with the intention of printing each of the three items
But it raises /bin/sh: 1: x: not found
Any idea what I'm doing wrong?

It looks like you're using backticks. What's in backticks gets executed and the text in the backticks gets replaced by what's returned by the results.
Try using single quotes or double quotes instead of backticks.
Try getting rid of the backticks like so:
RUN for i in x y z; do echo "$i"; done

I would suggest an alternative solution of this.
In stead of having the LOOP inside docker file, can we take a step back ...
Implement the loop inside a independent bash script;
Which means you would have a loop.sh as following:
#!/bin/bash
for i in $(seq 1 5); do echo "$i"; done
And in your Dockerfile, you will need to do:
COPY loop.sh loop.sh
RUN ./loop.sh
With the approach aboved it requires one extra step and costs one extra layer.
However, when you are going to do some more complicated stuff, I would recommend to put them all into the script.
All the operations inside the script will only cost one layer.
To me this approach is could be cleaner and more maintainable.
Please also have a read at this one:
https://hackernoon.com/tips-to-reduce-docker-image-sizes-876095da3b34

For a more maintainable Dockerfile, my preference is to use multiple lines with comments in RUN instructions especially if chaining multiple operations with &&
RUN sh -x \
#
# execute a for loop
#
&& for i in x \
y \
z; \
do \
echo "$i"
done \
\
#
# and tell builder to have a great day
#
&& echo "Have a great day!"

Run Docker container perpetually by writing a simple script
docker run -d <container id> \
sh -c "while :; do echo 'just looping here... nothing special'; sleep 1; done"

We can do it like
RUN for i in x \y \z; do echo "$i" "hi"; done
output of above command will be
x hi
y hi
z hi
Mind spaces while writing - for i in x \y \z;
example - snippet from git bash

Related

Difference between RUN bash -c and open form of RUN

Some Dockerfiles have
RUN bash -c "apt-get update -qq && ... \"
while others write without quotes like
RUN apt-get update -qq && ... \
What is the difference between these variants? Is one of them more preferable over another one?
You should just write RUN apt-get update ... without manually inserting a sh -c wrapper.
The RUN, CMD, and ENTRYPOINT directives all share the same syntax. It's best documented for ENTRYPOINT but all three commands work the same way. There are two ways to write commands for them: you can either provide a specific set of command words as a JSON array (exec form), or you can write a string and Docker will automatically wrap it sh -c (shell form). For example:
# Create a directory with a space in its name
RUN mkdir "a directory"
# JSON-array form: each array element is a shell word
RUN ["ls", "-ld", "a directory"]
# String form: Docker provides a shell, so these two are equivalent
RUN ls -ld 'a directory'
RUN ["/bin/sh", "-c", "ls -ld 'a directory'"]
This makes your first form redundant: if you RUN bash -c '...', it's a string, and Docker automatically wraps it in sh -c. So you get in effect
# RUN bash -c '...'
RUN ["/bin/sh", "-c", "bash -c '...'"]
GNU bash has a number of extensions that are not POSIX-standard syntax and it's possible to run into trouble with these, particularly on an Alpine-based image where /bin/sh is a minimal shell from the BusyBox toolset. I could see this as an attempt to force a shell command to run using bash rather than the default shell. For most things that appear in Dockerfiles, they won't usually be so complex that they can't be easily rewritten in standard syntax.
# Needs bash for the non-standard `source` syntax
RUN bash -c 'source ./venv/bin/activate && pip list'
# But you can use the standard `.` instead
RUN . ./venv/bin/activate && pip list
If you must have bash interpreting RUN lines then I'd suggest using the SHELL directive to change the command that's used to interpret bare strings.
Style-wise, I also occasionally see JSON-array syntax that begins with an explicit CMD ["/bin/sh", "-c", "..."]. There's no reason to write this out; it's shorter and no less clear to use the string form.

how to get the timestamps of a command execution in dockerfile

This is normal way of doing in shell
starttime=$(date '+%d/%m/%Y %H:%M:%S')
#echo $starttime
# sleep for 5 seconds
sleep 5
# end time
endtime=$(date '+%d/%m/%Y %H:%M:%S')
#echo $endtime
STARTTIME=$(date -d "${starttime}" +%s)
ENDTIME=$(date -d "${endtime}" +%s)
RUNTIME=$((ENDTIME-STARTTIME))
echo "Seconds ${RUNTIME} in sec"
Wanted the same way in a docker file
Wanted to get the timestamps before and after execution of a command in dockerfile
Could some please help on this.
It is exactly the same. A RUN command runs an ordinary Bourne shell command line (wrapping it in sh -c). If you have this much scripting involved you might consider writing it into a shell script, COPYing the script into your image, then RUNning it.
If this is just for temporary diagnostics, and you don't need to calculate the time in seconds, you can just run date as is without the rest of the scripting.
RUN date; make; date # except this won't actually stop on failure
If you were especially motivated you could take the script from the question, make it take a command as an argument, and write a script around it
#!/bin/sh
starttime=$(date '+%d/%m/%Y %H:%M:%S')
sh -c "$#"
rc=$?
endtime=$(date '+%d/%m/%Y %H:%M:%S')
...
exit "$rc"
Then in your Dockerfile you can use the SHELL directive to make this run RUN commands. You will rarely see RUN commands using JSON arrays, and this will bypass your script.
# must be executable and have a correct #!/bin/sh line
COPY timeit.sh /usr/local/bin
SHELL ["/usr/local/bin/timeit.sh"]
RUN make
RUN ["/bin/echo", "this will not be timed"]

Run execlineb when container start failed. Docker for windosw

I'm trying to run simple script inside docker container after start. Initialy previous developer decided to use s6 inside.
#!/usr/bin/execlineb -P
foreground { sleep 2 }
nginx
When i'm trying to start i'm gettings this message
execlineb: usage: execlineb [ -p | -P | -S nmin | -s nmin ] [ -q | -w | -W ] [ -c commandline ] script args
Looks like something wrong with executing this scripts or with execline.
I'm using docker for windows under windows10, however if somebody else trying to build this container in ubuntu(or any othe linux) evething is ok.
Can anybody help with this kind of problem?
DockerImage: simple alpine
According to our research of this "HUGE" problem we found two ways to solve it. Definitely it's a problem with special symbols, like '\r'
Option 1 dostounix:
install dostounix in your container(in docker file)
RUN apk --no-cache add \
dos2unix \
run it againts your sh script.
RUN for file in {PathToYourFiles}; do \
dos2unix $file; \
chmod a+xwr $file; \
done
enjoy your scripts.
Option 2 VsCode(or any textEditor):
Change CRLF 'End Of Line Sequence' to LF
VS Code bottom panel
Line endings options
enjoy your scripts.

Access files written in docker volumes from the host

I have a docker container writing logfiles to a name volume.
From the host I want to analyce the logfiles and search for given log messages. But when I access the folder which 'docker inspect VOLUMNAME' gives, I get strange behavior, which I do not understand.
e.g. following command does give empty lines as output:
user#docker-host-01:~/docker-server-env/otaya-designdb$ sudo bash -c "for logfile in /var/lib/docker/volumes/design-db-logs/_data/*/*; do echo ${logfile}; done"
user#docker-host-01:~/docker-server-env/otaya-designdb$
What could be the reason?
Your local shell is expanding the variable expansion inside the double quotes before the loop happens. Change the double quotes to single quotes.
That is, when you run
sudo bash -c "for ... ; do echo ${logfile}; done"
first your local shell replaces the variable reference with whatever your local environment has set for $logfile, probably nothing
sudo bash -c 'for ...; do echo ; done'
and then it runs that command. If you change this to single quotes initially
sudo bash -c 'for ... ; do echo ${logfile}; done'
it will avoid this expansion.
You can see this just by putting the word echo at the front of the command: the shell will do its expansion, and then echo will print out the command that would have run.

Why is capistrano interpreting a flag passed with a command to `run` as input?

I'm trying to do this:
run "echo -n 'foo' > bar.txt"
and the contents of bar.txt ends up being:
-n foo \n
(With \n representing an actual newline)
I use run for other commands like rm -rf and, to my knowledge, it works fine.
I just found this in man echo:
Some shells may provide a builtin echo command which is similar or identical to this utility. Most notably, the builtin echo in sh(1) does not accept the -n option. Consult the builtin(1) manual page.
My version of bash has an echo builtin but seems to be respecting the -n flag. It looks like the shell on your deployment machine doesn't, in which case using the full path to the echo binary might do what you want here:
run "/bin/echo -n 'foo' > bar.txt"
It appears as though the -n flag isn't being interpreted as a flag by the shell. If, from the command line, one executes echo -Y hi, the output will be -Y hi.

Resources