Reputation: 11592
I have a Dockerfile that I am putting together to install a vanilla python environment (into which I will be installing an app, but at a later date).
FROM ubuntu:12.04
# required to build certain python libraries
RUN apt-get install python-dev -y
# install pip - canonical installation instructions from pip-installer.org
# http://www.pip-installer.org/en/latest/installing.html
ADD https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py /tmp/ez_setup.py
ADD https://raw.github.com/pypa/pip/master/contrib/get-pip.py /tmp/get-pip.py
RUN python /tmp/ez_setup.py
RUN python /tmp/get-pip.py
RUN pip install --upgrade pip
# install and configure virtualenv
RUN pip install virtualenv
RUN pip install virtualenvwrapper
ENV WORKON_HOME ~/.virtualenvs
RUN mkdir -p $WORKON_HOME
RUN source /usr/local/bin/virtualenvwrapper.sh
The build runs ok until the last line, where I get the following exception:
[previous steps 1-9 removed for clarity]
...
Successfully installed virtualenvwrapper virtualenv-clone stevedore
Cleaning up...
---> 1fc253a8f860
Step 10 : ENV WORKON_HOME ~/.virtualenvs
---> Running in 8b0145d2c80d
---> 0f91a5d96013
Step 11 : RUN mkdir -p $WORKON_HOME
---> Running in 9d2552712ddf
---> 3a87364c7b45
Step 12 : RUN source /usr/local/bin/virtualenvwrapper.sh
---> Running in c13a187261ec
/bin/sh: 1: source: not found
If I ls
into that directory (just to test that the previous steps were committed) I can see that the files exist as expected:
$ docker run 3a87 ls /usr/local/bin
easy_install
easy_install-2.7
pip
pip-2.7
virtualenv
virtualenv-2.7
virtualenv-clone
virtualenvwrapper.sh
virtualenvwrapper_lazy.sh
If I try just running the source
command I get the same 'not found' error as above. If I RUN an interactive shell session however, source does work:
$ docker run 3a87 bash
source
bash: line 1: source: filename argument required
source: usage: source filename [arguments]
I can run the script from here, and then happily access workon
, mkvirtualenv
etc.
I've done some digging, and initially it looked as if the problem might lie in the difference between bash as the Ubuntu login shell, and dash as the Ubuntu system shell, dash not supporting the source
command.
However, the answer to this appears to be to use '.' instead of source
, but this just causes the Docker runtime to blow up with a go panic exception.
What is the best way to run a shell script from a Dockerfile RUN instruction to get around this (am running off the default base image for Ubuntu 12.04 LTS).
Upvotes: 480
Views: 513480
Reputation: 1
In a Dockerfile, the RUN instruction http://xivlauncher.com/ is used to execute commands during the build process of an image. However, using source (which is typically a built-in command in shell environments like Bash) directly with RUN might not work as expected because RUN executes commands in a non-interactive shell by default. Here's how you can handle this situation:
Understanding RUN and source:
RUN executes commands in a new shell session each time, and shell-specific commands like source may not persist their effects beyond the RUN command. source (or .) is used to execute commands from a file in the current shell context, typically to set environment variables or configure the shell environment. Alternative Approach:
Instead of using source, explicitly set environment variables or execute
Upvotes: -1
Reputation: 975
I had the same problem and in order to execute pip install
inside a virtualenv I had to use this command:
RUN pip install virtualenv virtualenvwrapper
RUN mkdir -p /opt/virtualenvs
ENV WORKON_HOME /opt/virtualenvs
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh \
&& mkvirtualenv myapp \
&& workon myapp \
&& pip install -r /mycode/myapp/requirements.txt"
Upvotes: 59
Reputation: 13788
The Dockerfile SHELL
command can be used to change the default shell. (SHELL was introduced in Docker 1.12)
e.g., use the bash shell:
SHELL ["/bin/bash", "-c"]
use bash and source a python virtualenv:
SHELL ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"]
The SHELL instruction allows the default shell used for the shell form of commands to be overridden. The default shell on Linux is ["/bin/sh", "-c"], and on Windows is ["cmd", "/S", "/C"]. https://docs.docker.com/engine/reference/builder/#shell
Upvotes: 66
Reputation: 41
This is my solution on "Ubuntu 20.04"
RUN apt -y update
RUN apt -y install curl
SHELL ["/bin/bash", "-c"]
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
RUN source /root/.bashrc
RUN bash -c ". /root/.nvm/nvm.sh && nvm install v16 && nvm alias default v16 && nvm use default"
Upvotes: 4
Reputation: 13248
The default shell for the RUN
instruction is ["/bin/sh", "-c"]
.
RUN "source file" # translates to: RUN /bin/sh -c "source file"
Using SHELL instruction, you can change default shell for subsequent RUN
instructions in Dockerfile:
SHELL ["/bin/bash", "-c"]
Now, default shell has changed and you don't need to explicitly define it in every RUN instruction
RUN "source file" # now translates to: RUN /bin/bash -c "source file"
Additional Note: You could also add --login
option which would start a login shell. This means ~/.bashrc
for example would be read and you don't need to source it explicitly before your command
Upvotes: 204
Reputation: 479
I had the same issue. If you also use a python base image you can change the shebang line in your shell script to #!/bin/bash
.
See for example the container_entrypoint.sh
from Manuel Lazo.
Upvotes: 0
Reputation: 862
I've dealing with a similar scenario for an application developed with Django web web framework and these are the steps that worked perfectly for me:
[mlazo@srvjenkins project_textile]$ cat docker/Dockerfile.debug
FROM malazo/project_textile_ubuntu:latest
ENV PROJECT_DIR=/proyectos/project_textile PROJECT_NAME=project_textile WRAPPER_PATH=/usr/share/virtualenvwrapper/virtualenvwrapper.sh
COPY . ${PROJECT_DIR}/
WORKDIR ${PROJECT_DIR}
RUN echo "source ${WRAPPER_PATH}" > ~/.bashrc
SHELL ["/bin/bash","-c","-l"]
RUN mkvirtualenv -p $(which python3) ${PROJECT_NAME} && \
workon ${PROJECT_NAME} && \
pip3 install -r requirements.txt
EXPOSE 8000
ENTRYPOINT ["tests/container_entrypoint.sh"]
CMD ["public/manage.py","runserver","0:8000"]
[mlazo@srvjenkins project_textile]$ cat tests/container_entrypoint.sh
#!/bin/bash
# *-* encoding : UTF-8 *-*
sh tests/deliver_env.sh
source ~/.virtualenvs/project_textile/bin/activate
exec python "$@"
[mlazo@srvjenkins project_textile]$ cat ./tests/container_deployment.sh
#!/bin/bash
CONT_NAME="cont_app_server"
IMG_NAME="malazo/project_textile_app"
[ $(docker ps -a |grep -i ${CONT_NAME} |wc -l) -gt 0 ] && docker rm -f ${CONT_NAME}
docker run --name ${CONT_NAME} -p 8000:8000 -e DEBUG=${DEBUG} -e MYSQL_USER=${MYSQL_USER} -e MYSQL_PASSWORD=${MYSQL_PASSWORD} -e MYSQL_HOST=${MYSQL_HOST} -e MYSQL_DATABASE=${MYSQL_DATABASE} -e MYSQL_PORT=${MYSQL_PORT} -d ${IMG_NAME}
I really hope this would be helpful for somebody else.
Greetings,
Upvotes: 0
Reputation: 1409
Here is an example Dockerfile leveraging several clever techniques to all you to run a full conda environment for every RUN
stanza. You can use a similar approach to execute any arbitrary prep in a script file.
Note: there is a lot of nuance when it comes to login/interactive vs nonlogin/noninteractive shells, signals, exec
, the way multiple args are handled, quoting, how CMD and ENTRYPOINT interact, and a million other things, so don't be discouraged if when hacking around with these things, stuff goes sideways. I've spent many frustrating hours digging through all manner of literature and I still don't quite get how it all clicks.
## Conda with custom entrypoint from base ubuntu image
## Build with e.g. `docker build -t monoconda .`
## Run with `docker run --rm -it monoconda bash` to drop right into
## the environment `foo` !
FROM ubuntu:18.04
## Install things we need to install more things
RUN apt-get update -qq &&\
apt-get install -qq curl wget git &&\
apt-get install -qq --no-install-recommends \
libssl-dev \
software-properties-common \
&& rm -rf /var/lib/apt/lists/*
## Install miniconda
RUN wget -nv https://repo.anaconda.com/miniconda/Miniconda3-4.7.12-Linux-x86_64.sh -O ~/miniconda.sh && \
/bin/bash ~/miniconda.sh -b -p /opt/conda && \
rm ~/miniconda.sh && \
/opt/conda/bin/conda clean -tipsy && \
ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh
## add conda to the path so we can execute it by name
ENV PATH=/opt/conda/bin:$PATH
## Create /entry.sh which will be our new shell entry point. This performs actions to configure the environment
## before starting a new shell (which inherits the env).
## The exec is important! This allows signals to pass
RUN (echo '#!/bin/bash' \
&& echo '__conda_setup="$(/opt/conda/bin/conda shell.bash hook 2> /dev/null)"' \
&& echo 'eval "$__conda_setup"' \
&& echo 'conda activate "${CONDA_TARGET_ENV:-base}"' \
&& echo '>&2 echo "ENTRYPOINT: CONDA_DEFAULT_ENV=${CONDA_DEFAULT_ENV}"' \
&& echo 'exec "$@"'\
) >> /entry.sh && chmod +x /entry.sh
## Tell the docker build process to use this for RUN.
## The default shell on Linux is ["/bin/sh", "-c"], and on Windows is ["cmd", "/S", "/C"]
SHELL ["/entry.sh", "/bin/bash", "-c"]
## Now, every following invocation of RUN will start with the entry script
RUN conda update conda -y
## Create a dummy env
RUN conda create --name foo
## I added this variable such that I have the entry script activate a specific env
ENV CONDA_TARGET_ENV=foo
## This will get installed in the env foo since it gets activated at the start of the RUN stanza
RUN conda install pip
## Configure .bashrc to drop into a conda env and immediately activate our TARGET env
RUN conda init && echo 'conda activate "${CONDA_TARGET_ENV:-base}"' >> ~/.bashrc
ENTRYPOINT ["/entry.sh"]
Upvotes: 0
Reputation: 8863
If you have SHELL
available you should go with this answer -- don't use the accepted one, which forces you to put the rest of the dockerfile in one command per this comment.
If you are using an old Docker version and don't have access to SHELL
, this will work so long as you don't need anything from .bashrc
(which is a rare case in Dockerfiles):
ENTRYPOINT ["bash", "--rcfile", "/usr/local/bin/virtualenvwrapper.sh", "-ci"]
Note the -i
is needed to make bash read the rcfile at all.
Upvotes: 6
Reputation: 70459
According to https://docs.docker.com/engine/reference/builder/#run the default [Linux] shell for RUN
is /bin/sh -c
. You appear to be expecting bashisms, so you should use the "exec form" of RUN
to specify your shell.
RUN ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"]
Otherwise, using the "shell form" of RUN and specifying a different shell results in nested shells.
# don't do this...
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh"
# because it is the same as this...
RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]
If you have more than 1 command that needs a different shell, you should read https://docs.docker.com/engine/reference/builder/#shell and change your default shell by placing this before your RUN commands:
SHELL ["/bin/bash", "-c"]
Finally, if you have placed anything in the root user's .bashrc
file that you need, you can add the -l
flag to the SHELL
or RUN
command to make it a login shell and ensure that it gets sourced.
Note: I have intentionally ignored the fact that it is pointless to source a script as the only command in a RUN.
Upvotes: 32
Reputation: 2759
FROM ubuntu:14.04
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
This should work for every Ubuntu docker base image. I generally add this line for every Dockerfile I write.
If you want to get the effect of "use bash
instead of sh
throughout this entire Dockerfile", without altering and possibly damaging* the OS inside the container, you can just tell Docker your intention. That is done like so:
SHELL ["/bin/bash", "-c"]
* The possible damage is that many scripts in Linux (on a fresh Ubuntu install
grep -rHInE '/bin/sh' /
returns over 2700 results) expect a fully POSIX shell at/bin/sh
. The bash shell isn't just POSIX plus extra builtins. There are builtins (and more) that behave entirely different than those in POSIX. I FULLY support avoiding POSIX (and the fallacy that any script that you didn't test on another shell is going to work because you think you avoided basmisms) and just using bashism. But you do that with a proper shebang in your script. Not by pulling the POSIX shell out from under the entire OS. (Unless you have time to verify all 2700 plus scripts that come with Linux plus all those in any packages you install.)
More detail in this answer below. https://stackoverflow.com/a/45087082/117471
Upvotes: 263
Reputation: 6606
I ended up putting my env stuff in .profile
and mutated SHELL
something like
SHELL ["/bin/bash", "-c", "-l"]
# Install ruby version specified in .ruby-version
RUN rvm install $(<.ruby-version)
# Install deps
RUN rvm use $(<.ruby-version) && gem install bundler && bundle install
CMD rvm use $(<.ruby-version) && ./myscript.rb
Upvotes: 1
Reputation: 3751
According to Docker documentation
To use a different shell, other than ‘/bin/sh’, use the exec form passing in the desired shell. For example,
RUN ["/bin/bash", "-c", "echo hello"]
See https://docs.docker.com/engine/reference/builder/#run
Upvotes: 12
Reputation: 6594
If you're just trying to use pip to install something into the virtualenv, you can modify the PATH env to look in the virtualenv's bin folder first
ENV PATH="/path/to/venv/bin:${PATH}"
Then any pip install
commands that follow in the Dockerfile will find /path/to/venv/bin/pip first and use that, which will install into that virtualenv and not the system python.
Upvotes: 0
Reputation: 5782
I also had issues in running source
in a Dockerfile
This runs perfectly fine for building CentOS 6.6 Docker container, but gave issues in Debian containers
RUN cd ansible && source ./hacking/env-setup
This is how I tackled it, may not be an elegant way but this is what worked for me
RUN echo "source /ansible/hacking/env-setup" >> /tmp/setup
RUN /bin/bash -C "/tmp/setup"
RUN rm -f /tmp/setup
Upvotes: 6
Reputation: 7477
Simplest way is to use the dot operator in place of source, which is the sh equivalent of the bash source
command:
Instead of:
RUN source /usr/local/bin/virtualenvwrapper.sh
Use:
RUN . /usr/local/bin/virtualenvwrapper.sh
Upvotes: 97
Reputation: 6754
Building on the answers on this page I would add that you have to be aware that each RUN statement runs independently of the others with /bin/sh -c
and therefore won't get any environment vars that would normally be sourced in login shells.
The best way I have found so far is to add the script to /etc/bash.bashrc
and then invoke each command as bash login.
RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc
RUN /bin/bash --login -c "your command"
You could for instance install and setup virtualenvwrapper, create the virtual env, have it activate when you use a bash login, and then install your python modules into this env:
RUN pip install virtualenv virtualenvwrapper
RUN mkdir -p /opt/virtualenvs
ENV WORKON_HOME /opt/virtualenvs
RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc
RUN /bin/bash --login -c "mkvirtualenv myapp"
RUN echo "workon mpyapp" >> /etc/bash.bashrc
RUN /bin/bash --login -c "pip install ..."
Reading the manual on bash startup files helps understand what is sourced when.
Upvotes: 28
Reputation: 4595
You might want to run bash -v
to see what's being sourced.
I would do the following instead of playing with symlinks:
RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc
Upvotes: 4
Reputation: 15788
This might be happening because source
is a built-in to bash rather than a binary somewhere on the filesystem. Is your intention for the script you're sourcing to alter the container afterward?
Upvotes: 2