In this tutorial, we’ll learn how to run the source command on an existing running Docker container. Particularly, we’ll explore running the source command using the docker exec without starting a shell session in the container.
2. Problem Statement
Let’s consider that there’s a running Docker container with the name ubuntu. We know that by using the docker exec command, we can run a command inside the container. For example, we can print the directory structure of the container using the ls command:
$ docker exec -ti ubuntu ls
bin dev home lib32 libx32 mnt proc run set-envs.sh sys usr
boot etc lib lib64 media opt root sbin srv tmp var
$ docker exec -ti ubuntu source set-env.sh
OCI runtime exec failed: exec failed: unable to start container process: exec: "source": executable file not found in $PATH: unknown
This might seem puzzling because we’re sure that the source command exists on the container. We can prove its presence by starting an interactive shell in the container and running the source command:
$ docker exec -ti ubuntu bash
root@87be3ef80b11:/# source set-envs.sh
root@87be3ef80b11:/# env | grep API
In the series of commands above, we first start an interactive Bash session on the Docker container ubuntu using the -ti option. The root@87be3ef80b11 prefix on each line shows that we’re currently in the container’s shell.
Then, we run the source command on the script file and then use the env and grep commands to print environment variables with API in its name. This series of steps shows that the source command is indeed working perfectly in the container.
So why does docker exec complain that the source command is not found when we can run it in the container’s shell session? Furthermore, how can we execute the source command using the docker exec?
3. Why Can’t docker exec Find the source Command?
To understand the issue we’re facing, we’ll need to first understand what exactly is the source command, as well as what the docker exec does when given an argument.
3.1. source Is Not an Executable
An executable file in Linux refers to a file that contains a program that we can run. The content of the executable files ranges from shell scripts to compiled machine code that the system can run. For example, date, bash, and pwd are examples of executables in Linux that we can run.
The source command, however, is not an executable. It’s a Bash built-in command that’s synonymous with the Bourne shell’s single dot operator. In other words, there isn’t a file anywhere in the system with the executable name source. Instead, the Bash shell actively interprets the source command and then runs the single dot operator action instead.
We can do a quick demonstration to prove this point. Firstly, we start a Docker container with the Ubuntu Linux image in interactive mode:
$ docker run --rm -ti ubuntu:latest bash
root@3156a3de590d:/# which source
root@3156a3de590d:/# echo $?
In the demonstration above, we use the which command to locate the path of the executable source. With an exit code of 1, we can see that there isn’t an executable with the name source. However, running the source command will still work:
root@3156a3de590d:/# cat <<EOF >script.sh
> echo "Hello from script.sh"
root@3156a3de590d:/# source script.sh
Hello from script.sh
The command above writes a simple script into the script.sh file using the cat command and heredoc. Then, we use the source command to execute the script.sh and clearly show that the source command works.
The reason why this distinction between Bash built-in and an executable is important in the context of the docker exec command.
3.2. docker exec Requires an Executable
The docker exec command treats the first argument as an executable to execute in the container. For example, when we run docker exec container-ubuntu date, it runs the executable date in the container-ubuntu Docker container:
$ docker run --rm ubuntu date
Sat Jan 13 04:49:30 UTC 2024
Similarly, when we run the docker exec container-ubuntu source script.sh, the docker exec tries to locate an executable file in the system with the name source. This will inevitably fail as there’s no such executable file in the container. Hence, the docker exec exits with an error saying that the source executable cannot be located.
4. Using bash as the Executable
The solution is to pass the bash as an executable and then the source as an argument to the bash command. Specifically, we can prefix the source command with the bash -c:
$ docker exec -ti ubuntu bash -c 'source set-envs.sh'
In the command above, we’re instructing the docker exec to run the bash executable. Then, we pass the -c ‘source set-envs.sh’ as the arguments to our bash executable. The -c option instructs the bash to execute whatever follows as command and exit. Since bash is an executable, the docker exec will have no problem locating and running the command.
In this tutorial, we’ve first stated the issue we’ll encounter when we run the source command using the docker exec on an existing container. Then, we learned that the error is because the source command is a Bash built-in and not an executable. Additionally, the docker exec command expects the argument to be an executable, which explains why passing the source command directly to the docker exec command fails.
Finally, we’ve learned that the way to run the source command with docker exec is to prepend the source command with bash -c.