À l'aide de Docker-Compose, comment exécuter plusieurs commandes

500

Je veux faire quelque chose comme ça où je peux exécuter plusieurs commandes dans l'ordre.

db:
  image: postgres
web:
  build: .
  command: python manage.py migrate
  command: python manage.py runserver 0.0.0.0:8000
  volumes:
    - .:/code
  ports:
    - "8000:8000"
  links:
    - db
RustyShackleford
la source

Réponses:

861

Compris, utilisez bash -c.

Exemple:

command: bash -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"

Même exemple dans les multilignes:

command: >
    bash -c "python manage.py migrate
    && python manage.py runserver 0.0.0.0:8000"

Ou:

command: bash -c "
    python manage.py migrate
    && python manage.py runserver 0.0.0.0:8000
  "
RustyShackleford
la source
6
@Pedram Assurez-vous que vous utilisez une image sur laquelle bash est installé. Certaines images peuvent également nécessiter un chemin direct vers bash, par exemple/bin/bash
codemaven
6
Si aucun bash n'est installé, vous pouvez essayer sh -c "votre commande"
Chaoste
Assurez-vous de mettre vos commandes entre guillemets lors du passage à bash et j'ai dû glisser un "sleep 5" pour m'assurer que la base de données était en place, mais cela a fonctionné pour moi.
traday
74
Les images alpines semblent n'avoir aucun bash installé - faites comme @Chaoste recommande et utilisez shplutôt:[sh, -c, "cd /usr/src/app && npm start"]
Florian Loch
1
Peut également être utilisé uniquement ashen alpin :)
Jonathan
160

J'exécute des trucs de pré-démarrage comme des migrations dans un conteneur éphémère séparé, comme ça (notez que le fichier de composition doit être de type '2'):

db:
  image: postgres
web:
  image: app
  command: python manage.py runserver 0.0.0.0:8000
  volumes:
    - .:/code
  ports:
    - "8000:8000"
  links:
    - db
  depends_on:
    - migration
migration:
  build: .
  image: app
  command: python manage.py migrate
  volumes:
    - .:/code
  links:
    - db
  depends_on:
    - db

Cela aide à garder les choses propres et séparées. Deux choses à considérer:

  1. Vous devez vous assurer de la séquence de démarrage correcte (en utilisant depend_on)

  2. vous voulez éviter plusieurs builds ce qui est réalisé en le marquant la première fois en utilisant build et image; vous pouvez vous référer à l'image dans d'autres conteneurs puis

Bjoern Stiel
la source
2
Cela me semble être la meilleure option et j'aimerais l'utiliser. Pouvez-vous élaborer sur votre configuration de marquage pour éviter plusieurs versions? Je préférerais éviter les étapes supplémentaires, donc si cela en a besoin, je pourrais aller plus bash -chaut.
Stavros Korokithakis
3
Dans le yaml ci-dessus, la génération et le balisage se produisent dans la section de migration. Ce n'est pas vraiment évident à première vue, mais docker-compose le balise lorsque vous spécifiez les propriétés de construction ET d'image - la propriété d'image spécifiant la balise pour cette construction. Cela peut ensuite être utilisé par la suite sans déclencher une nouvelle build (si vous regardez sur le Web, vous voyez qu'il n'a pas de build mais seulement une propriété d'image). Voici quelques détails supplémentaires docs.docker.com/compose/compose-file )
Bjoern Stiel
26
Bien que j'aime l'idée de cela, le problème est que depend_on s'assure seulement qu'ils commencent dans cet ordre, pas qu'ils soient prêts dans cet ordre. wait-for-it.sh peut être la solution dont certaines personnes ont besoin.
traday
2
C'est tout à fait correct et un peu dommage que docker-compose ne supporte aucun contrôle à grain fin comme attendre la sortie d'un conteneur ou commencer à écouter sur un port. Mais oui, un script personnalisé résout ce problème, bon point!
Bjoern Stiel
1
Cette réponse donne des informations incorrectes et potentiellement destructrices sur le fonctionnement de depend_on.
antonagestam
96

