Behavior of parameter pass-through using ENTRYPOINT and CMD unclear - dockerfile

(update from 14th Nov 14:25:
This question can be closed as it faces multiple issues and should not be confused with each other.
Later on I will come back and add the reference to the more specific question.
Inspired by https://stackoverflow.com/a/39408777 I made an table of possible cases and was able to find identify 2 isolated cases:
+------------------+-------------------------------------------+------------------------+---------------------------------------------------+
| | No Entrypoint | Entrypoint (JSON-form) | Entrypoint (shell-form) |
+------------------+-------------------------------------------+------------------------+---------------------------------------------------+
| No CMD | HostConfig.Config.cmd=/bin/bash is called | breaks | ok |
| | (assumption as of docker inspect) | (See Case 1) | |
+------------------+-------------------------------------------+------------------------+---------------------------------------------------+
| CMD (JSON-form) | breaks(See Case 1) | breaks | breaks |
| | | (See Case 1) | (See Case 2) |
+------------------+-------------------------------------------+------------------------+---------------------------------------------------+
| CMD (shell-form) | ok | ok | Breaks [seems to work as designed] |
| | | | (both are called with a shell concatinated) |
| | | | Example: /bin/sh -c <ENTRYPOINT> /bin/sh -c <CMD> |
+------------------+-------------------------------------------+------------------------+---------------------------------------------------+
Case 1 – escaping issue of double quotes for the exec form.
Possible workarounds:
Create a separate shell script and handle all the logic within this script.
Case 2 - Parameter of CMD in exec form get lost if used with Entrypoint
(
used wording:
exec form using json array -> CMD["coommand", "parameter"]
shell form -> CMD command parameter
see: https://docs.docker.com/engine/reference/builder/#cmd
)
(update from 14th Nov 12:16:
I tried to reproduce this behavior with ubuntu:latest and faced a different behavior with bash and dash. After some trial and error I can say that it works in the shellform for both - ENTRYPOINT and CMD.
I will investigate further what happens when I make use of the jsonform. The escape directive didn't changed the behavior)
my question is not focusing on the decision making when to use ENTRYPOINT or CMD but more how it behaves when it comes to the pass through of the parameter to the container. But t.b.h. I'm not sure if this is a docker or alpine environment / shell related question -> tagged both.
Expected behavior
Use ENTRYPOINT to set stable default command and arguments and use CMD to set overwritable parameter.
Those parameter are then passed through correctly to the container.
Actual behavior
The called executable behaves diffently in three different scenarios. Each three cases will be described below.
Case 1:
Parameter are passed through as expected. But I wasn't able to achive it with ENTRYPOINT. Only CMD.
Case 2:
Combination of ENTRYPOINT and CMD. Two chars "/\" with unknown source appear.
Case 3:
Combination of ENTRYPOINT (shell form) and CMD. Parameter from CMD seem to vanish.
Obervations to fix Case 3 with an interactive shell (docker exec -it --entrypoint="/bin/ash"):
escape all parameter with a single quote from
/bin/sh -c /usr/bin/run.sh server --watch=true --bind=0.0.0.0 --source="/src" --destination="/output"
to
/bin/sh -c '/usr/bin/run.sh server --watch=true --bind=0.0.0.0 --source="/src" --destination="/output"'
But why does it work in Case 1 with CMD only? And the SHLVL is set to the value '2'.
What I tried so far
diff the docker inspect output of relevant container -> nothing suspicious for me (can be posted if required)
wrote a simple wrapper script to print environment variables, ps faux and parameter passed through. (output below)
Read online articles (e.g. Dockerfile reference and https://stackoverflow.com/a/39408777 (possible combinations of ENTRYPOINT and CMD))
Source of referenced run.sh -> link:
Command used to build an run the container:
"sudo docker build -t docker-hugo . && sudo docker run --rm docker-hugo"
Dockerfile CASE 1 - Works but without ENTRYPOINT (what I like to achieve):
FROM alpine:latest
RUN apk --no-cache add hugo
CMD hugo server --watch=true --bind=0.0.0.0 --source="/src" --destination="/output"
Relevant output:
Error: Unable to locate Config file. Perhaps you need to create a new site.
Run `hugo help new` for details. (Config File "config" Not Found in "[/src]") <-- "/src" as provieded via CMD
Command within Container (achived via "ps faux" and docker exec -it --entrypoint="/bin/ash")
PID USER TIME COMMAND
1 root 0:00 /bin/sh -c hugo server --watch=true --bind=0.0.0.0 --source="/src" --destination="/output"
7 root 0:00 hugo server --watch=true --bind=0.0.0.0 --source=/src --destination=/output
28 root 0:00 ash
34 root 0:00 ps faux
Dockerfile CASE 2 - Don't understand where the "/\" from the stdout is coming from
FROM alpine:latest
RUN apk --no-cache add hugo
#ENTRYPOINT ["/usr/bin/run.sh"]
ENTRYPOINT ["hugo"]
CMD ["server", "--watch=true", "--bind=0.0.0.0", "--source=\"/src\"", "--destination=\"/output\""]
Relevant output:
Error: Unable to locate Config file. Perhaps you need to create a new site.
Run `hugo help new` for details. (Config File "config" Not Found in "[/\"/src\"]") <-- where is the "/\" comming from?)
Command and environment from container (stdout from run.sh)
HOME='/root'
HOSTNAME='805e3dd6110a'
IFS='
'
OPTIND='1'
PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
PPID='0'
PS1='\w \$ '
PS2='> '
PS4='+ '
PWD='/'
SHLVL='1'
PID USER TIME COMMAND
1 root 0:00 ash /usr/bin/run.sh server --watch=true --bind=0.0.0.0 --source="/src" --destination="/output"
7 root 0:00 ps faux
server --watch=true --bind=0.0.0.0 --source="/src" --destination="/output"
Dockerfile CASE 3 - Not OK - CMD Parameter seem to "vanish"
FROM alpine:latest
RUN apk --no-cache add hugo
#ENTRYPOINT ["/usr/bin/run.sh"]
ENTRYPOINT /usr/bin/run.sh
CMD ["server", "--watch=true", "--bind=0.0.0.0", "--source=\"/src\"", "--destination=\"/output\""]
Relevant output:
Error: Unable to locate Config file. Perhaps you need to create a new site.
Run `hugo help new` for details. (Config File "config" Not Found in "[/]") <-- Parameter had no effect
Command and environment from container (stdout from run.sh)
HOME='/root'
HOSTNAME='cd6df15c5028'
IFS='
'
OPTIND='1'
PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
PPID='1'
PS1='\w \$ '
PS2='> '
PS4='+ '
PWD='/'
SHLVL='2'
PID USER TIME COMMAND
1 root 0:00 /bin/sh -c /usr/bin/run.sh server --watch=true --bind=0.0.0.0 --source="/src" --destination="/output"
7 root 0:00 ash /usr/bin/run.sh
8 root 0:00 ps faux

Related

How can I correct this Dockerfile to take arguments properly?

I have this dockerfile content:
FROM python:latest
ARG b=8
ARG r=False
ARG p=1
ENV b=${b}
ENV r=${r}
ENV p=${p}
# many lines of successful setup
ENTRYPOINT python analyze_all.py /analysispath -b $b -p $p -r $r
My intention was to take three arguments at the command line like so:
docker run -it -v c:\analyze containername -e b=16 -e p=22 -e r=False
But unfortunately, I'm misunderstanding something fundamental and simple here instead of something complicated, so I'm helpless :).
If I understood the question correctly, this Dockerfile should do what is required:
FROM python:latest
# test script that prints argv
COPY analyze_all.py /
ENTRYPOINT ["python", "analyze_all.py", "/analysispath"]
Launch:
$ docker run -it test:latest -b 16 -p 22 -r False
sys.argv=['analyze_all.py', '/analysispath', '-b', '16', '-p', '22', '-r', 'False']
Looks like your Dockerfile is designed to build and run a container on Windows. I tested my Dockerfile on Linux, it probably won't be much different to use this approach on Windows.
I think ARG instructions isn't needed in this case because it defines a variable that users can pass at build-time using the docker build command. I would also suggest that you take a look at the Dockerfile reference for the ENTRYPOINT instruction:
Command line arguments to docker run will be appended after all elements in an exec form ENTRYPOINT, and will override all elements specified using CMD. This allows arguments to be passed to the entry point, i.e., docker run -d will pass the -d argument to the entry point.
Also, this question will probably be useful for you: How do I use Docker environment variable in ENTRYPOINT array?

Missing output or input when running c++ binary in docker

Building a cpp binary inside a docker builder with cmake
FROM ubuntu:focal as builder
WORKDIR /var/lib/project
RUN cmake ... && make ...
Then copying the built binary to final image which also is ubuntu:focal, to a WORKDIR.
WORKDIR /var/lib/project
COPY --from=builder /usr/src/project/bin/binary ./binary
EXPOSE 8080
CMD ["./binary"]
Running the docker with docker run hangs the docker (even with -d), no input and output. To stop the docker, I have to kill it from another terminal.
However, if I exec into that same container while it is hanging, and run the binary from the shell inside the docker, it will work as expected.
Command used to build the docker
docker build . --platform=linux/amd64 --file docker/Dockerfile --progress=plain -c 32 -t mydocker:latest
Tried:
CMD ["/bin/bash", "-c" , "./binary"]
CMD ["/bin/bash", "-c" , "exec", "./binary"]
And same configurations with ENTRYPOINT too.
Same behavior.
I'm guessing there is a problem with how the binary is built, maybe some specific flags are needed, because if I do docker run ... ls or any other built in command it will work and output to my stdout.
Expecting to have my binary std* redirected to my std* just like any other command inside docker.
There is another related question, as of now, unanswered.
Update:
Main binary is built with these
CXX_FLAGS= -fno-builtin-memcmp -fPIC -Wfatal-errors -w -msse -msse4.2 -mavx2
Update:
Main part of the binary code,
The version is apache arrow 10.0.1
int main() {
arf::Location srv_loc = arf::Location::ForGrpcTcp("127.0.01", 8080).ValueUnsafe();
arf::FlightServerOptions opt(srv_loc);
auto server = std::make_unique<MyService>();
ARROW_RETURN_NOT_OK(server->Init(opt));
std::printf("Starting on port: %i\n", server->port());
return server->Serve().ok();
}
UPDATE: The problem seems to be here:
While the container is hanging, I entered into it, and did a ps aux.
The binary gets PID=1, I don't know why this happens, but hardly this is the correct behavior.
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.2 0.0 341520 13372 ? Ssl 07:52 0:00 ./binary
root 12 0.5 0.0 4116 3412 pts/0 Ss 07:52 0:00 /bin/bash
root 20 0.0 0.0 5900 2884 pts/0 R+ 07:52 0:00 ps aux
UPDATE:
./binary is executable, chmod +x is useless.
Adding printfs to multiple places did not work, unless the last line server->Serve() is commented out, or prints are really large, then everything gets printed.
Separating return statement from server->Server() makes no difference.
UPDATE:
Running the built container with docker --init -d flag, works.
Tough is this the right way? and if so how can this be forced in dockerfile?
This problem is related to the pid of the process in the container. You can use the --init flag to avoid the problem. for more information visit this site- https://docs.docker.com/engine/reference/run/#specify-an-init-process

Use Docker-Windows for Gitlab-runner

I'm trying to use Docker in Windows to create a Gitlab-Runner to build a C++ application. It works so far, but I guess there are better aproaches. Here's what I did:
Here's my initial Docker Container:
FROM mcr.microsoft.com/windows/servercore:2004
# Restore the default Windows shell for correct batch processing.
SHELL ["cmd", "/S", "/C"]
# Download the Build Tools bootstrapper.
ADD https://aka.ms/vs/16/release/vs_buildtools.exe C:\TEMP\vs_buildtools.exe
# Install Build Tools with the Microsoft.VisualStudio.Workload.AzureBuildTools workload, excluding workloads and components with known issues.
RUN C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache `
--installPath C:\BuildTools `
--add Microsoft.VisualStudio.Workload.VCTools `
--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
--add Microsoft.VisualStudio.Component.VC.CMake.Project `
--add Microsoft.VisualStudio.Component.Windows10SDK.19041 `
--locale en-US `
|| IF "%ERRORLEVEL%"=="3010" EXIT 0
# Define the entry point for the docker container.
# This entry point starts the developer command prompt and launches the PowerShell shell.
ENTRYPOINT ["cmd","/k", "C:\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"]
And my .gitlab-ci.yml looks like this:
build Docker Windows:
image: buildtools2019_core
stage: build
tags:
- win-docker
script:
- mkdir build
- cd build
- cmake -DCMAKE_BUILD_TYPE=Release -DenableWarnings=ON -G Ninja -DCMAKE_MAKE_PROGRAM=Ninja ../
- ninja
This works so far and everthing builds correctly. The main problem however is that if the build fails the job succeeds anyways. I suspect that my entrypoint is wrong because powershell is executed inside of a cmd and only the exit code of cmd is checked which always succeeds.
So I tried to use powershell directly as entrypoint. I need to set environment variables via vcvars64.bat but that is not that trivial to do. I tried to execute the "Developer Powershell for VS 2019" but I can't execute the link in the entrypoint directly and the link looks like this:
"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -noe -c "&{Import-Module """C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"""; Enter-VsDevShell 6f66c5f6}"
Which I don't quit understand what it does and the hash also varies from installation to installation. Also simply using this as entrypoint didn't work.
I then tried to use the Invoke-Environment Script taken from "https://github.com/nightroman/PowerShelf/blob/master/Invoke-Environment.ps1". This allows me to execute the .bat file from powershell like this:
Invoke-Environment C:\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat
But to do this I need to add this function to my profile, as far as I understood. I did this by copying it to "C:\Windows\system32\WindowsPowerShell\v1.0\profile.ps1" so that it would be accessible by all users.
In my Docker file I added:
COPY Invoke-Environment.ps1 C:\Windows\system32\WindowsPowerShell\v1.0\profile.ps1
and replaced the entrypoint with:
ENTRYPOINT ["C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe", "-NoExit", "-NoLogo", "-ExecutionPolicy", "Bypass", "Invoke-Environment C:\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat"]
But that didn't initialize the environment variables correctly. Also "Invoke-Environment" is not found by the gitlab-runner. My last resort was to write a small script (Init64.ps1) that executes the Invoke-Environment function with vcvars64.bat:
function Invoke-Environment {
param
(
# Any cmd shell command, normally a configuration batch file.
[Parameter(Mandatory=$true)]
[string] $Command
)
$Command = "`"" + $Command + "`""
cmd /c "$Command > nul 2>&1 && set" | . { process {
if ($_ -match '^([^=]+)=(.*)') {
[System.Environment]::SetEnvironmentVariable($matches[1], $matches[2])
}
}}
}
Invoke-Environment C:\BuildTools\VC\Auxiliary\Build\vcvars64.bat
I copied this in docker via:
COPY Init64.ps1 Init64.ps1
and used this entrypoint:
ENTRYPOINT ["C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe"]
In my build script I need to manually call it to setup the variables:
build Docker Windows:
image: buildtools2019_core
stage: build
tags:
- win-docker
script:
- C:\Init64.ps1
- mkdir build
- cd build
- cmake -DCMAKE_BUILD_TYPE=Release -DenableWarnings=ON -G Ninja -DCMAKE_MAKE_PROGRAM=Ninja ../
- ninja
Now everything works as intended the build works and the job only succeeds if the build succeeds.
However, I would prefer to setup my environment in the entrypoint so that I don't have to do this in my build script.
Is there a better way to do this? Also feel free to suggest any improvements I could make.
Ok, after some struggling, here is my entry.bat that correctly loads the environment exports the error-level/return-value:
REM Load environment
call C:\\BuildTools\\VC\\Auxiliary\\Build\\vcvars64.bat
REM If there are no parameters call cmd
IF [%1] == [] GOTO NOCALL
REM If there are parameters call cmd with /S for it to exit directly
cmd /S /C "%*"
exit %errorlevel%
:NOCALL
cmd
exit %errorlevel%

Using regex with run-parts on Alpine 3.9

I'm creating a Docker image FROM alpine:3.9.2 and I need to run run-parts. I used the script below in the past on ubuntu:16.04 without problems.
run-parts --verbose --regex '\.sh$' "$DIR"
However, this time around, I get errors on the options I pass to it. I.e.
run-parts: unrecognized option: verbose
run-parts: unrecognized option: regex
From my understading Alpine 3.9.2 uses run-parts 4.8.6 https://pkgs.alpinelinux.org/package/edge/main/x86/run-parts which should come from the debianutils https://manpages.debian.org/testing/debianutils/run-parts.8.en.html and supports both verbose and regex.
Am I missing anything here?
how can I run all the files ending with .sh on Alpine 3.9.2?
There is very cut version of run-parts in alpine image by default. It is busybox one:
/ # which run-parts
/bin/run-parts
/ # run-parts --help
BusyBox v1.29.3 (2019-01-24 07:45:07 UTC) multi-call binary.
Usage: run-parts [-a ARG]... [-u UMASK] [--reverse] [--test] [--exit-on-error] DIRECTORY
Run a bunch of scripts in DIRECTORY
-a ARG Pass ARG as argument to scripts
-u UMASK Set UMASK before running scripts
--reverse Reverse execution order
--test Dry run
--exit-on-error Exit if a script exits with non-zero
It can only run a bunch of scripts in directory.
If you want to use uncut run-parts from the debianutils package, you need to install it first to alpine image:
/ # apk add --no-cache run-parts
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
(1/1) Installing run-parts (4.8.6-r0)
Executing busybox-1.29.3-r10.trigger
OK: 6 MiB in 15 packages
Now, there is a full version of run-parts in alpine instance:
/ # which run-parts
/usr/bin/run-parts
/ # run-parts --help
Usage: run-parts [OPTION]... DIRECTORY
--test print script names which would run, but don't run them.
--list print names of all valid files (can not be used with
--test)
-v, --verbose print script names before running them.
--report print script names if they produce output.
--reverse reverse execution order of scripts.
--exit-on-error exit as soon as a script returns with a non-zero exit
code.
--lsbsysinit validate filenames based on LSB sysinit specs.
--new-session run each script in a separate process session
--regex=PATTERN validate filenames based on POSIX ERE pattern PATTERN.
-u, --umask=UMASK sets umask to UMASK (octal), default is 022.
-a, --arg=ARGUMENT pass ARGUMENT to scripts, use once for each argument.
-V, --version output version information and exit.
-h, --help display this help and exit.

Git alias command with bash script egrep produces different results

Here's an example of pretty basic shell alias in git
[alias]
em = "!git branch --merged | egrep -v '\\*\\|master' | xargs -L 1 -n 1 echo"
It echos the branches that are already merged exluding the master branch (and the current branch you're on). Pretty simple. However the results are different when running this alias and just the script. Example:
will ~/example master
$ git branch --merged | egrep -v '\\*\\|master' | xargs -L 1 -n 1 echo
feature
will ~/example master
$ git em
feature
*
master
For some reason it's like egrep clause isn't even run. In fact (since I'm OS X) it's almost like it ran the grep command instead! Like this: (note the grep vs egrep)
will ~/example master
$ git branch --merged | grep -v '\\*\\|master' | xargs -L 1 -n 1 echo
feature
*
master
Has anybody come across this before?
I've read up on these SO questions and they aren't dealing with the same thing I am. The top one is the closest, but has to do with from where shell command alias are run (the toplevel of the repo).
Why does running command as git alias gives different results?
Bash script produces different result when executed from shell prompt than executed by cron
It appears that the alias definition undergoes some amount of shell processing (or at least backslash processing) before it is defined. Add another set of escaped backslashes to the alias.
[alias]
em = "!git branch --merged | egrep -v '\\\\*\\\\|master' | xargs -L 1 -n 1 echo"