In Progress
Unit 1, Lesson 1
In Progress

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.

IO.puts("hello, jessitron")

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

Do we have elixir?

# which 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.

code hello.exs

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
# 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.