Je recommande d'utiliser shpar opposition à bashcar il est plus facilement disponible sur la plupart des images basées sur Unix (alpine, etc.).

Voici un exemple docker-compose.yml:

version: '3'

services:
  app:
    build:
      context: .
    command: >
      sh -c "python manage.py wait_for_db &&
             python manage.py migrate &&
             python manage.py runserver 0.0.0.0:8000"

Cela appellera les commandes suivantes dans l'ordre:

  • python manage.py wait_for_db - attendez que la base de données soit prête
  • python manage.py migrate - exécuter toutes les migrations
  • python manage.py runserver 0.0.0.0:8000 - démarrer mon serveur de développement
LondonAppDev
la source
2
Personnellement, c'est ma solution préférée et la plus propre.
BugHunterUK
1
Le mien aussi. Comme le souligne @LondonAppDev, bash n'est pas disponible par défaut dans tous les conteneurs pour optimiser l'espace (par exemple, la plupart des conteneurs construits sur Alpine Linux)
ewilan
2
J'ai dû échapper à la multiligne && avec un \
Andre Van Zuydam
@AndreVanZuydam hmmm c'est étrange, je n'avais pas besoin de faire ça. Avez-vous entouré de citations? Quelle saveur de docker utilisez-vous?
LondonAppDev
2
@oligofren le >est utilisé pour démarrer une entrée multiligne (voir stackoverflow.com/a/3790497/2220370 )
LondonAppDev
40

Cela fonctionne pour moi:

version: '3.1'
services:
  db:
    image: postgres
  web:
    build: .
    command:
      - /bin/bash
      - -c
      - |
        python manage.py migrate
        python manage.py runserver 0.0.0.0:8000

    volumes:
      - .:/code
    ports:
      - "8000:8000"
    links:
      - db

docker-compose essaie de déréférencer les variables avant d' exécuter la commande, donc si vous voulez que bash gère les variables, vous devrez échapper aux signes dollar en les doublant ...

    command:
      - /bin/bash
      - -c
      - |
        var=$$(echo 'foo')
        echo $$var # prints foo

... sinon vous obtiendrez une erreur:

Format d'interpolation non valide pour l'option "commande" dans le service "web":

MatrixManAtYrService
la source
Salut mon pote. J'ai rencontré un problème: `` `arguments non reconnus: / bin / bash -c python3 /usr/local/airflow/__init__.py -C Local -T Windows '' 'la commande dans mon docker-compose.yml est: commande: - / bin / bash - -c - | python3 /usr/local/airflow/__init__.py -C $ {Client} -T $ {Types} Savez-vous comment résoudre ce problème? J'ajoute Client et Types dans mon fichier .env.
Newt
Voici un document pour vous: docs.docker.com/compose/compose-file/#variable-substitution Je pense que ce qui se passe, c'est que votre fichier .env place ces variables dans l'environnement conteneur, mais docker-compose regarde dans votre environnement shell . Essayez plutôt $${Types}et $${Client}. Je pense que cela empêchera Docker Composer d'interpréter ces variables et de rechercher leurs valeurs dans le shell à partir duquel vous appelez Docker-Composer, ce qui signifie qu'elles sont toujours là pour bash pour les déréférencer ( après que Docker a traité votre .envfichier).
MatrixManAtYrService
Merci pour votre commentaire. J'ai fait ce que tu as dit en fait. J'ai donc obtenu le $ (Client) dans les informations d'erreur. J'ai changé la façon de lire les variables d'environnement pour utiliser os.getenv en python, ce qui est plus facile. Merci quand même.
Newt
23

