Використання інструкції RUN в Dockerfile з 'source' не працює


274

У мене є Dockerfile, який я збираю, щоб встановити середовище ванільного пітона (в яке я буду встановлювати додаток, але пізніше).

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]

Я можу запустити скрипт тут, а потім благополучно доступ workon, і mkvirtualenvт.д.

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

Однак відповідь на це, мабуть, використовується "." замість цього source, але це просто призводить до того, що час роботи Docker вибухне за винятком паніки.

Який найкращий спосіб запустити скрипт оболонки з інструкції Dockerfile RUN, щоб обійти це (я запускаю базове зображення за замовчуванням для Ubuntu 12.04 LTS).


2
Тому не "джерело" це, просто запустіть команду. Або спеціально запустіть скрипт оболонки з 'bash'.
Алістер Бульман

Спробував це - хоча сценарій не виходить з ладу, я не отримую доступу до різних команд, чого я хотів. Це питання те саме - github.com/dotcloud/docker/isissue/2847
Уго Роджер-Браун

2
Що, думаючи про це, правильно. Virtualenvwrapper, ймовірно, не має сенсу в контейнерному середовищі. Я збираюся відмовитись від цього і використаю замість нього "рідний" virtualenv.
Уго Роджер-Браун

1
Більш фундаментальний спосіб підійти до цього - stackoverflow.com/questions/4732200/…
Gaurav Ojha

СпробуйтеCMD source activate django-py35
Белтер

Відповіді:


315

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


66
Так? Якщо ви джерело скрипту всередині оболонки, який існує лише для команди, він не може мати тривалий ефект на будь-який майбутній запуск команд, припускаючи, що загальна сума його дії визначає змінні середовища. То чому б ви взагалі використовували 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
Кріс Stryczynski

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 (і помилки, що будь-який сценарій, який ви не тестували на іншій оболонці, спрацює, тому що ви думаєте, що уникнув басмізму) і просто використовує башизм. Але ви робите це правильним шебангом у вашому сценарії. Не витягуючи оболонку POSIX з-під усієї ОС. (Якщо ви не встигнете перевірити всі сценарії 2700 плюс, що постачаються в комплекті з Linux, а також всі ті, що встановлені в будь-яких пакунках.)

Детальніше у цій відповіді нижче. https://stackoverflow.com/a/45087082/117471


18
Це можна трохи спростити:ln -snf /bin/bash /bin/sh
апотертер

2
@ user1442219 це замінює інтерпретатора команд за замовчуванням з shнаbash
Bhargav Nanekalva

27
ln -s /bin/bash /bin/shце жахлива ідея. ubuntu - цілі / bin / sh, щоб тирети чомусь. дефіс - це повністю позиксивна оболонка, яка набирає порядку на швидкість, ніж баш. посилання / bin / sh до bash різко знизить продуктивність вашого сервера. цитуйте
xero

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

За допомогою 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

"джерело - це вбудована оболонка 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"] 

для 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 у Dockerfile.

Інструкція SHELL особливо корисна для Windows, де є дві загальноприйняті та зовсім різні нативні оболонки: cmd та powerhell, а також доступні альтернативні оболонки, включаючи 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 у формі оболонки вимагає додаткової префіксації команди.

Щоб зробити це більш ефективним, можна використовувати один з двох механізмів. Перший - використовувати форму 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 у Windows, семантика розширення змінної середовища може бути змінена.

Інструкція SHELL може також використовуватися в Linux, якщо потрібна альтернативна оболонка, така як zsh, csh, tcsh та інші.

Функція SHELL була додана в Docker 1.12.


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, створити віртуальну 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 допомагає зрозуміти, що відбувається коли.


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' Якщо один із випадків використання додає більше змінних середовища для ін'єкцій в перспективу побудови докера, як моя, я б рекомендував ознайомитися з docs.docker.com/compose/yml / # env-файл теж.
daniel.kahlenberg

