Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
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.
The CMD directive can be used in two ways:
This article will focus on the first case, where we define an executable with 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.
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.
The sh -c command accepts a list of commands and executes them. Moreover, the commands can be separated with the known shell operators:
The sh -c command provides us the means to execute multiple commands.
Let’s create a simple Dockerfile:
FROM ubuntu:latest
CMD echo FIRST COMMAND;echo SECOND COMMAND
Next, we’ll build and run it:
$ sudo docker build -t example1 .
$ sudo docker run -it example1
FIRST COMMAND
SECOND COMMAND
As we can see, we used the shell form of the CMD directive. The two echo commands were effectively executed sequentially.
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:
FROM ubuntu:latest
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
FIRST COMMAND
SECOND COMMAND
We effectively executed the two commands sequentially with the exec form of the CMD directive.
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.
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
test1
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.
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"
test1
/root
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.
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:
version: '2.2'
services:
echoer:
image: ubuntu:latest
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
test1;echo test2;
Now we’ve successfully executed multiple commands with the help of the docker-compose tool.
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:
FROM ubuntu:latest
COPY startup.sh .
CMD ["/bin/bash","-c","./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:
#! /bin/bash
echo $HOME
date
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
/root
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.