Vous pouvez utiliser le point d'entrée ici. Le point d'entrée dans Docker est exécuté avant la commande tandis que la commande est la commande par défaut qui doit être exécutée au démarrage du conteneur. Ainsi, la plupart des applications comportent généralement une procédure de configuration dans un fichier de point d'entrée et, dans le dernier, elles permettent l'exécution de la commande.

faire un fichier de script shell peut être comme docker-entrypoint.sh(le nom n'a pas d'importance) avec le contenu suivant.

#!/bin/bash
python manage.py migrate
exec "$@"

dans le fichier docker-compose.yml, utilisez-le avec entrypoint: /docker-entrypoint.shet enregistrez la commande en tant que command: python manage.py runserver 0.0.0.0:8000 PS: n'oubliez pas de copier docker-entrypoint.shavec votre code.

Harshad Yeola
la source
Notez que cela s'exécutera également lorsque vous le ferezdocker-compose run service-name ....
thisismydesign
18

Une autre idée:

Si, comme dans ce cas, vous créez le conteneur, placez-y simplement un script de démarrage et exécutez-le avec la commande. Ou montez le script de démarrage en tant que volume.

rweng
la source
Oui, à la fin, j'ai créé un script run.sh: #!/bin/bash \n python manage.py migrate \n python manage.py runserver 0.0.0.0:8000(laid en ligne)
fero
9

* MISE À JOUR *

J'ai pensé que la meilleure façon d'exécuter certaines commandes est d'écrire un Dockerfile personnalisé qui fait tout ce que je veux avant que le CMD officiel ne soit exécuté à partir de l'image.

docker-compose.yaml:

version: '3'

# Can be used as an alternative to VBox/Vagrant
services:

  mongo:
    container_name: mongo
    image: mongo
    build:
      context: .
      dockerfile: deploy/local/Dockerfile.mongo
    ports:
      - "27017:27017"
    volumes:
      - ../.data/mongodb:/data/db

Dockerfile.mongo:

FROM mongo:3.2.12

RUN mkdir -p /fixtures

COPY ./fixtures /fixtures

RUN (mongod --fork --syslog && \
     mongoimport --db wcm-local --collection clients --file /fixtures/clients.json && \
     mongoimport --db wcm-local --collection configs --file /fixtures/configs.json && \
     mongoimport --db wcm-local --collection content --file /fixtures/content.json && \
     mongoimport --db wcm-local --collection licenses --file /fixtures/licenses.json && \
     mongoimport --db wcm-local --collection lists --file /fixtures/lists.json && \
     mongoimport --db wcm-local --collection properties --file /fixtures/properties.json && \
     mongoimport --db wcm-local --collection videos --file /fixtures/videos.json)

C'est probablement la façon la plus propre de le faire.

* ANCIENNE VOIE *

J'ai créé un script shell avec mes commandes. Dans ce cas, je voulais démarrer mongodet exécuter, mongoimportmais appeler mongodvous empêche d'exécuter le reste.

docker-compose.yaml :

version: '3'

services:
  mongo:
    container_name: mongo
    image: mongo:3.2.12
    ports:
      - "27017:27017"
    volumes:
      - ./fixtures:/fixtures
      - ./deploy:/deploy
      - ../.data/mongodb:/data/db
    command: sh /deploy/local/start_mongod.sh

start_mongod.sh :

mongod --fork --syslog && \
mongoimport --db wcm-local --collection clients --file /fixtures/clients.json && \
mongoimport --db wcm-local --collection configs --file /fixtures/configs.json && \
mongoimport --db wcm-local --collection content --file /fixtures/content.json && \
mongoimport --db wcm-local --collection licenses --file /fixtures/licenses.json && \
mongoimport --db wcm-local --collection lists --file /fixtures/lists.json && \
mongoimport --db wcm-local --collection properties --file /fixtures/properties.json && \
mongoimport --db wcm-local --collection videos --file /fixtures/videos.json && \
pkill -f mongod && \
sleep 2 && \
mongod

