Использование инструкции RUN в Dockerfile с 'source' не работает

274

У меня есть Dockerfile, который я собираю для установки среды vanilla 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выполнить команду, я получаю ту же ошибку «not found», что и выше. Однако, если я запускаю сеанс интерактивной оболочки, источник работает:

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

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

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

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

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

Хьюго Роджер-Браун
источник
2
Так что не «исходите», просто запустите команду. Или специально запустите скрипт оболочки с помощью 'bash'.
Алистер Булман
Попробовал это - хотя скрипт не дает сбоя, я не получаю доступ к различным командам, что я и хотел. Эта проблема - то же самое - github.com/dotcloud/docker/issues/2847
Уго Роджер-Браун
2
Что, думая об этом, является правильным. Virtualenvwrapper, вероятно, не имеет смысла в контейнерной среде. Я собираюсь поддержать это и использовать вместо этого «native» virtualenv.
Уго Роджер-Браун
1
Более фундаментальный способ решения этой проблемы является stackoverflow.com/questions/4732200/...
Gaurav Ojha
ПопробуйтеCMD source activate django-py35
Бельтер

Ответы:

316

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

chobo
источник
67
А? Если вы создадите сценарий в оболочке, которая существует только для этой команды, он не сможет иметь долгосрочного эффекта при выполнении любых будущих команд, предполагая, что общая сумма его действия - установка переменных среды. Так зачем тебе sourceвообще, просто bash /usr/local/bin/virtualenvwrapper.shв таком случае?
Чарльз Даффи
13
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh; my_command; my_command; my_command;"
Лев
29
Это не правильно, хотя это работает. Прочитайте docs.docker.com/engine/reference/builder/#run и не останавливайтесь после второго примера кода. Прочитайте примечание: это сразу следует. Поскольку /bin/sh -cэто оболочка по умолчанию, эта «форма оболочки» RUN переводится как RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]. Вы должны пойти дальше и использовать «исполнительную форму» RUN, чтобы вы могли убрать shвот такRUN ["/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]
Бруно Броноски
8
Пожалуйста, посмотрите stackoverflow.com/a/45087082/117471, чтобы понять, почему это создает bashвложенные в shи поэтому следует избегать.
Бруно Броноски
4
Гораздо лучший ответ здесь: stackoverflow.com/a/42216046/1663462
Крис Стрычински
150

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

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, плюс все те, которые есть в любых установленных вами пакетах.)

Более подробно в этом ответе ниже. https://stackoverflow.com/a/45087082/117471

Анубхав Синха
источник
18
Это можно немного упростить:ln -snf /bin/bash /bin/sh
apottere
2
@ user1442219 это заменяет интерпретатор команд по умолчанию с shнаbash
Бхаргав Нанекалва
27
ln -s /bin/bash /bin/shэто ужасная идея Ubuntu целевых / bin / sh, чтобы разбить по причине. dash - это полностью posix-оболочка, которая на несколько порядков быстрее, чем bash. ссылка / bin / sh на bash резко снизит производительность вашего сервера. Цитировать
ксерокопия
7
Это грязный взлом, а не решение. Если ваш скрипт запускается shоболочкой, но вы хотите bash, правильное решение - это shвызвать процесс bashкак однократный, например bash -c 'source /script.sh && …', или вы могли бы даже пойти так далеко, чтобы полностью избежать бешизмов (например source), и вместо этого выбрать только когда-либо использовать действительные эквиваленты POSIX, например . /script.sh. (Запомните пробел после .!) И наконец, если ваш скрипт исполняемый (а не только исходный), никогда не заставляйте ваш скрипт лежать с #!/bin/shшебангом, если он на самом деле не совместим с sh. Используйте #!/bin/bashвместо этого.
Марк Г.
7
А теперь, как я могу понизить исходный ответ и одобрить редактирование заинтересованным лицом?
Слава
65

Оболочка по умолчанию для 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что, например, будет прочитано, и вам не нужно явно указывать его перед вашей командой

Ахмад Абдельхани
источник
1
Отличный указатель на использование --login- только что сам понял
mattexx
1
Благодаря этому SHELL ["/bin/bash", "-c", "-l"] я смог использовать дополнительные обновления для файла .bashrc, что позволило мне легко запускать команды asdf.
Ровинсон Гальего
46

У меня была такая же проблема, и для выполнения установки 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"

Я надеюсь, что это помогает.

Андреа Гранди
источник
Если вы пришли от ответов ROS, да, это работает. Что-то вроде:RUN /bin/bash -c "source /opt/ros/melodic/setup.bash && \ cd /home && \ git clone https://angelos.p:$password@gitlab.com/inno/grpc-comms.git && \ cd grpc-comms && \ mkdir build && \ cd build && \ cmake .. && make"
angelos.p
44

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

