Docker is a computer program build to allow an easier process of creating and running applications by using containers. Have you often used Docker? If so you may have come across a shell message after logging into a docker container with an intention to edit a text file.
However, don't worry as Senior Software Engineer Maciek Opała is here with possible solutions! Check them out and we hope they help you with your editing needs.
'bash: <EDITOR_NAME>: command not found'— if you’ve ever encountered this shell message after logging into a docker container with an intention to edit a text file, this is a post you should read.
What’s the problem?
Docker is meant to be lightweight (doing one job and doing it well), hence docker containers are trimmed to a bare minimum — they have only necessary packages installed to play the required role in a given project ecosystem. From this point of view, having any editor installed is pointless and introduces needless complication. So if you prepared a 'Dockerfile', built an image and after running a container you need to edit a file you may get surprised:
~/ docker run -it openjdk:11 bash
root@d0fb3a0b527c:/# vi Lol.java
bash: vi: command not found
root@d0fb3a0b527c:/#
What are the possible solutions?
#1 Use volume
Let’s use the following 'Dockerfile':
FROM openjdk:11
WORKDIR "/app"
Now, build an image with:
docker build -t lol .
And finally run the container with a 'volume' attached (a 'volume' can be also created with a 'docker volume create' command):
docker run --rm -it --name=lol -v $PWD/app-vol:/app lol bash
'$PWD/app-vol' folder will be created automatically if it does not exist. Now if you try to list all the files in the '/app' directory you will get an empty result:
~/ docker run --rm -it --name=lol -v $PWD/app-vol:/app lol bash
root@4b72fbabb0af:/app# ls
Navigate to the '$PWD/app-vol' directory from another terminal and create a 'Lol.java' file. If you try to list files once again in the container being run you’ll see that newly-created 'Lol.java' file is there:
root@4b72fbabb0af:/app# ls
Lol.java
root@4b72fbabb0af:/app# cat Lol.java
public class Lol {
}
root@4b72fbabb0af:/app#
As you can see 'cat' command works, so you can at least view the file’s content.
#2 Install the editor
If using a 'volume' is not an option you can install the editor you need to use in a running container. Run the container first (this time mounting a 'volume' is not necessary):
docker run --rm -it --name=lol lol bash
And then install the editor:
root@4b72fbabb0af:/app# apt-get update
root@4b72fbabb0af:/app# apt-get -y install vim
Installing a package in a running container is something that should be done incidentally. If you do it repeatedly, it’s a better idea to add the required package to the 'Dockerfile':
FROM openjdk:11
RUN ["apt-get", "update"]
RUN ["apt-get", "-y", "install", "vim"]
WORKDIR "/app"
It seems that vim-tiny is a light-weight alternative, hence a better choice for an editor in a docker container.
#3 Copy file into a running docker container
Let’s run a container with no editor installed ('Dockerfile' from #1):
docker run --rm -it --name=lol lol bash
(again, no 'volume' needed). If you try to 'ls' files in '/app' folder you’ll get an empty result. This time we will use docker tools to copy the file to the running container. So, on the host machine create the 'Lol.java' file and use the following command to copy the file:
docker cp Lol.java lol:/app
where 'lol' represents the container name. Instead of the name of the container its 'ID' may be also used when copying a file. Files cannot be copied directly between containers. So, if there’s a need to copy a file from one container to an another one, the host machine must be involved.
Another, quite similar option, is to use the 'docker exec' command combined with 'cat'. The following command will also copy the 'Lol.java' file to the running container:
docker exec -i lol sh -c 'cat > /app/Lol.java' < Lol.java
Where '/app/Lol.java' represents a file in a docker container whereas 'Lol.java' is an existing file on the host.
#4 Use linux tools
No favourite (or even any) editor installed on the docker container? No problem! Other linux tools like 'sed', 'awk', 'echo', 'cat', 'cut' are on board and will come to the rescue. With some of them like 'sed' or 'awk' you can edit a file in place. Other, like 'echo', 'cat', 'cut' combined with powerful stream redirection can be used to create and then edit files. As you’ve already seen in the previous examples these tools can be combined with the 'docker exec' command which makes them even more robust.
#5 Use vim (or other editor) remote
IMPORTANT: this idea is bad for many reasons (like running multiple processes in a docker container or enabling others to ssh into a running container via exposed port number 22). I’m showing it rather as a curiosity than something you should use in a day-to-day work. Let’s have a look a the 'Dockerfile', since it has changed a bit:
FROM openjdk:11
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "openssh-server"]
RUN mkdir /var/run/sshd
RUN echo 'root:lollol0' | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN ["/etc/init.d/ssh", "start"]
EXPOSE 22
WORKDIR "/app"
CMD ["/usr/sbin/sshd", "-D"]
This time, since 'scp' will be used for remote edit, we need to install 'openssh-server', expose a port, and finally start it. After building the container with the following command:
docker build -t lol .
run it with the following command:
docker run --rm -p 2222:22 -d --name=lol lol
Now, when the container is running you can edit the 'Lol.java' file with the following command:
vim scp://root@localhost:2222//app/Lol.java
After connect confirmation and entering the password 'vi' opens and the file can be edited. Because of this issue, run ':set bt=acwrite' in 'vi' screen and go ahead with file edition. After you finished, save and exit 'vi', confirm with root’s password and you’re done. Now, run:
docker exec -it lol cat /app/Lol.java
to confirm that the file was in fact created and saved.
Why do I need this?
Actually, you do not, Docker containers are meant to be immutable units of work, devoted to running a single, particular process. Images should be built and run without any further intervention. What’s more, when you edit a file in a running docker container you need to ensure that all the processes that depend on the edited file have been notified about the change. If they’re not configured for redeployment on a configuration change, they need to be restarted manually.
Editing files in a docker container might be useful only during development. When you don’t want or even need to build an image, run it and verify it the change introduced has taken the desired effect every single time you add or remove something in 'Dockerfile'. This way you can save some time, but after it’s done, the redundant packages added to an image should be removed.
This article was written by Maciek Opała and posted originally on SoftwareMill Blog.