Использование инструкции RUN в файле Docker с "исходным кодом" не работает

У меня есть файл Docker, который я собираю, чтобы установить среду python для ванили (в которую я буду устанавливать приложение, но позже).

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

Строка работает нормально до последней строки, где я получаю следующее исключение:

[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

Если я ls в этот каталог (просто чтобы проверить, что были выполнены предыдущие шаги), я вижу, что файлы существуют как ожидалось:

$ 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

Если я попробую запустить команду source, я получаю ту же ошибку "не найден", как указано выше. Если я запускаю сеанс интерактивной оболочки, источник работает:

$ docker run 3a87 bash
source
bash: line 1: source: filename argument required
source: usage: source filename [arguments]

Я могу запустить script отсюда, а затем с радостью получить доступ к workon, mkvirtualenv и т.д.

Я сделал несколько копий, и изначально было похоже, что проблема может заключаться в различии между bash как оболочкой входа Ubuntu и тире как системная оболочка Ubuntu, тире не поддерживает команду source.

Тем не менее, ответ на этот вопрос заключается в использовании '. вместо source, но это просто приводит к тому, что время выполнения Docker взорвано с помощью исключения паники.

Каков наилучший способ запустить оболочку script из инструкции Dockerfile RUN, чтобы обойти это (я запускаю базовое изображение по умолчанию для Ubuntu 12.04 LTS).

Ответ 1

RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh"

Ответ 2

Оригинальный ответ

FROM ubuntu:14.04
RUN rm /bin/sh && ln -s /bin/bash /bin/sh

Это должно работать для каждого базового образа докера Ubuntu. Я обычно добавляю эту строку для каждого Dockerfile, который я пишу.

Редактировать заинтересованным свидетелем

Если вы хотите получить эффект "использовать bash вместо sh во всем этом Dockerfile", не изменяя и не повреждая * ОС внутри контейнера, вы можете просто сообщить Docker о своем намерении. Это делается так:

SHELL ["/bin/bash", "-c"]

* Возможный ущерб состоит в том, что многие скрипты в Linux (на новой установке Ubuntu grep -rHInE '/bin/sh'/ возвращает более 2700 результатов) ожидают полностью POSIX-оболочку в /bin/sh. Оболочка bash - это не просто POSIX плюс дополнительные встроенные функции. Есть встроенные функции (и даже больше), которые ведут себя совершенно иначе, чем в POSIX. Я ПОЛНОСТЬЮ поддерживаю избегание POSIX (и заблуждение, что любой скрипт, который вы не тестировали в другой оболочке, сработает, потому что вы думаете, что вы избежали басмизм), и просто использование bashism. Но вы делаете это с правильным шебангом в вашем сценарии. Не вытаскивая оболочку POSIX из-под всей ОС. (Если у вас нет времени, чтобы проверить все 2700 и более скриптов, которые поставляются с Linux, плюс все в любых пакетах, которые вы устанавливаете.)

Более подробно в этом ответе ниже. fooobar.com/questions/46731/...

Ответ 3

У меня была такая же проблема, и для выполнения установки pip внутри virtualenv мне пришлось использовать эту команду:

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"

Надеюсь, это поможет.

Ответ 4

Самый простой способ - использовать оператор точки вместо источника, что эквивалентно команде bash source:

Вместо:

RUN source /usr/local/bin/virtualenvwrapper.sh

Использование:

RUN . /usr/local/bin/virtualenvwrapper.sh

Ответ 5

Оболочкой по умолчанию для инструкции RUN является ["/bin/sh", "-c"].

RUN "source file"      # translates to: RUN /bin/sh -c "source file"

Используя инструкцию SHELL, вы можете изменить оболочку по умолчанию для последующих инструкций RUN в Dockerfile:

SHELL ["/bin/bash", "-c"] 

Теперь оболочка по умолчанию изменилась, и вам не нужно явно определять ее в каждой инструкции RUN

RUN "source file"    # now translates to: RUN /bin/bash -c "source file"

Дополнительное примечание: Вы также можете добавить --login которая запускает оболочку входа. Это означает, что ~/.bachrc например, будет прочитан, и вам не нужно явно указывать его перед вашей командой

Ответ 6

Основываясь на ответах на этой странице, я бы добавил, что вам нужно знать, что каждый оператор RUN работает независимо от других с помощью /bin/sh -c и, следовательно, не будет получать каких-либо окружающих vars, которые, как правило, будут получены в оболочках входа.

Лучший способ, который я нашел до сих пор, - добавить script в /etc/bash.bashrc, а затем вызвать каждую команду как bash login.

RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc
RUN /bin/bash --login -c "your command"

Вы могли бы, например, установить и настроить virtualenvwrapper, создать виртуальный env, активировать его при использовании входа bash, а затем установить ваши модули python в это 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 ..."

Чтение руководства по bash файлам-автозагрузкам помогает понять, что происходит, когда.

Ответ 7

Если вы используете Docker 1.12 или новее, просто используйте SHELL!

Краткий ответ:

Общие:

SHELL ["/bin/bash", "-c"] 

для python vituralenv:

SHELL ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"]

Длинный ответ:

из https://docs.docker.com/engine/reference/builder/#/shell

SHELL ["executable", "parameters"]

Инструкция SHELL позволяет использовать оболочку по умолчанию, используемую для формы оболочки команд, подлежащих переопределению. По умолчанию оболочка в Linux [ "/bin/sh", "-c" ], а в Windows - [ "cmd", "/S", "/C" ]. SHELL инструкция должна быть записана в форме JSON в файле Docker.

Инструкция SHELL особенно полезна в Windows, где являются двумя обычно используемыми и совершенно разными родными оболочками: cmd и powershell, а также альтернативные оболочки, включая sh.

Инструкция SHELL может появляться несколько раз. Каждый SHELL команда отменяет все предыдущие инструкции SHELL и влияет на все последующие инструкции. Например:

FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S"", "/C"]
RUN echo hello

