Manipulating Docker Containers
Recently Jessica Kerr joined us to show us how Docker can help us quickly try out new programming languages and tools, without having to set up and troubleshoot a new development environment. Today she returns to demonstrate how we can manage and edit project files directly on our workstation, while still enjoying the benefits of Docker for running and testing the code. Enjoy!
Video transcript & code
Manipulating containers in Docker
docker run -it --rm elixir
The other day, we ran a docker container using elixir's published image in order to try out the programming language.
Running the container gave us access to an interactive Elixir prompt, without installing Elixir on our laptop.
Docker operates a mini pretend computer that has Elixir installed. What if we want to do something on that computer besides running Elixir interactively?
fastest way: run the container with 'bash' instead of iex
We could execute anything we want if only we had a shell prompt instead of an iex prompt.
When we run a container based on the elixir image, that image comes with a default command to run. It is iex.
docker run -it --rm elixir
If we add an argument, that represents the command to run instead of the default one.
We can make it say hello.
docker run -it --rm elixir echo "hello"
But that wasn't very useful.
Let's run a shell instead.
> docker run -it --rm elixir zsh C:\Program Files\Docker\Docker\Resources\bin\docker.exe: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "exec: \"zsh\": executable file not found in $PATH": unknown.
Well that didn't work. A tricky part about running commands in containers based on other people's images is that you don't know what's available in that mini computer.
Most containers are Linux based, though. And most Linux containers have at least /bin/sh. That's a good start.
Great! We're in! We are inside the computer and we can do whatever we want!
We are in the root directory
We are the root user
> docker run -it --rm elixir /bin/sh # pwd / # ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var # whoami root #
Do we have elixir?
# which elixir /usr/local/bin/elixir
every container gets its own filesystem
Let's use elixir to run an elixir script.
We can create a file in here.
echo 'IO.puts("hello, jessitron")' > hello.exs
Here it is, in the root directory.
# ls bin boot dev etc hello.exs home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
This container has a whole filesystem of its own. So I'm not messing up my root directory. In a container, you can do what you want, and it'll be gone in the morning (or in this case, since I ran with --rm, when I exit this shell).
we can see that the filesystem has changed in that container
While the container is still running, check this out.
We can go to another terminal and list running containers
> docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES fa9d44541c0f elixir "/bin/sh" 6 hours ago Up 8 minutes naughty_khayyam
Then we can take the container ID and we can ask Docker what changes I've made to the filesystem since that container was created.
> docker diff fa9d44541c0f A /hello.exs
Wow, it says one file has been added. It is called hello.exs, in the root directory. That's neat.
we can run the file with elixir
Let's run the file with Elixir. Over in the Docker container, we have the 'elixir' executable.
# elixir hello.exs hello, jessitron
we can edit the file
Next, let's change the file.
# vi hello.exs /bin/sh: 4: vi: not found
oh no! No vi! You never know what is installed or not in an image that someone else made.
What editors DO we have?
# sensible-editor update-alternatives: error: no alternatives for editor /usr/bin/sensible-editor: 25: /usr/bin/sensible-editor: editor: not found /usr/bin/sensible-editor: 28: /usr/bin/sensible-editor: nano: not found /usr/bin/sensible-editor: 31: /usr/bin/sensible-editor: nano-tiny: not found /usr/bin/sensible-editor: 34: /usr/bin/sensible-editor: vi: not found Couldn't find an editor! Set the $EDITOR environment variable to your desired editor.
Sad day! No editors!
Okay. We could install one. But let's not.
We could copy the file to our laptop's filesystem and edit it there.
Docker has a way to do this.
We say 'docker cp' and then give it the container id or name, colon, path to the file we want to copy.
Then the destination, which is my current directory on my laptop's filesystem.
> docker cp 45ecbd48e0bf:/hello.exs . > ls Directory: C:\Users\jessi\Dropbox\avdi\blistered-tomatoes Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 12/20/2019 14:49 28 hello.exs
There it is! Now we can use our favorite editor to change it.
Let's print more of the same thing.
IO.puts(String.duplicate("hello, jessitron", 3))
I want to run this in the docker container.
# cat hello.exs IO.puts("hello, jessitron")
Oh, but I don't have the changes.
We could copy it back and forth all day, but let's not.
we can mount a local directory
What we want is for the container to have access to this part of our local filesystem. We can do that; we can mount a directory on our laptop filesystem into the container's filesystem. The trick is, we can only do that at the time we run the container.
Let's exit this container. Goodbye, old hello.exs.
# exit jessitron >
Let's a run a fresh container and mount our current directory into a directory inside the container.
We use the option '--mount' which takes a complicated stringD specifying the details of what and how.
First we have to tell it what kind of mount. Trust me, we want type=bind for this. Then a comma.
Then we tell it where the files are in real life. We have to give it an absolute path, or it'll complain. In my shell, I can embed a call to 'pwd' to get the current directory. Then a comma.
Then we tell it where to mount this directory inside the container's filesystem. I'm gonna pick '/src'.
> docker run -it --rm --mount type=bind,source=$(pwd),destination=/src elixir /bin/sh
Here we are in the fresh container.
This container has a /src directory, and wow, it has my file in it!
# cd /src # ls hello.exs # cat hello.exs IO.puts(String.duplicate("hello, jessitron", 3))
It has the right content!
Let's run it!
# elixir hello.exs hello, jessitronhello, jessitronhello, jessitron
Well, it's different. But it could be more different.
How about adding some newlines.
IO.puts(String.duplicate("hello, jessitron\n", 3))
Run it again!
# elixir hello.exs hello, jessitron hello, jessitron hello, jessitron
Sure enough, we can edit it on our laptop and run it in the container.
Exit the container, and the file remains.
When we exit the container, elixir is gone, but our file remains.
# exit jessitron > ls Directory: C:\Users\jessi\Dropbox\Avdi\blistered-tomatoes Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 12/20/2019 21:04 51 hello.exs
Now you know:
- How to run whatever command you want in a container.
- How to see changes to a container's filesystem.
- How to copy files from a container to your local filesystem.
- How to mount a local directory into a container's filesystem.
You are now prepared to use programming languages and tools that you never install on your own laptop. Happy hacking.