210

In this official document, it can run command in a yaml config file:

https://kubernetes.io/docs/tasks/configure-pod-container/

apiVersion: v1
kind: Pod
metadata:
  name: hello-world
spec:  # specification of the pod’s contents
  restartPolicy: Never
  containers:
  - name: hello
    image: "ubuntu:14.04"
    env:
    - name: MESSAGE
      value: "hello world"
    command: ["/bin/sh","-c"]
    args: ["/bin/echo \"${MESSAGE}\""]

If I want to run more than one command, how to do?

Steven K7FAQ
  • 798
  • 1
  • 9
  • 17
scho
  • 3,275
  • 6
  • 19
  • 30

10 Answers10

279
command: ["/bin/sh","-c"]
args: ["command one; command two && command three"]

Explanation: The command ["/bin/sh", "-c"] says "run a shell, and execute the following instructions". The args are then passed as commands to the shell. In shell scripting a semicolon separates commands, and && conditionally runs the following command if the first succeed. In the above example, it always runs command one followed by command two, and only runs command three if command two succeeded.

Alternative: In many cases, some of the commands you want to run are probably setting up the final command to run. In this case, building your own Dockerfile is the way to go. Look at the RUN directive in particular.

Tim Allclair
  • 7,347
  • 2
  • 27
  • 25
  • 3
    Yes, very valid, however, I think there are also good use cases to extend `command` as it overrides the Dockerfile's `Entrypoint` ;) – Michael Hausenblas Nov 24 '15 at 08:18
  • 1
    Any idea on how to do this with container lifecycle? It has no args – aclowkay Jul 09 '18 at 11:23
  • 1
    @aclokay you can just specify the arguments as additional command strings. The separation between command & args in the Container is just to make overriding the arguments easier. They are functionally equivalent. – Tim Allclair Jul 09 '18 at 18:18
  • what -c does here? – Abdul Dec 07 '19 at 03:36
  • 2
    @Abdul it means run the script provided as an argument, rather than starting an interactive shell or loading the script from a file. – Tim Allclair Dec 09 '19 at 23:41
  • if you keep `&` instead of `&&` that means, it's not compulsory that the first command must be succeed.if first command is not succeed then it will go for the next, if you use `&` like this: `args: ["command one; command two & command three"]` – Yogi Ghorecha Feb 13 '20 at 12:27
  • 2
    This makes no sense to me... Isn't `-c` an argument? Then why the heck would you put it inside `command` and not `args`? Then why not put everything inside `command` since that means that `command` does not have to contain only the executable but it can also add arguments... why bother separating the two? Why wouldn't `command: ["/bin/sh", "-c", "command one; command two && command three""]` work exactly like your example? This would make sense if you had to write `command: "/bin/sh"` and then `args: ["-c", "..."]` – GACy20 Feb 04 '21 at 16:17
  • Why are command and args of list type instead of string. – aandis Nov 27 '21 at 03:56
  • @YogiGhorecha In sh/bash/zsh a single `&` means to run that command in the background. You are probably thinking of `;` – MEMark Nov 29 '21 at 13:41
178

My preference is to multiline the args, this is simplest and easiest to read. Also, the script can be changed without affecting the image, just need to restart the pod. For example, for a mysql dump, the container spec could be something like this:

containers:
  - name: mysqldump
    image: mysql
    command: ["/bin/sh", "-c"]
    args:
      - echo starting;
        ls -la /backups;
        mysqldump --host=... -r /backups/file.sql db_name;
        ls -la /backups;
        echo done;
    volumeMounts:
      - ...

The reason this works is that yaml actually concatenates all the lines after the "-" into one, and sh runs one long string "echo starting; ls... ; echo done;".