Donc, cette fourche mongo, fait de la monogimportation puis tue le mongo fourchu qui est détaché, et le redémarre sans se détacher. Je ne sais pas s'il existe un moyen de s'attacher à un processus fourchu, mais cela fonctionne.

REMARQUE: Si vous souhaitez strictement charger des données de base de données initiales, voici la façon de procéder:

mongo_import.sh

#!/bin/bash
# Import from fixtures

# Used in build and docker-compose mongo (different dirs)
DIRECTORY=../deploy/local/mongo_fixtures
if [[ -d "/fixtures" ]]; then
    DIRECTORY=/fixtures
fi
echo ${DIRECTORY}

mongoimport --db wcm-local --collection clients --file ${DIRECTORY}/clients.json && \
mongoimport --db wcm-local --collection configs --file ${DIRECTORY}/configs.json && \
mongoimport --db wcm-local --collection content --file ${DIRECTORY}/content.json && \
mongoimport --db wcm-local --collection licenses --file ${DIRECTORY}/licenses.json && \
mongoimport --db wcm-local --collection lists --file ${DIRECTORY}/lists.json && \
mongoimport --db wcm-local --collection properties --file ${DIRECTORY}/properties.json && \
mongoimport --db wcm-local --collection videos --file ${DIRECTORY}/videos.json

Les fichiers mongo_fixtures / *. json ont été créés via la commande mongoexport.

docker-compose.yaml

version: '3'

services:
  mongo:
    container_name: mongo
    image: mongo:3.2.12
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db:cached
      - ./deploy/local/mongo_fixtures:/fixtures
      - ./deploy/local/mongo_import.sh:/docker-entrypoint-initdb.d/mongo_import.sh


volumes:
  mongo-data:
    driver: local
radtek
la source
5

Si vous devez exécuter plusieurs processus démon, il est suggéré dans la documentation Docker d'utiliser Supervisord en mode non détaché pour que tous les sous-démons soient envoyés vers la sortie standard.

À partir d'une autre question SO, j'ai découvert que vous pouvez rediriger la sortie des processus enfants vers la sortie standard. De cette façon, vous pouvez voir toutes les sorties!

Tim Tisdall
la source
En examinant à nouveau cela, cette réponse semble plus adaptée pour exécuter plusieurs commandes en parallèle plutôt qu'en série.
Tim Tisdall
1

Utilisez un outil tel que l'attendre ou dockerize . Ce sont de petits scripts wrapper que vous pouvez inclure dans l'image de votre application. Ou écrivez votre propre script wrapper pour exécuter des commandes plus spécifiques à l'application. selon: https://docs.docker.com/compose/startup-order/

Eran
la source
0

J'ai rencontré cela en essayant de configurer mon conteneur jenkins pour créer des conteneurs docker en tant qu'utilisateur jenkins.

Je devais toucher le fichier docker.sock dans le Dockerfile car je le lierai plus tard dans le fichier docker-compose. Sauf si je l'ai touché en premier, il n'existait pas encore. Cela a fonctionné pour moi.

Dockerfile:

USER root
RUN apt-get update && \
apt-get -y install apt-transport-https \
ca-certificates \
curl \
software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; 
echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable" && \
apt-get update && \
apt-get -y install docker-ce
RUN groupmod -g 492 docker && \
usermod -aG docker jenkins  && \
touch /var/run/docker.sock && \
chmod 777 /var/run/docker.sock

USER Jenkins

docker-compose.yml:

version: '3.3'
services:
jenkins_pipeline:
    build: .
    ports:
      - "8083:8083"
      - "50083:50080"
    volumes:
        - /root/pipeline/jenkins/mount_point_home:/var/jenkins_home
        - /var/run/docker.sock:/var/run/docker.sock
Jason Anderson
la source
Cela semble être la réponse à une question différente.
kenorb
-7

essayez d'utiliser ";" pour séparer les commandes si vous êtes dans les versions deux, par exemple

command: "sleep 20; echo 'a'"

chanllen
la source