Referencing the first argument passed to the docker entrypoint? - bash

I'm trying to obtain the value of the first argument I pass to the Docker Entrypoint. I received an answer earlier on how to do this. Here is the link:
Referencing a dynamic argument in the Docker Entrypoint
So I setup an experiment to see if this works:
Here's my Dockerfile:
FROM alpine:3.3
MAINTAINER ole.ersoy#gmail.com
RUN apk add --update --no-cache --no-progress bash
COPY run.sh .
ENTRYPOINT /run.sh
And the run.sh entrypoint:
#!/bin/sh
echo The first argument is: $1
I then build this:
docker build -t test .
And run the image:
ole#MKI:~/docker-test$ docker run test one
The first argument is:
I was expecting:
ole#MKI:~/docker-test$ docker run test one
The first argument is: one
Thoughts?
TIA,
Ole

Change ENTRYPOINT to next:
ENTRYPOINT ["bash", "run.sh"]
It works for me. Read more about entrypoint args here https://docs.docker.com/engine/reference/builder/#entrypoint

Related

Pass a bash script command-line option from a Dockerfile

I have a bash script that is run as part of a Dockerfile build process.
my_script.sh
poetry install
Dockerfile
RUN ./my_script.sh
What I would like to do is optionally invoke poetry install -vvv when I build the Docker image.
I tried something like:
Dockerfile
ARG POETRY_OPTION="-vvv"
RUN ./my_script.sh ${POETRY_OPTION}
and my_script.sh
poetry install $1
But that isn't being honored. It runs as if that option is not there.
It seems to me that maybe that works for regular arguments, but won't work for "options".
You can have the ARG inside of the Dockerfile as ARG POETRY_OPTION. While running the docker build command use docker build --build-arg POETRY_OPTION="vvv" .

Command Not Found with Dockerfile CMD