Следующие инструкции могут быть изменены инструкцией SHELL когда их форма оболочки используется в файле Dockerfile: RUN, CMD и Entrypoint.

Следующий пример - это общий шаблон, найденный в Windows, который может оптимизироваться с помощью инструкции SHELL:

...
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
...

Команда, вызываемая докером, будет:

cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

Это неэффективно по двум причинам. Во-первых, нет необходимости Вызывается командный процессор cmd.exe(aka shell). Во-вторых, каждый RUN инструкции в форме оболочки требуется дополнительная команда powershell -command префикс команды.

Чтобы сделать это более эффективным, можно использовать один из двух механизмов. Один из них - использовать форму JSON команды RUN, например:

...
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
...

Хотя форма JSON недвусмысленна и не использует ненужные cmd.exe, это требует большей многословности посредством двойного цитирования и побега. Альтернативным механизмом является использование инструкции SHELL и форма оболочки, создавая более естественный синтаксис для пользователей Windows, особенно в сочетании с директивой анализа парсера:

# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

Результат:

PS E:\docker\build\shell> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
 ---> Running in 6fcdb6855ae2
 ---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in d0eef8386e97


    Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       10/28/2016  11:26 AM                Example


 ---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
 ---> Running in be6d8e63fe75
hello world
 ---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\docker\build\shell>

Инструкция SHELL может также использоваться для изменения способа, которым оболочка работает. Например, используя SHELL cmd/S/C/V: ON | OFF on Windows, семантика расширения переменной задержки с задержкой может быть изменен.

Инструкция SHELL может также использоваться на Linux, если альтернативный оболочки, такие как zsh, csh, tcsh и другие.

Функция SHELL была добавлена ​​в Docker 1.12.

Ответ 8

Согласно https://docs.docker.com/engine/reference/builder/#run оболочкой [Linux] по умолчанию для RUN является /bin/sh -c. Вы, кажется, ожидаете bashisms, поэтому вы должны использовать "exec форму" RUN чтобы указать вашу оболочку.

RUN ["/bin/bash", "-c", "source /usr/local/bin/virtualenvwrapper.sh"]

В противном случае использование "формы оболочки" RUN и указание другой оболочки приводит к появлению вложенных оболочек.

# 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"]

Если у вас более одной команды, для которой требуется другая оболочка, вам следует прочитать https://docs.docker.com/engine/reference/builder/#shell и изменить оболочку по умолчанию, поместив ее перед командами RUN:

SHELL ["/bin/bash", "-c"]

Наконец, если вы поместили что-либо в файл .bashrc корневого пользователя, который вам нужен, вы можете добавить флаг -l к команде SHELL или RUN чтобы сделать его оболочкой входа и убедиться, что он получен.

Примечание. Я намеренно проигнорировал тот факт, что бессмысленно указывать сценарий в качестве единственной команды в RUN.

Ответ 9

Согласно документации Docker

Чтобы использовать другую оболочку, отличную от '/bin/sh, используйте форму exec, проходящую в нужной оболочке. Например,

RUN ["/bin/bash", "-c", "echo hello"]

См. https://docs.docker.com/engine/reference/builder/#run

Ответ 10

У меня также были проблемы при запуске source в Dockerfile

Это отлично работает для создания контейнера CentOS 6.6 Docker, но дает проблемы в контейнерах Debian.

RUN cd ansible && source ./hacking/env-setup

Вот как я справился с этим, возможно, не изящным, но это то, что сработало для меня.

RUN echo "source /ansible/hacking/env-setup" >> /tmp/setup
RUN /bin/bash -C "/tmp/setup"
RUN rm -f /tmp/setup

Ответ 11

Это может произойти, потому что source является встроенным в bash, а не двоичным файлом где-то в файловой системе. Ваше намерение использовать script для поиска контейнера после этого?

Ответ 12

Возможно, вы захотите запустить bash -v, чтобы узнать, что источник.

Я бы сделал следующее вместо игры с символическими ссылками:

RUN echo "source /usr/local/bin/virtualenvwrapper.sh" >> /etc/bash.bashrc

Ответ 13

Если вы просто пытаетесь использовать pip для установки чего-то в virtualenv, вы можете изменить PATH env, чтобы сначала посмотреть в папку виртуального диска bin

ENV PATH="/path/to/venv/bin:${PATH}"

Затем любые команды pip install, которые следуют в файле Docker, сначала найдут /path/to/venv/bin/pip и будут использовать это, которое будет установлено в этот virtualenv, а не системный python.

Ответ 14

Я закончил тем, что поместил свой материал env в .profile и мутировал SHELL что-то вроде

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

Ответ 15

Если у вас есть SHELL вы должны пойти с этим ответом - не используйте принятый ответ, который заставляет вас поместить остальную часть файла docker в одну команду для каждого комментария.

Если вы используете старую версию Docker и не имеете доступа к SHELL, это будет работать до тех пор, пока вам ничего не нужно из .bashrc (что является редким случаем в Dockerfiles):

ENTRYPOINT ["bash", "--rcfile", "/usr/local/bin/virtualenvwrapper.sh", "-ci"]

Обратите внимание, что -i необходим, чтобы bash вообще читал rcfile.