Oliver
  • 27,510
  • 9
  • 72
  • 103
  • 1
    Nice, but when you request an edit with kubectl, it will be in one line again. :) – sekrett Jul 23 '18 at 10:04
  • @sekrett oh no ! :( – aclowkay Oct 02 '18 at 12:44
  • 4
    This worked quite nicely - the key is the semicolon on each line. This is a particularly good solution when the commands are many and would be multiline with the solution above. Makes git diff a breeze – kellyfj Feb 12 '19 at 13:31
  • This is what I was looking for. using the environment variable as arguments with this solution works nicely. – Jingpeng Wu Feb 15 '19 at 15:18
  • 1
    +1 Beautiful, plus multi-line commands work perfectly: `command: ['/bin/bash', '-c']` `args:` `- exec &> /path/to/redirected/program.output;` ` python /program.py\` ` --key1=val1\` ` --key2=val2\` ` --key3=val3` – nelsonspbr Aug 27 '19 at 14:31
  • way cleaner and it works better, thanks for the idea. – lauksas Sep 28 '21 at 10:44
  • But what if one command is so long (many parameters) that for formatting purposes one wants to break that into multiple lines using `\`? This seems to fail for us and the next line after `\` is interpreted as new command instead of as a continuation. – Spenhouet Jan 05 '23 at 10:48
  • @Spenhouet Long scripts should instead be refactored into a `.sh` file and put in the image. If you're using a third-party image, create a custom image that has `FROM third-party-image` and just add the `.sh` there. – Oliver Jan 06 '23 at 18:43
73

If you're willing to use a Volume and a ConfigMap, you can mount ConfigMap data as a script, and then run that script:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
data:
  entrypoint.sh: |-
    #!/bin/bash
    echo "Do this"

    echo "Do that"
---
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: my-container
    image: "ubuntu:14.04"
    command:
    - /bin/entrypoint.sh
    volumeMounts:
    - name: configmap-volume
      mountPath: /bin/entrypoint.sh
      readOnly: true
      subPath: entrypoint.sh
  volumes:
  - name: configmap-volume
    configMap:
      defaultMode: 0700
      name: my-configmap

This cleans up your pod spec a little and allows for more complex scripting.

$ kubectl logs my-pod
Do this
Do that
dhulihan
  • 11,053
  • 9
  • 40
  • 45
  • 1
    Very cool, but I think it is simpler to have the script inline, just use multiline syntax. I show this in a separate answer. – Oliver Apr 12 '18 at 16:28
  • What about when I need to pass double quotes. For example imagine this command: printf '%s @%s\n' "$(echo 'user')" "$(echo 'host')" – L3K0V Jul 08 '19 at 14:19
  • This is the most flexible solution – orkenstein Nov 24 '21 at 16:36
55

If you want to avoid concatenating all commands into a single command with ; or && you can also get true multi-line scripts using a heredoc:

command: 
 - sh
 - "-c"
 - |
   /bin/bash <<'EOF'

   # Normal script content possible here
   echo "Hello world"
   ls -l
   exit 123

   EOF

This is handy for running existing bash scripts, but has the downside of requiring both an inner and an outer shell instance for setting up the heredoc.

bluenote10
  • 23,414
  • 14
  • 122
  • 178
27

I am not sure if the question is still active but due to the fact that I did not find the solution in the above answers I decided to write it down.

I use the following approach:

readinessProbe:
  exec:
    command:
    - sh
    - -c
    - |
      command1
      command2 && command3

I know my example is related to readinessProbe, livenessProbe, etc. but suspect the same case is for the container commands. This provides flexibility as it mirrors a standard script writing in Bash.

Dharman
  • 30,962
  • 25
  • 85
  • 135
tmetodie
  • 559
  • 6
  • 6
13

IMHO the best option is to use YAML's native block scalars. Specifically in this case, the folded style block.

By invoking sh -c you can pass arguments to your container as commands, but if you want to elegantly separate them with newlines, you'd want to use the folded style block, so that YAML will know to convert newlines to whitespaces, effectively concatenating the commands.

A full working example:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  containers:
  - name: busy
    image: busybox:1.28
    command: ["/bin/sh", "-c"]
    args:
    - >
      command_1 &&
      command_2 &&
      ... 
      command_n
piscesgeek
  • 200
  • 1
  • 6
  • But then if you go to edit the deployment yaml, it will be in one line, unreadable again – WurmD Jun 29 '21 at 10:08
  • 6
    Editing a live resource with `kubectl edit` and the likes is an anti-pattern and a bad idea anyway. Your running configuration should always match what's written on your manifests. That's what declarative configuration and version control systems are for. – piscesgeek Jul 01 '21 at 12:26
11

Here is my successful run

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - command:
    - /bin/sh
    - -c
    - |
      echo "running below scripts"
      i=0; 
      while true; 
      do 
        echo "$i: $(date)"; 
        i=$((i+1)); 
        sleep 1; 
      done
    name: busybox
    image: busybox
brajesh jaishwal
  • 322
  • 2
  • 12
9

Here is another way to run multi line commands.

apiVersion: batch/v1
kind: Job
metadata:
  name: multiline
spec:
  template:
    spec:
      containers:
      - command:
        - /bin/bash
        - -exc
        - |
          set +x
          echo "running below scripts"
          if [[ -f "if-condition.sh" ]]; then
            echo "Running if success"
          else
            echo "Running if failed"
          fi
        name: ubuntu
        image: ubuntu
      restartPolicy: Never
  backoffLimit: 1
WBAR
  • 4,924
  • 7
  • 47
  • 81
8

Here is one more way to do it, with output logging.

apiVersion: v1
kind: Pod
metadata:
  labels:
    type: test
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
      - name: log-vol
        mountPath: /var/mylog
    command:
        - /bin/sh
        - -c
        - >
            i=0;
            while [ $i -lt 100 ];
            do
             echo "hello $i";
             echo "$i :  $(date)" >> /var/mylog/1.log;
             echo "$(date)" >> /var/mylog/2.log;
             i=$((i+1));
             sleep 1;
            done

  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
    - name: log-vol
      emptyDir: {}
sun2
  • 1,118
  • 1
  • 13
  • 17
1

Just to bring another possible option, secrets can be used as they are presented to the pod as volumes:

Secret example:

apiVersion: v1
kind: Secret 
metadata:
  name: secret-script
type: Opaque
data:
  script_text: <<your script in b64>>

Yaml extract:

....
containers:
    - name: container-name
      image: image-name
      command: ["/bin/bash", "/your_script.sh"]
      volumeMounts:
        - name: vsecret-script
          mountPath: /your_script.sh
          subPath: script_text
....
  volumes:
    - name: vsecret-script
      secret:
        secretName: secret-script

I know many will argue this is not what secrets must be used for, but it is an option.

nighter
  • 125
  • 1
  • 2
  • 10