I have a Dockerfile that uses
CMD ['/usr/local/bin/gunicorn', '-b 0.0.0.0:8000', 'myapp.wsgi']
But when I run the container using docker run --rm myimage:latest I get an error:
/bin/sh: 1: [/usr/local/bin/gunicorn,: not found
Yet, when I run docker run --rm -it myimage:latest /bin/bash to go into the container, I can see that gunicorn runs, and running which gunicorn returns the correct path for gunicorn. Why is it failing to run?
Similarly, I planned on adding
ENTRYPOINT ['/entrypoint.sh']
to my Dockerfile, but when I run that, I get the error
/bin/sh: 1: /bin/sh: [/entrypoint.sh]: not found
The entrypoint.sh file contains:
#! /bin/bash
echo 'Starting app...'
cd /app || exit;
python manage.py migrate;
So why does it keep saying command not found when all the commands are there?
The issue here is the quotes. Use double " quotes.
From Docker Documentation:
The exec form is parsed as a JSON array, which means that you must use
double-quotes (“) around words not single-quotes (‘).
This is applicable for other instructions such as RUN, LABEL, ENV, ENTRYPOINT and VOLUME.

/bin/sh: No such file or directory when setting a docker-compose entrypoint

I have a container that runs a database migration (source):
FROM golang:1.12-alpine3.10 AS downloader
ARG VERSION
RUN apk add --no-cache git gcc musl-dev
WORKDIR /go/src/github.com/golang-migrate/migrate
COPY . ./
ENV GO111MODULE=on
ENV DATABASES="postgres mysql redshift cassandra spanner cockroachdb clickhouse mongodb sqlserver firebird"
ENV SOURCES="file go_bindata github github_ee aws_s3 google_cloud_storage godoc_vfs gitlab"
RUN go build -a -o build/migrate.linux-386 -ldflags="-s -w -X main.Version=${VERSION}" -tags "$DATABASES $SOURCES" ./cmd/migrate
FROM alpine:3.10
RUN apk add --no-cache ca-certificates
COPY --from=downloader /go/src/github.com/golang-migrate/migrate/build/migrate.linux-386 /migrate
ENTRYPOINT ["/migrate"]
CMD ["--help"]
I want to integrate it into a docker-compose and make it dependent on the Postgres database service. However, since I have to wait until the database is fully initialised I have to wrap the migrate command in a script and thus replace the entrypoint of the migration container. I'm using the wait-for script to poll the database, which is a pure shell (not bash) script and should thus work in an alpine container.
This is how the service is defined in the docker-compose:
services:
database:
# ...
migration:
depends_on:
- database
image: migrate/migrate:v4.7.0
volumes:
- ./scripts/migrations:/migrations
- ./scripts/wait-for:/wait-for
entrypoint: ["/bin/sh"]
command: ["./wait-for database:5432", "--", "./migrate", "-path", "/migrations", "-database", "postgres://test:test#database:5432/test?sslmode=disable", "-verbose", "up"]
Running docker-compose up on this fails with
migration_1 | /bin/sh: can't open './wait-for database:5432': No such file or directory
Running the migrate container for itself with
docker run -it --entrypoint /bin/sh -v $(pwd)/scripts/wait-for:/wait-for migrate/migrate:v4.7.0
does work flawlessly, the script is there and can be run with /bin/sh ./wait-for.
So why does it fail as part of the docker-compose?
If you read the error message carefully, you will see that the file that cannot be found is not ./waitfor, it is ./wait-for database:5432. This is consistent with your input file, where that whole thing is given as the first element of the command list:
command: ["./wait-for database:5432", "--", "./migrate", "-path", "/migrations", "-database", "postgres://test:test#database:5432/test?sslmode=disable", "-verbose", "up"]
It's unclear to me what you actually want instead, since the working alternatives presented do not seem to be fully analogous, but possibly it's
command: ["./wait-for", "database:5432", "--", "./migrate", "-path", "/migrations", "-database", "postgres://test:test#database:5432/test?sslmode=disable", "-verbose", "up"]
Running the migrate container for itself with does work flawlessly
When you run it like:
docker run -it --entrypoint /bin/sh -v $(pwd)/scripts/wait-for:/wait-for migrate/migrate:v4.7.0
entrypoint /bin/sh is executed.
When you run it using docker-compose:
entrypoint (/bin/sh ) + command (./wait-for database:5432) ...` is executed.
./wait-for database:5432 as whole stands for executable that will run and it can't be found, that's why you get the error No such file or directory
Try to specify an absolute path to wait-for in command: and split ./wait-for database:5432 into "./wait-for", "database:5432".
It's possible that splitting will be enough
As an alternative you can follow CMD syntax docs and use different command syntax without array: command: ./wait-for database:5432 ...
ENTRYPOINT ["/bin/sh"] is not enough, you also need the -c argument.
Example (testing a docker-compose.yml with docker-compose run --rm MYSERVICENAMEFROMTHEDOCKERCOMPOSEFILE bash here):
entrypoint: ["/bin/sh"]
Throws:
/bin/sh: 0: cannot open bash: No such file
ERROR: 2
And some wrong syntax examples like
entrypoint: ["/bin/sh -c"]
(wrong!)
or
entrypoint: ["/bin/sh, -c"]
(wrong!)
throw errors:
starting container process caused: exec: "/bin/sh, -c": stat /bin/sh, -c: no such file or directory: unknown
ERROR: 1
starting container process caused: exec: "/bin/sh -c": stat /bin/sh -c: no such file or directory: unknown
ERROR: 1
In docker-compose or Dockerfile, for an entrypoint, you need the -c argument.
This is right:
entrypoint: "/bin/sh -c"
or:
entrypoint: ["/bin/sh", "-c"]
The -c is to make clear that this is a command executed in the command line, waiting for an additional command to be used in that command line. but not starting the bash /bin/sh just on its own. You can read that between the lines at What is the difference between CMD and ENTRYPOINT in a Dockerfile?.

Using variable interpolation in string in Docker

I am having trouble creating and using variables in a Dockerfile - I build a Docker image via a Dockerfile with this command:
$ docker build --build-arg s=scripts/a.sh -t a .
(So because I use --build-arg, $s will be an available argument in the Dockerfile, and this part works)
The Dockerfile is like so:
ARG s
RUN echo $s
RUN useradd -ms /bin/bash newuser
USER newuser
WORKDIR /home/newuser
ENV fn=$(filename $s) # fails on this line
COPY $s .
ENTRYPOINT ["/bin/bash", "/home/newuser/$fn"]
The problem I have is that the Docker build is failing on the line indicated above.
Error response from daemon: Syntax error - can't find = in "$s)". Must be of the form: name=value
If I change that line to this:
RUN fn=$(filename $s)
I get this error:
Error: Command failed: docker build --build-arg s=scripts/a.sh -t a .
The command '/bin/sh -c fn=$(filename $s)' returned a non-zero code: 127
Anyone know the correct way to
Create a variable inside the docker file
Use string interpolation with that variable so that I can reference the variable in the ENTRYPOINT arguments like so:
ENTRYPOINT ["/bin/bash", "/home/newuser/$var"]
Even if I do this:
ARG s
ARG sname
RUN echo $s # works as expected
RUN echo $sname # works as expected
RUN useradd -ms /bin/bash newuser
USER newuser
WORKDIR /home/newuser
COPY $s . # works as expected (I believe)
ENTRYPOINT /bin/bash /home/newuser/$sname # does not work as expected
even though I am using the "non-JSON" version of ENTRYPOINT, it still doesn't seem to pick up the value for the $sname variable.
I would avoid using variable in ENTRYPOINT at all. It's tricky and requires a deep understanding of what is going on. And is easy to break it by accident. Just consider one of the following.
Create link with the known name to your start script.
RUN ln -s /home/newuser/$sname /home/newuser/docker_entrypoint.sh
ENTRYPOINT ["/home/newuser/docker_entrypoint.sh"]
or write standalone entrypoint script that runs what you need.
But if you want to know how and why solutions in your questions work just keep reading.
First some definitions.
ENV - is environment variable available during buildtime (docker build) and runtime (docker run)
ARG - is environment variable available only during buildtime
If you look at https://docs.docker.com/engine/reference/builder/#environment-replacement you see the list of dockerfile instructions that support those environment variables directly. This is why COPY "picks up the variable" as you said.
Please note that there is no RUN nor ENTRYPOINT. How does it work?
You need to dig into the documentation. First RUN (https://docs.docker.com/engine/reference/builder/#run). There are 2 forms. The first one executes command through the shell and this shell has access to buildtime environment variables.
# this works because it is called as /bin/sh -c 'echo $sname'
# the /bin/sh replace $sname for you
RUN echo $sname
# this does NOT work. There is no shell process to do $sname replacement
# for you
RUN ["echo", "$sname"]
Same thing applies to the ENTRYPOINT and CMD except only runtime variables are available during container start.
# first you need to make some runtime variable from builtime one
ENV sname $sname
# Then you can use it during container runtime
# docker runs `/bin/sh -c '/bin/bash /home/newuser/$sname'` for you
# and this `/bin/sh` proces interprets `$sname`
ENTRYPOINT /bin/bash /home/newuser/$sname
# but this does NOT work. There is no process to interpolate `$sname`
# docker runs what you describe.
ENTRYPOINT ["/bin/bash", "/home/newuser/$sname"]
edit 2017-04-03: updated links to the docker documentations and slight rewording to avoid confusion that I sense from other answers and comments.
I requested #Villem to answer, and his answer is much more definitive, but the following will work (just is not a stable solution). His answer is basically saying that this answer is not a good way to do it:
ARG s # passed in via --build-arg s=foo
ARG sname # passed in via --build-arg sname=bar
RUN echo $s
RUN echo $sname
ENV sname $sname # this is the key part
RUN useradd -ms /bin/bash newuser
USER newuser
WORKDIR /home/newuser
COPY $s .
ENTRYPOINT /bin/bash /home/newuser/$sname # works, but is not stable!
don't ask me why the COPY command picks up the variable that was declared via ARG, but that the ENTRYPOINT command does not seem to pick up the variable declared via ARG, but only picks up the variable declared via ENV. At least, this appears to be the case.
I spent a lot of time to find out that.
Don't works !
ARG install="bundle install --jobs=4"
FROM ruby:2.6.3-alpine
RUN eval $install
But this works...
FROM ruby:2.6.3-alpine
ARG install="bundle install --jobs=4"
RUN eval $install
Then to build the docker image:
docker build -t server --no-cache --build-arg install="bundle install --without development test" .`
I wanted both variable substitution and arguments passing.
Let's say our Dockerfile has:
ENV VARIABLE=replaced
And we want to run this:
docker run <image> arg1 arg2
I obviously wanted this output:
replaced arg1 arg2
I eventually found this one:
ENTRYPOINT [ "sh", "-c", "echo $VARIABLE $0 $#" ]
It works!!!
But I feel SOOOO dirty!
Obviously, in real life, I wanted to do something more useful:
docker run <image> --app.options.additional.second=true --app.options.additional.third=false
ENTRYPOINT [ "sh", "-c", "java -Xmx$XMX $0 $#", \
"-jar", \
"/my.jar", \
"--app.options.first=true" ]
Why a so complicated answer?
"ENTRYPOINT java..." would not pass docker arguments to the entrypoint => "ENTRYPOINT [..." is mandatory for that
"ENTRYPOINT [..." will NOT call the shell, so no variable substitution is done at all => "sh -c" is mandatory for that
"sh -c" only take the FIRST argument passed to it and split it in command+arguments => so everything must be in the first argument of "sh -c" for variables to be visible by the command
Docker arguments are passed as extra array entries of "ENTRYPOINT [..." => so the "$#" is necessary to "copy" the remainings arguments of the "sh -c" into the "echo ..." command to be executed (and as a bonus, we can reuse the additional array entries of ENTRYPOINT[] to place forced arguments in a readable way in the Dockerfile)
"$#" removes the first argument => so explicit "$0" must be used before "$#"
Fiou...
I added a comment to this issue, for Docker developers to see what we are forced to do and perhaps change their mind to make ENTRYPOINT[] replace environment variables: https://github.com/moby/moby/issues/4783#issuecomment-442466609

docker-compose not found from script

I have a Dockerfile which is basically the following:
FROM mhart/alpine-node:5
COPY . /project
WORKDIR /project
ENTRYPOINT ["./startup.sh"]
And my startup.sh is quite simple too:
#!/bin/sh
set -e
docker-compose up -d
I do have a docker-compose.yml, but there is no point to describe it here.
First thing I do is to build the docker image by using my Dockerfile, so:
docker build -t test .
Then run this image:
docker run -d test
Which will launch the startup.sh
Unfortunately, I have the following error showing up:
./startup.sh: line 10: docker-compose: not found
And if I do only ./startup.sh without the docker stuff, it works like a charm.
Where the issue can be possibly coming from?
Try to add the full path to the docker-compose inside the script
which docker-compose
>/usr/bin/some/path/docker-compose
Then add this to your script
#!/bin/sh
set -e
/usr/bin/some/path/docker-compose up -d
Your local PATH settings are unknown to the script called by docker. Therefore you have to name the full path.

Resources