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

Ho un Dockerfile che sto mettendo insieme per installare un ambiente vanilla python (nel quale 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 compilazione va bene 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 in quella directory (solo per verificare che i passaggi precedenti siano stati eseguiti), 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 semplicemente a eseguire il comando source ottengo lo stesso errore ‘non trovato’ come sopra. Se eseguo una sessione di shell intertriggers, 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 e accedere felicemente a workon , mkvirtualenv ecc.

Ho fatto alcuni scavi e inizialmente sembrava che il problema potesse risiedere nella differenza tra bash come shell di login di Ubuntu e dash come la shell di sistema di Ubuntu, trattino che non supportavano il comando source .

Tuttavia, la risposta a questo sembra essere quella di usare “.” invece di source , ma questo fa solo saltare il runtime di Docker con un’eccezione di panico go.

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

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

Risposta originale

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

Questo dovrebbe funzionare per ogni immagine della base mobile di Ubuntu. Generalmente aggiungo questa linea per ogni Dockerfile che scrivo.

Modifica di uno spettatore interessato

Se vuoi ottenere l’effetto di “usa bash invece di sh in questo intero Dockerfile”, senza alterare e possibilmente danneggiando * il sistema operativo all’interno del contenitore, puoi semplicemente dire a Docker la tua intenzione . Questo è fatto in questo modo:

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

* Il ansible danno è che molti script in Linux (su una nuova installazione di Ubuntu grep -rHInE '/bin/sh' / restituisce oltre 2700 risultati) si aspettano una shell POSIX completa su /bin/sh . La shell bash non è solo POSIX più builtins extra. Esistono build (e altro) che si comportano in modo completamente diverso da quelli di POSIX. Suppongo COMPLETAMENTE di evitare POSIX (e l’errore che 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 una corretta shebang nella tua sceneggiatura. Non estraendo la shell POSIX da sotto l’intero sistema operativo. (A meno che non abbiate il tempo di verificare tutti gli script 2700 più forniti con Linux più tutti quelli contenuti nei pacchetti che installate.)

Maggiori dettagli in questa risposta qui sotto. https://stackoverflow.com/a/45087082/117471

Ho avuto lo stesso problema e per eseguire l’installazione di 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.

Il modo più semplice è utilizzare l’operatore punto al posto della sorgente, che è l’equivalente sh del comando source bash:

Invece di:

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

Uso:

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

Controlla il comando SHELL . La shell predefinita su Linux è [“/ bin / sh”, “-c”]

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

Puoi cambiare la shell di default usando SHELL che cambia la shell usata per le istruzioni RUN successive nel 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 : puoi anche aggiungere --login opzione --login che avvia una shell di login. Ciò significa che ~/.bachrc per esempio, dovrebbe essere letto e non è necessario che lo si ~/.bachrc esplicitamente prima del comando

Basandomi sulle risposte di questa pagina, vorrei aggiungere che devi essere consapevole che ogni istruzione RUN viene eseguita indipendentemente dagli altri con /bin/sh -c e quindi non otterrà alcun ambiente vars che verrebbe normalmente estratto in shell di login.

Il modo migliore che ho trovato finora è quello di aggiungere lo script a /etc/bash.bashrc e quindi richiamare 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, è ansible installare e configurare virtualenvwrapper, creare l’ambiente virtuale, triggersrlo quando si utilizza un accesso bash e quindi installare i moduli python in questo ambiente:

 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 viene generato quando.

Se usi Docker 1.12 o successivo, usa SHELL !

Risposta breve:

generale:

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

per python 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 di default utilizzata per la forma shell dei 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 Dockerfile.

L’istruzione SHELL è particolarmente utile su Windows in cui esistono 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 sostituisce 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 della shell di esse viene utilizzata in un Dockerfile: RUN, CMD e ENTRYPOINT.

L’esempio seguente è un modello comune trovato su Windows che può essere ottimizzato 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 ragioni. Innanzitutto, viene invocato un comando del comando cmd.exe non necessario (noto anche come shell). In secondo luogo, ogni istruzione RUN nel modulo shell richiede un comando PowerShell supplementare che precede il comando.

Per rendere questo più efficiente, è ansible utilizzare uno dei due meccanismi. Uno è quello di utilizzare il modulo JSON del comando RUN come:

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

Mentre il modulo JSON non è ambiguo e non usa il cmd.exe non necessario, richiede più verbosità attraverso la doppia citazione e l’escaping. Il meccanismo alternativo consiste nell’utilizzare l’istruzione SHELL e la forma della shell, creando una syntax più naturale per gli utenti di Windows, specialmente se combinata con la direttiva escape parser:

 # 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 utilizzata per modificare il modo in cui una shell funziona. Ad esempio, utilizzando SHELL cmd / S / C / V: ON | OFF su Windows, è ansible modificare la semantica di espansione delle variabili di ambiente ritardate.

L’istruzione SHELL può anche essere utilizzata su Linux se è richiesta una shell alternativa come zsh, csh, tcsh e altri.

La funzione SHELL è stata aggiunta a Docker 1.12.

Secondo la documentazione di Docker

Per usare una shell diversa, diversa da ‘/ bin / sh’, usa il modulo exec che passa nella shell desiderata. Per esempio,

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

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

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

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

Altrimenti, usando la “shell form” 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 un comando che richiede una shell diversa, leggi https://docs.docker.com/engine/reference/builder/#shell e modifica la shell predefinita posizionandola prima dei tuoi comandi RUN:

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

Infine, se hai inserito qualcosa nel file .bashrc dell’utente root di cui hai bisogno, puoi aggiungere il flag -l al comando SHELL o RUN per renderlo una shell di login e assicurarti che venga acquisito.

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

Ho anche avuto problemi con l’esecuzione del source in un Dockerfile

Questo funziona perfettamente per la costruzione del contenitore DockOS di CentOS 6.6, ma ha causato problemi nei contenitori 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 

Potresti voler eseguire bash -v per vedere cosa viene originato.

Farei quanto segue invece di giocare con i link simbolici:

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

Questo potrebbe accadere perché source è un built-in in bash piuttosto che un binario da qualche parte nel filesystem. La tua intenzione per la sceneggiatura che stai cercando di alterare il contenitore in seguito?

Se stai solo provando a usare pip per installare qualcosa nel virtualenv, puoi modificare l’ambiente PATH per cercare prima nella cartella bin del virtualenv

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

Quindi qualsiasi comando di pip install che segue nel Dockerfile troverà prima / path / to / venv / bin / pip e userà quello, che verrà installato in quel virtualenv e non nel python di sistema.

Ho finito per mettere le mie cose di .profile in .profile e mutato SHELL qualcosa 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