L'uso dell'istruzione RUN in un Dockerfile con 'source' non funziona


274

Ho un Dockerfile che sto mettendo insieme per installare un ambiente Python Vanilla (in cui installerò un'app, ma in un secondo momento).

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

La build funziona correttamente fino all'ultima riga, dove ottengo la seguente eccezione:

[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

Se mi trovo lsin quella directory (solo per testare che sono stati commessi i passaggi precedenti) posso vedere che i file esistono come previsto:

$ 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

Se provo a eseguire il sourcecomando, visualizzo lo stesso errore "non trovato" di cui sopra. Se eseguo comunque una sessione di shell interattiva, l'origine funziona:

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

Posso eseguire lo script da qui, quindi accedere felicemente workon, mkvirtualenvecc.

Ho fatto qualche ricerca, e inizialmente sembrava che il problema potesse risiedere nella differenza tra bash come shell di accesso Ubuntu e dash come shell di sistema Ubuntu , dash non supporta il sourcecomando.

Tuttavia, la risposta a questa sembra essere l'uso di "." invece di source, ma questo fa semplicemente esplodere il runtime Docker con un'eccezione di panico.

Qual è il modo migliore per eseguire uno script di shell da un'istruzione RUN Dockerfile per aggirare questo problema (sto eseguendo l'immagine di base predefinita per Ubuntu 12.04 LTS).


2
Quindi non 'sorgente', basta eseguire il comando. O in particolare eseguire lo script di shell con 'bash'.
Alister Bulman,

Ci ho provato - anche se lo script non fallisce, non riesco ad accedere ai vari comandi, cosa che volevo. Questo problema è la stessa cosa: github.com/dotcloud/docker/issues/2847
Hugo Rodger-Brown,

2
Che, a pensarci, è corretto. Virtualenvwrapper probabilmente non ha senso in un ambiente container. Ho intenzione di tornare indietro e utilizzare invece virtualenv 'nativo'.
Hugo Rodger-Brown,

1
Il modo più fondamentale per affrontare questo è stackoverflow.com/questions/4732200/...
Gaurav Ojha

ProvaCMD source activate django-py35
Belter il

Risposte:


316

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


67
Eh? Se si genera uno script all'interno di una shell che esiste solo per il comando, non è in grado di avere un effetto duraturo su eventuali comandi futuri eseguiti, supponendo che la somma totale della sua azione stia impostando le variabili di ambiente. Quindi perché dovresti usare sourceaffatto, contro solo bash /usr/local/bin/virtualenvwrapper.sh, in quel caso?
Charles Duffy,

13
RUN /bin/bash -c "source /usr/local/bin/virtualenvwrapper.sh; my_command; my_command; my_command;"
Leone

29
Questo non è corretto anche se funziona. Leggi docs.docker.com/engine/reference/builder/#run e non fermarti dopo il secondo esempio di codice. Leggi la nota: che segue immediatamente. Poiché /bin/sh -cè la shell predefinita, questa "forma shell" di RUN si traduce in RUN ["/bin/sh", "-c", "/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]. Dovresti andare avanti e utilizzare il "modulo esecutivo" di RUN in modo da poter shuscire cosìRUN ["/bin/bash" "-c" "source /usr/local/bin/virtualenvwrapper.sh"]
Bruno Bronosky,

8
Si prega di consultare stackoverflow.com/a/45087082/117471 per capire perché questo crea un bashnidificato in un she dovrebbe quindi essere evitato.
Bruno Bronosky,

4
Una risposta molto migliore è qui: stackoverflow.com/a/42216046/1663462
Chris Stryczynski

150

Risposta originale

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

Questo dovrebbe funzionare per ogni immagine base docker di Ubuntu. In genere aggiungo questa riga per ogni Dockerfile che scrivo.

Modifica di uno spettatore interessato

Se si desidera ottenere l'effetto di "utilizzo bashanziché shdell'intero Dockerfile", senza alterare e possibilmente danneggiare * il sistema operativo all'interno del contenitore, è possibile comunicare a Docker le proprie intenzioni . Questo è fatto così:

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

* Il possibile danno è che molti script in Linux (su una nuova installazione di Ubuntu grep -rHInE '/bin/sh' /restituiscono oltre 2700 risultati) si aspettano una shell POSIX completa su /bin/sh. La shell bash non è solo POSIX più builtin extra. Ci sono builtin (e altro) che si comportano in modo completamente diverso da quelli in POSIX. Sostengo pienamente evitando di POSIX (e l'errore di qualsiasi script che non hai testato su un'altra shell funzionerà perché pensi di aver evitato i basmismi) e di usare solo il bashismo. Ma lo fai con un vero shebang nella tua sceneggiatura. Non estraendo la shell POSIX da sotto l'intero sistema operativo. (A meno che tu non abbia il tempo di verificare tutti gli oltre 2700 script forniti con Linux e tutti quelli presenti nei pacchetti installati.)

Maggiori dettagli in questa risposta di seguito. https://stackoverflow.com/a/45087082/117471


18
Questo può essere semplificato un po ':ln -snf /bin/bash /bin/sh
apottere

2
@ user1442219 sostituisce l'interprete dei comandi predefinito da shabash
Bhargav Nanekalva,

27
ln -s /bin/bash /bin/shquesta è un'idea terribile. ubuntu ha come target / bin / sh to dash per un motivo. dash è una shell completamente posix che è ordini di grandezza più veloci di bash. il collegamento / bin / sh a bash ridurrà drasticamente le prestazioni del tuo server. citare: wiki.ubuntu.com/DashAsBinSh
xero

7
Questo è un trucco sporco, non una soluzione. Se il tuo script viene eseguito dalla shshell, ma vuoi bash, la soluzione corretta è quella di far shinvocare il processo bashcome una tantum, ad esempio bash -c 'source /script.sh && …', oppure potresti persino arrivare al punto di evitare del tutto i bashismi (come source), e invece scegliere di utilizzare sempre e comunque equivalenti POSIX validi, ad es . /script.sh. (Pensa allo spazio dopo il .!) Infine, se la tua sceneggiatura è eseguibile (non solo sourceable), non far mai mentire la tua sceneggiatura con un #!/bin/shshebang se non è realmente sh-compatibile. Usa #!/bin/bashinvece.
Mark G.

7
E ora come faccio a sottoporre a voto negativo la risposta originale e a votare la modifica di "un interessato"?
Slava,

65

La shell predefinita per l' RUNistruzione è ["/bin/sh", "-c"].

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

Utilizzando l' istruzione SHELL , è possibile modificare la shell predefinita per le RUNistruzioni successive in Dockerfile:

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

Ora, la shell predefinita è cambiata e non è necessario definirla esplicitamente in ogni istruzione RUN

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

Nota aggiuntiva : è inoltre --loginpossibile aggiungere un'opzione che avvierebbe una shell di accesso. Ciò significa che, ~/.bachrcad esempio, verrebbe letto e non è necessario cercarlo esplicitamente prima del comando


1
Grande --login
suggerimento sull'uso

1
Usando SHELL ["/bin/bash", "-c", "-l"] sono stato in grado di utilizzare ulteriori aggiornamenti al file .bashrc, che mi ha permesso di eseguire facilmente comandi asdf.
Rowinson Gallego,

46

Ho avuto lo stesso problema e per eseguire l'installazione pip all'interno di virtualenv ho dovuto usare questo comando:

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"

Spero possa essere d'aiuto.


Nel caso in cui tu venissi dalle risposte ROS, sì, funziona. Qualcosa del tipo: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

Il modo più semplice è usare l'operatore punto al posto della sorgente, che è l'equivalente sh del sourcecomando bash :

Invece di:

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

Uso:

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

"source è un builtin bourne shell e un POSIX` special 'builtin "- ss64.com/bash/source.html linux.die.net/man/1/sh ... . / sourceaccetta anche parametri posizionali dopo il nome del file
Wes Turner,

5
Questo non funziona poiché ogni comando RUN funziona in modo indipendente. Le modifiche da sourceo .vanno perse al termine del comando RUN. Vedi: stackoverflow.com/a/40045930/19501
Amit

26

Se stai usando Docker 1.12 o più recente, basta usare SHELL!

Risposta breve:

generale:

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

per pitone vituralenv:

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

Risposta lunga:

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

SHELL ["executable", "parameters"]

L'istruzione SHELL consente di sovrascrivere la shell predefinita utilizzata per la forma shell di comandi. La shell predefinita su Linux è ["/ bin / sh", "-c"] e su Windows è ["cmd", "/ S", "/ C"]. L'istruzione SHELL deve essere scritta in formato JSON in un file Docker.

L'istruzione SHELL è particolarmente utile su Windows dove ci sono due shell native comunemente usate e abbastanza diverse: cmd e powershell, oltre a shell alternative disponibili tra cui sh.

L'istruzione SHELL può apparire più volte. Ogni istruzione SHELL ha la precedenza su tutte le precedenti istruzioni SHELL e influisce su tutte le istruzioni successive. Per esempio:

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

Le seguenti istruzioni possono essere influenzate dall'istruzione SHELL quando la forma shell di esse viene utilizzata in un file Docker: RUN, CMD ed ENTRYPOINT.

Il seguente esempio è un modello comune trovato su Windows che può essere semplificato usando l'istruzione SHELL:

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

Il comando invocato dalla finestra mobile sarà:

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

Questo è inefficiente per due motivi. Innanzitutto, viene invocato un processore di comando cmd.exe non necessario (aka shell). In secondo luogo, ogni istruzione RUN nel modulo shell richiede un comando powershell aggiuntivo che precede il comando.

Per renderlo più efficiente, è possibile utilizzare uno dei due meccanismi. Uno è usare la forma JSON del comando RUN come:

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

Sebbene il modulo JSON sia inequivocabile e non utilizzi il cmd.exe non necessario, richiede una maggiore verbosità mediante virgolette doppie e escape. Il meccanismo alternativo consiste nell'utilizzare l'istruzione SHELL e il modulo shell, creando una sintassi più naturale per gli utenti Windows, in particolare se combinato con la direttiva parser di 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'

Con il risultato di:

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>

L'istruzione SHELL potrebbe anche essere usata per modificare il modo in cui una shell opera. Ad esempio, utilizzando SHELL cmd / S / C / V: ON | OFF su Windows, è possibile modificare la semantica di espansione della variabile di ambiente ritardata.

L'istruzione SHELL può essere utilizzata anche su Linux nel caso in cui sia richiesta una shell alternativa come zsh, csh, tcsh e altre.

La funzione SHELL è stata aggiunta in Docker 1.12.


20

Basandomi sulle risposte in questa pagina, aggiungerei che devi essere consapevole che ogni istruzione RUN viene eseguita indipendentemente dalle altre con /bin/sh -ce quindi non otterrà alcun ambiente che verrebbe normalmente nelle shell di login.

Il modo migliore che ho trovato finora è quello di aggiungere lo script /etc/bash.bashrce quindi invocare ogni comando come login bash.

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

Ad esempio, è possibile installare e configurare virtualenvwrapper, creare l'env virtuale, attivarlo quando si utilizza un accesso bash e quindi installare i moduli python in questo 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 ..."

Leggere il manuale sui file di avvio di bash aiuta a capire cosa proviene da quando.


1
Fantastico, in base alla tua soluzione quello che ho fatto solo per illustrare è stato: ADD env-file /etc/profile.d/installerenv.sh RUN /bin/bash --login -c 'env' RUN /bin/bash -c 'rm /etc/profile.d/installerenv.sh' se il caso d'uso di un utente aggiunge più di una variabile d'ambiente iniettata alla prospettiva di costruzione docker come la mia, consiglierei di dare un'occhiata a docs.docker.com/compose/yml / # env-file troppo.
daniel.kahlenberg,

1
Il problema con questo, credo, è che non si finisce per memorizzare nella cache i risultati di ciascun RUNcomando, il che significa che non è possibile installare molte dipendenze del progetto e quindi copiare sul codice sorgente e sfruttare i vantaggi di Memorizzazione nella cache dei passaggi intermedi di Docker. Reinstalla tutte le dipendenze del progetto ogni volta.
erewok,

Utilizzare /etc/bashrcper Redhat, anziché /etc/bash.bashrccome indicato sopra (per Ubuntu)
Jordan Gee,

Ho usato /root/.bashrc per centos.
schmudu,

RUN echo "source /yourscript.bash" >> /etc/bash.bashrc fa il trucco. se stai usando ros all'interno della finestra mobile e vuoi impostare l'ambiente, questo è quello che dovresti fare
user27221

17

Secondo https://docs.docker.com/engine/reference/builder/#run la shell [Linux] predefinita RUNè /bin/sh -c. Sembra che ti aspetti bashismi, quindi dovresti usare il "exec form" RUNper specificare la tua shell.

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

Altrimenti, usando la "forma shell" di RUN e specificando una shell diversa si ottengono shell nidificate.

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

Se hai più di 1 comando che necessita di una shell diversa, dovresti leggere https://docs.docker.com/engine/reference/builder/#shell e cambiare la shell predefinita posizionandola prima dei comandi RUN:

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

Infine, se hai inserito qualcosa nel .bashrcfile dell'utente root di cui hai bisogno, puoi aggiungere il -lflag al comando SHELLo RUNper renderlo una shell di accesso e assicurarti che venga fornito.

Nota: ho intenzionalmente ignorato il fatto che sia inutile generare uno script come unico comando in un RUN.


1
SHELL ["/bin/sh", "-c", "-l"]quindi
genera

1
@MortenB, ma hai specificato (digitato?) /bin/sh Che non risolverà il problema di non utilizzare bash. Inoltre, quando lo fai docker buildè improbabile che ci sia qualcosa di utile nel .bashrc dell'utente root di cui hai bisogno. Ma, se hai inserito qualcosa prima nel Dockerfile (come forse a JAVA_HOME, allora sì. Metterò un messaggio al riguardo nella mia risposta.
Bruno Bronosky,

Ci scusiamo per l'errore di battitura, sto usando pyenv che ha bisogno di source ~ / .bashrc per impostare i percorsi per la corretta versione di Python nelle mie immagini di base. questo mi fa usare qualunque base linux ci sia e con due righe aggiungere qualsiasi versione su Python. Come python 3.7 su ubuntu16.04 dove base python è 3.5.2
MortenB

11

Secondo la documentazione Docker

Per usare una shell diversa, diversa da '/ bin / sh', usa il modulo exec passando nella shell desiderata. Per esempio,

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

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


Questa è la risposta giusta EFFETTIVA. L'autore della risposta selezionata stackoverflow.com/a/25086628/117471 sembra aver letto solo il primo esempio nella documentazione a cui ci si collega. Non sembrano aver letto il prossimo paragrafo che è quello che hai citato.
Bruno Bronosky,

4

Se hai a SHELLdisposizione, dovresti scegliere questa risposta : non usare quella accettata, che ti costringe a mettere il resto del file dock in un comando per questo commento .

Se stai usando una vecchia versione Docker e non hai accesso a SHELL, funzionerà finché non avrai bisogno di nulla .bashrc(che è un caso raro in Dockerfiles):

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

Nota che -iè necessario per far leggere bash al file rc.


3

Potresti voler correre bash -vper vedere cosa proviene.

Vorrei fare quanto segue invece di giocare con i collegamenti simbolici:

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


3

Ho anche avuto problemi nell'esecuzione sourcein un file Docker

Questo funziona perfettamente per la costruzione del container DockOS CentOS 6.6, ma ha causato problemi nei container Debian

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

È così che l'ho affrontato, potrebbe non essere un modo elegante, ma questo è ciò che ha funzionato per me

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

2

Questo potrebbe accadere perché sourceè un built-in per bash piuttosto che un binario da qualche parte sul filesystem. La tua intenzione per lo script che stai cercando di modificare in seguito il contenitore?


1
Lo script aggiorna il contenitore, ma ad essere sincero stavo cercando di fare qualcosa che non aveva senso, quindi ho aggirato il problema.
Hugo Rodger-Brown,

1

Ho finito per mettere la mia roba env .profilee mutato SHELLqualcosa del genere

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" deve essere l'ultimo argomento (prima che il comando sia eseguito)
peterk,

0

Se stai solo cercando di usare pip per installare qualcosa nel virtualenv, puoi modificare il PATH env per cercare prima nella cartella bin del virtualenv

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

Quindi tutti i pip installcomandi che seguono nel Dockerfile troveranno prima / path / to / venv / bin / pip e lo useranno, che verrà installato in quel virtualenv e non nel sistema Python.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.