Вместо того:

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

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

RUN . /usr/local/bin/virtualenvwrapper.sh
mixja
источник
«source - встроенная оболочка Bourne и специальная встроенная POSIX» - ss64.com/bash/source.html linux.die.net/man/1/sh ... . / sourceтакже принимает позиционные параметры после имени файла
Wes Тернер
5
Это не работает, так как каждая команда RUN работает независимо. Изменения от sourceили .теряются при завершении команды RUN. См .: stackoverflow.com/a/40045930/19501
amit
26

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

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

Общее:

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

для питона 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 в Dockerfile.

Инструкция 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"
...

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

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

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

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

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

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

# 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 в Windows, можно изменить семантику расширения переменной среды с задержкой.

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

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

Mithril
источник
20

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

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

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

Например, вы можете установить и настроить virtualenvwrapper, создать виртуальную среду, активировать ее при использовании входа в систему bash, а затем установить модули python в эту среду:

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 помогает понять, когда это происходит.

TomDotTom
источник
1
Круто, основываясь на вашем решении, я проиллюстрировал это следующим образом: ADD env-file /etc/profile.d/installerenv.sh RUN /bin/bash --login -c 'env' RUN /bin/bash -c 'rm /etc/profile.d/installerenv.sh' если один из вариантов использования добавляет дополнительные переменные среды ввода в перспективу сборки Docker, как я сделал, я бы порекомендовал взглянуть на docs.docker.com/compose/yml. / # env-файл тоже.
daniel.kahlenberg
1
Я полагаю, что проблема в том, что вы не заканчиваете кэшированием результатов каждой RUNкоманды, а это значит, что вы не можете установить множество зависимостей проекта, а затем скопировать исходный код и воспользоваться преимуществами Кэширование промежуточных шагов докера. Он будет переустанавливать все зависимости проекта каждый раз.
erewok
Используйте /etc/bashrcдля Redhat, а не /etc/bash.bashrcкак указано выше (для Ubuntu)
Джордан Джи
Я использовал /root/.bashrc для centos.
Шмуду
RUN echo "source /yourscript.bash" >> /etc/bash.bashrc делает свое дело. если вы используете ros в Docker и хотите настроить среду, это то, что вы должны сделать
user27221
17

Согласно https://docs.docker.com/engine/reference/builder/#run оболочка [Linux] по умолчанию для RUNis /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файл пользователя root что-то, что вам нужно, вы можете добавить -lфлаг к команде SHELLor, RUNчтобы сделать его оболочкой входа в систему и убедиться, что он получен.

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

Бруно Броноски
источник
1
SHELL ["/bin/sh", "-c", "-l"]поэтому он
получает исходные тексты
1
@MortenB, но вы указали (опечатано?), Что /bin/shне решит проблему неиспользования bash. Кроме того, при выполнении docker buildоперации вряд ли будет что-то полезное в .bashrc пользователя root, которое вам нужно. Но, если вы поместите что-то там ранее в Dockerfile (например, возможно JAVA_HOME, тогда да. Я добавлю примечание об этом в своем ответе.
Бруно Броноски
Извините за опечатку, я использую pyenv, которому требуется исходный текст ~ / .bashrc, чтобы установить пути для правильной версии python в моих базовых изображениях. это заставляет меня использовать любую имеющуюся базу linux и двумя строками добавить любую версию на python. Как и Python 3.7 в Ubuntu16.04, где базовый Python - 3.5.2
MortenB
11

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

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

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

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

Джанлука Казати
источник
Это фактический правильный ответ. Автор выбранного ответа stackoverflow.com/a/25086628/117471, кажется, прочитал только первый пример в документации, на которую вы ссылаетесь. Кажется, они не читали следующий абзац, который вы цитировали.
Бруно Броноски
4

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

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

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

Обратите внимание, что -ibash необходим для чтения rcfile.

Mohan
источник
3

Вы могли бы хотеть бежать bash -v чтобы увидеть, что происходит.

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

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

vimdude
источник
3

У меня тоже были проблемы с бегом 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
vikas027
источник
2

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

Пол Мори
источник
1
Скрипт обновляет контейнер - но, честно говоря, я пытался сделать что-то, что не имело смысла, поэтому обошел проблему.
Хьюго Роджер-Браун
1

Я закончил тем, что положил свои вещи в 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
mattexx
источник
3
«-c» должен быть последним аргументом (перед выполнением команды.)
peterk
0

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

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

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

shadfc
источник