blog post image
Andrew Lock avatar

Andrew Lock

~7 min read

Handy Docker commands for local development - Part 1

This post includes a grab-bag of Docker commands that I always find myself having to Google. Now I can just bookmark my own post and have them all at my finger tips! I use Docker locally inside a Linux VM rather than using Docker for Windows, so they all apply to that setup. Your mileage may vary - I imagine they work in Docker for Windows but I haven't checked.

I've split the list over 2 posts, but this is what you can expect:

Don't require sudo to execute Docker commands

By default, when you first install Docker on a Linux machine, you might find that you get permission errors when you try and run any Docker commands:

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.30/images/json: dial unix /var/run/docker.sock: connect: permission denied

To get round this, you need to run all your commands with sudo, e.g. sudo docker images. There's good security reasons for requiring sudo, but when I'm just running it locally in a VM, I'm not too concerned about them. To get round it, and avoid having to type sudo every time, you can add your current user to the docker group, which effectively gives it root access.

sudo usermod -aG docker $USER

After running the command, just log out of your VM (or SSH session) and log back in, and you should be able to run your docker commands without needing to type sudo for everything.

Examining the file system of a failed Docker build

When you're initially writing a Dockerfile to build your app, you may find that it fails to build for some reason. That could happen for lots of reasons - it could be an error in your code,it could be an error in your Dockerfile invoking the wrong commands, or you might not be copying all the required files into the image for example.

Sometimes you can get obscure errors that leave you unsure what went wrong. When that happens, you might want to inspect the filesystem when the build failed, to see what went wrong. You can do this by running one of the previous image layers for your Dockerfile.

When Docker builds an image using a Dockerfile, it does so in layers. Each command in the Dockerfile creates a new layer when it executes. When a command fails, the layer is not created. To view the filesystem of the image when the command failed, we can just run the image that contains all the preceding layers.

Luckily, when you build a Dockerfile, Docker shows you the unique reference for each layer it creates - it's the random strings of numbers and letters like b1f30360c452 in the following example:

Step 1/13 : FROM microsoft/aspnetcore-build:2.0.3 AS builder
 ---> b1f30360c452
Step 2/13 : WORKDIR /sln
 ---> Using cache
 ---> 4dee75249988
Step 3/13 : COPY ./build.sh ./build.cake ./NuGet.config ./aspnetcore-in-docker.sln ./
 ---> fee6f958bf9f
Step 4/13 : RUN ./build.sh -Target=Clean
 ---> Running in ab207cd28503
/usr/bin/env: 'bash\r': No such file or directory

This build failed executing the ./build.sh -Target=Clean command. To view the filesystem we can create a container from the image created by the previous layer, fee6f958bf9f by calling docker run. We can execute a bash shell inside the container, and inspect the contents of the filesystem (or do anything else we like).

docker run --rm -it fee6f958bf9f /bin/bash

The arguments are as follows:

  • --rm - when we exit the container, it will be deleted. this prevents the build up of exited transient images like this.
  • -it - create an "interactive" session. When the docker container starts up, you will be connected to it, rather than it running in the background.
  • fee6f958bf9f - the image layer to run in, taken from our failed output.
  • /bin/bash - The command to execute in the container when it starts. Using /bin/bash creates a shell, so you can execute commands and generally inspect the filesystem.

Note that --rm deletes the container, not the image. A container is basically a running image. Think of the image as a hard drive, it contains all the details on how to run a process, but you need a computer or a VM to actually do anything with the hard drive.

Once you've looked around and figured out the problem, you can quit the bash shell by running exit, which will also kill the container.

This docker run command works if you want to inspect an image during a failed build, but what if your build succeeded, and now you want to check the filesystem rather than running the app it contains? For that you'll need the command in the next section.

Examining the file system of an image with an ENTRYPOINT

Lets say your Docker build succeeded, but for some reason your app isn't starting correctly when you call docker run. You suspect there may be a missing file, and so you want to inspect the filesystem. Unfortunately, the command in the previous section won't work for you. If you try it, you'll probably be greeted with an error that looks like the following:

$ docker run -it --rm myImage /bin/bash
Unhandled Exception: System.FormatException: Value for switch '/bin/bash' is missing.
   at Microsoft.Extensions.Configuration.CommandLine.CommandLineConfigurationProvider.Load(

The problem, is that the Docker image you're running (myImage) in this case, already includes an ENTRYPOINT command in the Dockerfile used to build it. The ENTRYPOINT defines the command to run when the container is started, which for ASP.NET Core apps, typically looks something like the following:

ENTRYPOINT ["dotnet", "MyApp.dll"]

With the previous example, the /bin/bash argument is actually passed as an extra command line argument to the previously-defined entrypoint, so you're actually running dotnet MyApp.dll /bin/bash in the container when it starts up.

In order to override the entrypoint, and to just run the shell directly, you need to use the --entrypoint argument instead. Note that the argument order is different in this case - the image name goes at the end in this command, whereas the shell was at the end in the previous example

$ docker run --rm -it --entrypoint /bin/bash  myImage

You'll now have a shell inside the container that you can use to inspect the contents or run other commands.

Copying files from a Docker container to the host

If you're not particularly au fait with Linux (🙋) then trying to diagnose a problem from inside a shell can be tricky. Sometimes, I'd rather copy a file back from a container and inspect it on the Windows side, using tools I'm familiar with. I typically have folders mapped between my Windows machine and my Linux VM, so I just need to get the file out of the Docker container and into the Linux host.

If you have a running (or stopped) container, you can copy a file from the container to the host with the docker cp command. For example, if you created a container called my-image-app from the myImage container using:

docker run -d --name my-image-app myImage

then you could copy a file from the container to the host using something like the following:

docker cp my-image-app:/app/MyApp.dll /path/on/the/host/MyApp.dll

Copying files from a Docker image to the host

The previous example shows how to copy files from a container, but you can't use this to copy files directly from an image. Unfortunately, to do that you have to create a container from the image and run it.

There's a number of ways you can do this, depending on exactly what state your image is in (e.g. does it have a defined ENTRYPOINT), but I tend to just use the following:

docker run --rm -it --entrypoint cat myImage /app/MyApp.dll > /path/on/the/host/MyApp.dll

This gives me a single command I can run to grab the /app/MyApp.dll file from the image, and write it out to /path/on/the/host/MyApp.dll. It relies on the cat command being available, which it is for the ASP.NET Core base image. If that's not available, you'll essentially have to manually create a container from your image, copy the file across, and then kill the container. For example:

id=$(docker create myImage)
docker cp $id:/app/MyApp.dll /path/on/the/host/MyApp.dll
docker rm -v $id

If the image has a defined ENTRYPOINT you may need to override it in the docker create call:

id=$(docker create --entrypoint / andrewlock/aspnetcore-in-docker)
docker cp $id:/app/MyApp.dll /path/on/the/host/MyApp.dll
docker rm -v $id

That gives you three ways to copy files out of your containers - hopefully at least one of them works for you!

Summary

That's it for this first post of Docker commands. Hope you find them useful!

Andrew Lock | .Net Escapades
Want an email when
there's new posts?