1
Проблема з цим, я вважаю, полягає в тому, що ви не закінчуєте кешування результатів кожної RUNкоманди, а це означає, що ви не можете встановити цілу кількість залежностей проекту, а потім скопіювати вихідний код і скористатися перевагами Проміжний кэш керування Докера Він буде встановлювати всі залежності проекту кожного разу.
erewok

Використовуйте /etc/bashrcдля Redhat, замість /etc/bash.bashrcзазначеного вище (для Ubuntu)
Jordan Gee

Я використовував /root/.bashrc для центів.
schmudu

RUN echo "source /yourscript.bash" >> /etc/bash.bashrc виконує свою справу. якщо ви використовуєте ros всередині docker і хочете створити середовище, це те, що вам слід зробити
user27221

17

Відповідно до https://docs.docker.com/engine/reference/builder/#run типовою оболонкою [Linux] для RUNє /bin/sh -c. Ви, схоже, очікуєте башизмів, тому вам слід скористатися "exec form" 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"]

Якщо у вас є більше 1 команди, якій потрібна інша оболонка, вам слід прочитати https://docs.docker.com/engine/reference/builder/#shell і змінити стандартну оболонку, поставивши це перед командами RUN:

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

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

Примітка. Я навмисно ігнорував той факт, що безглуздо джерело скрипту як єдиної команди в RUN.


SHELL ["/bin/sh", "-c", "-l"]тож він
надсилає

1
@MortenB, але ви вказали (друкували?), /bin/shЩо не вирішить проблему використання bash. Крім того, коли ви робите docker buildце, навряд чи в корисному користувачеві .bashrc буде що-небудь корисне, що вам потрібно. Але, якщо ви поклали що - то там на початку Dockerfile (наприклад , може бути JAVA_HOME., То так , я покладу записку про це в своїй відповіді.
Бруно Bronosky

Вибачте за друкарську помилку, я використовую pyenv, якому потрібно джерело ~ / .bashrc, щоб встановити шляхи для правильної версії python у моїх базових зображеннях. це змушує мене використовувати будь-яку базу Linux, і за допомогою двох рядків додайте будь-яку версію на python. Як і python 3.7 на ubuntu16.04, де базовий python становить 3.5.2
MortenB

11

Відповідно до документації Докера

Щоб використовувати іншу оболонку, крім '/ bin / sh', використовуйте форму виконання, що передається в потрібну оболонку. Наприклад,

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

Дивіться https://docs.docker.com/engine/reference/builder/#run


Це дійсна правильна відповідь. Автор вибраної відповіді stackoverflow.com/a/25086628/117471, схоже, прочитав лише перший приклад у документації, на яку ви посилаєтесь. Вони, здається, не прочитали наступний абзац, який ви цитували.
Бруно Броноський

4

Якщо у вас є SHELLдоступ, вам слід перейти з цією відповіддю - не використовуйте прийнятий, що змушує вас залишити решту dockerfile в одну команду за цим коментарем .

Якщо ви використовуєте стару версію Docker і не маєте доступу до неї SHELL, це буде працювати до тих пір, поки вам нічого не потрібно .bashrc(що рідкісний випадок у Dockerfiles):

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

Зауважте -i, необхідне, щоб баш читав rcfile взагалі.


3

Можливо, ви захочете побігти, bash -vщоб побачити, що відбувається.

Я б зробив наступне, а не грав із посиланнями:

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


3

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

Це ідеально підходить для створення контейнера Docker CentOS 6.6, але випускає проблеми з контейнерами 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

2

Це може статися тому source, що це вбудована програма для заміщення файлів, а не двійковий файл десь у файловій системі. Чи є ваш намір для сценарію, який ви шукаєте, щоб згодом змінити контейнер?


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

3
"-c" повинен бути останнім аргументом (перед виконанням command.to)
peterk

0

Якщо ви просто намагаєтесь використовувати pip, щоб встановити щось у virtualenv, ви можете змінити PATH env, щоб спочатку подивитися у папці бін virtualenv

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

Тоді будь-які pip installкоманди, що слідують у Dockerfile, спочатку знайдуть / path / to / venv / bin / pip та використають те, що встановить у цей virtualenv, а не системний python.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.