Docker containers are processes that perform a task. A task can be one or more commands, executables, or shell scripts. Docker indeed offers many ways to set what a container should run.
As we know, we usually start by setting the ENTRYPOINT and CMD Dockerfile directives. In simple use cases, a single command may be all we need to run when our container starts. But in real-world cases, we may need to create complex configurations with many commands.
In this tutorial, we’ll explore ways to define more than a single command in the CMD directive.
2. The CMD Directive Forms
The CMD directive can be used in two ways:
- To define an executable with its arguments that the container should run. In this case, we can omit the ENTRYPOINT directive.
- To provide additional arguments to the executable defined in the ENTRYPOINT directive
This article will focus on the first case, where we define an executable with the CMD directive.
2.1. The Exec Form of the CMD Directive
In the exec form, we’ll define the command and its arguments as an array of quoted strings:
CMD ["executable", "param1", "param2"]
The exec form invokes only the defined executable, without a shell. Unless we set a shell, like /bin/sh or /bin/bash, to be the executable, the container doesn’t start a shell. This resembles the way the exec command works.
2.2. The Shell Form of the CMD Directive
In contrast, the shell form is closer to what we’d type on a terminal:
CMD executable param1 param2
This will make the container run:
/bin/sh -c executable param1 param2
So the shell form will invoke the sh shell under the hood.
3. Run Multiple Commands With Dockerfile
The sh -c command accepts a list of commands and executes them. Moreover, the commands can be separated with the known shell operators:
- the semicolon (;) operator
- the ambersand (&) operator
- the AND (&&) operator
- the OR (||) operator
- the pipe (|)
The sh -c command provides us the means to execute multiple commands.
3.1. Run Multiple Commands With the Shell Form
Let’s create a simple Dockerfile:
CMD echo FIRST COMMAND;echo SECOND COMMAND
Next, we’ll build and run it:
$ sudo docker build -t example1 .
$ sudo docker run -it example1
As we can see, we used the shell form of the CMD directive. The two echo commands were effectively executed sequentially.
3.2. Run Multiple Commands With the Exec Form
Although the exec form doesn’t start a shell on its own, we can set the /bin/sh -c or the /bin/bash -c commands in the CMD executable position, and invoke the shell:
CMD ["/bin/bash", "-c", "echo FIRST COMMAND;echo SECOND COMMAND"]
Next, we’ll build and run:
$ sudo docker build -t example2 .
$ sudo docker run -it example2
We effectively executed the two commands sequentially with the exec form of the CMD directive.
4. Run Multiple Commands With the docker run Command
The docker run command provides the means to set or override the CMD directive. We can indeed set the command that our container will execute with the COMMAND option of the docker run command. Furthermore, this option allows us to enter multiple commands.
4.1. Run Multiple Commands Without Shell Invocation
Let’s continue the previous example, but this time we’ll create and run the container with the COMMAND option:
$ sudo docker run -it example2 echo -e "\ttest1";echo test2
First, we’ve successfully overridden the CMD directive of the Dockerfile. Second, we managed to execute two commands separated by a semicolon. We should note that we didn’t invoke a shell inside the container.
4.2. Run Multiple Commands With Shell Invocation
We can achieve a similar result with the invocation of a shell:
$ sudo docker run -it example2 /bin/bash -c "echo -e '\ttest1';echo \$HOME"
As a result, we again executed two commands. This time, we used echo \$HOME as the second command. This is to demonstrate the shell substitution that happens in the container. We also escaped the dollar sign of the $HOME shell variable. If we don’t do this, then the HOME shell variable of the host system will be printed.
5. Run Multiple Commands With docker-compose
Apart from the Dockerfile CMD directive and the docker run command, we can execute multiple commands with the docker-compose tool.
Let’s create a pretty simple docker-compose.yaml file:
command: echo -e '\ttest1';echo test2;
Here we defined a service called echoer from the Ubuntu image, and set the command attribute of the service to a list of echo commands separated by a semicolon. We should note that we can also use other operators besides the semicolon.
Next, we’ll build and run our container:
$ sudo docker-compose run echoer
Now we’ve successfully executed multiple commands with the help of the docker-compose tool.
6. Run Multiple Commands With a Shell Script
Sometimes, we need to do more complex processing, instead of chaining a few commands. If this is the case, we can create a shell script that contains all the necessary logic, and copy it to the container’s filesystem. We can use the Dockerfile COPY directive to copy the shell script file to our container’s filesystem.
Let’s create a simple Dockerfile for our image:
COPY startup.sh .
We’ll use the COPY directive to copy the shell script to the container’s filesystem. We’ll execute the script with the CMD directive.
Next, we’ll create the startup.sh shell script:
The above script prints our home directory in the container and the current date. An important note is that we should grant the execute privilege to the shell script on the host machine. This is because the execute privilege will be transferred to our container when we copy the file. Otherwise, the container won’t be able to execute the script when it starts.
Finally, we’ll build the image and run the container:
$ sudo docker build -t example3 .
$ sudo docker run -it example3
Tue Aug 16 08:15:25 UTC 2022
Now we’ve successfully executed our shell script that echoed our home directory and the current date.
In this article, we explored various ways to execute multiple commands on a Docker container. We discussed different approaches using Dockerfile, the docker run command, and the docker-compose run command.
Finally, for more complex processing, we learned how to create a shell script and execute it in our container.