Tag Archives: Debug Docker build

Beginner’s Guide to Docker – Part 3 – Debugging a Docker Build (Continued)

In this post I starting a series on getting started with Docker; here, I’m going to expand to give some tips on how to debug a docker build when even the build itself is failing.

Imagine that you’re trying to debug a docker build script, and you keep getting build errors. The script never completes, and so you can’t attach to the running container (as shown here).

In this case, there are still a few options available. Let’s consider the following build script:

FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["pcm-test.csproj", "pcm-test/"]
RUN dotnet restore "pcm-test/pcm-test.csproj"
COPY . .
WORKDIR "/src/pcm-test"

RUN dotnet build "pcm-test.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "pcm-test.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "pcm-test.dll"]

When I run this (it’s a console app, btw), I get the following build error:

 => ERROR [build 10/10] RUN dotnet build "pcm-test.csproj" -c Release -o /app/build

#18 0.790 Microsoft (R) Build Engine version 17.0.0-preview-21501-01+bbcce1dff for .NET
#18 0.790 Copyright (C) Microsoft Corporation. All rights reserved.
#18 0.790
#18 1.351   Determining projects to restore...
#18 1.623   All projects are up-to-date for restore.
#18 1.703   You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview
#18 2.897 CSC : error CS5001: Program does not contain a static 'Main' method suitable for an entry point

I can absolutely assure you that the program does have a main method.

Debugging

When a build goes wrong, I’ve determined two ways to debug – the first is by simply executing debug commands inside the build, and the second is to reduce the build until it works, and then inspect the image.

Executing Debug Commands

Since you can run any command inside a build file, you can simply do something like this:

RUN ls -lrt

Remember that if you do this, you’ll need to change a couple of settings:

–progress

This can be set to auto (default), plain, and tty.

tty (or interactive terminal) and auto will compress the output; whereas plain will show all the container output (including these kind of debug messages.

–no-cache

Docker tries to be clever, and cache the commands that have already executed; however, for debug statements, this is going to mean that they’ll only ever fire the first time. It tells you when it’s executing these from the cache:

docker build -t pcm-test --no-cache --progress=plain .

Sometimes, executing these statements is enough; however, sometimes, it helps to build a partial image.

Removing the breaking parts of the build

When I first started writing software, on a ZX Spectrum – I’d frequently copy (BASIC) code out from a magazine. I didn’t really know what I was writing, so if it didn’t work it would give me an error that I didn’t understand; however, it would tell me the offending line number, and so I’d simply remove that line. Obviously, subsequent lines would then start to fail, and typically, I’d end up with a program that ended at the first typing (or printing) error. This didn’t make for a very good game.

This isn’t true in docker – if you remove the offending code, you can still create an environment, explore it, and even manually execute the rest of the build inside the environment, to see what’s going wrong!

FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["pcm-test.csproj", "pcm-test/"]
RUN dotnet restore "pcm-test/pcm-test.csproj"
COPY . .
WORKDIR "/src/pcm-test"

#RUN dotnet build "pcm-test.csproj" -c Release -o /app/build
#
#FROM build AS publish
#RUN dotnet publish "pcm-test.csproj" -c Release -o /app/publish
#
#FROM base AS final
#WORKDIR /app
#COPY --from=publish /app/publish .
#ENTRYPOINT ["dotnet", "pcm-test.dll"]

This will now create an image, and so we can run and attach to that image:

 docker run -it pcm-test sh

I can now have a look around the image:

As you can see, this looks a bit strange – there’s no code there.

Summary

In this post, I’ve covered two techniques to work out why your docker build may be failing.

Beginner’s Guide to Docker – Part 2 – Debugging a Docker Build

In this post I covered the very basics of getting started with Docker. Once you start to experiment, you’ll need to learn how to debug and investigate some of the unexpected things that happen.

Caveat

In this post, you’ll see references to WebbApplication4 and WebApplication5. This is simply because, during creating the post, I switched, didn’t realise the screenshots were a mix of both, and now don’t consider it worth my time to change. Just consider the two interchangeable.

Asp.Net 5

For this example, we’ll use the default dockerfile that comes with Asp.Net 5; however, we’ll build it up ourselves. Just build a new Asp.Net project.

When setting this up, do not enable docker support:

If we had enabled docker support, we would get a docker file – so let’s build that (this is taken directly from that file). We’ll put it in the same directory as the sln file.

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["WebApplication4/WebApplication4.csproj", "WebApplication4/"]
RUN dotnet restore "WebApplication4/WebApplication4.csproj"
COPY . .
WORKDIR "/src/WebApplication4"
RUN dotnet build "WebApplication4.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication4.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication4.dll"]

To give a quick rundown of what’s happening in this file:
– Where you can see `AS base`, `AS build`, etc, these are known as stages (we’ll come back to that).
– The `FROM` statements are telling docker to build on an existing image.
– WORKDIR changes the running directory inside the container: if this directory doesn’t exist, then the command will create it.
– COPY does what you would think: copies whatever you tell it from the first parameter (the host machine) into the second (the docker container)
– RUN executes a command in the container.
– Finally, ENTRYPOINT tells docker what to do when it starts the container – it our case, we’re running `dotnet WebApplication4.dll`.

Building and Running

To build the image, go to the application’s solution directory (where your sln file is located):

docker build -t pcm-web-app-4 . 

Once the image builds successfully, you can run it like this:

docker run pcm-web-app-4 -p 1234:80

In this post, we’re going to be more interested in debugging the build itself that running the container. Having said that, we should quickly visit the port mapping (-p); the command above is mapping the port 80 inside the container (in the docker file, we issues an EXPOSE on that port) to post 1234 on the host (so you would navigate to localhost:1234 on the host machine to connect).

Listing Docker Containers, Stopping, Killing, and Attaching

You can list running docker containers by issuing the command:

docker ps

Once you have the container ID (highlighted in the image), you can do various things with it.

Attach

To attach to the instance:

docker attach 229

Notice that I only used the first three letters of the container instance ID? That’s because docker will let you abridge the ID to the smallest unique set of numbers. This will attach to the container, and you can browse around.

Stop and Kill

If you want to remove an instance, the command is:

docker stop 229

Which will stop the current instance. However, the instance is still there. You can see it by calling:

docker ps -a

To remove the instance completely, you’ll need to call:

docker rm 229

However, you will only be able to remove the container once it’s stopped.

Now that we’ve covered some basics, let’s get into the debugging.

Target

The first useful tip here is to use the target parameter. To build the dockerfile above, you may do something like this (as shown above):

docker build -t web-app-5 . 

That will build the entire image; but if you get an issue, it may fail at an intermediate stage; in that case, you can break down the build; for example:

docker build --target build -t pcm-web-app-5 .

You can then have a look around at the build files by attaching to the container:

docker run -it pcm-web-app-5

A similar technique can be used if you’re getting issues with the entry point not functioning as expected.

ENTRYPOINT

In the dockerfile, you can simply comment out the ENTRYPOINT:

#ENTRYPOINT ["dotnet", "WebApplication5.dll"]

Now, if you run the container; for example:

docker run -d -it pcm-web-app-5

-d launches the container in detached mode; you can then attach:

docker attach eb5

You can then manually run the entry point; for example:

Finally, let’s see how we can see details of a running container.

Inspecting the Container

While the container is running, there’s a set of metadata that is accessible. This contains things like the IP, ports, Mac Address, Name, etc… To view this, call:

docker inspect <container ID>

For example:

docker inspect 41b

There’s a lot of information here, and you can traverse it by using the -f parameter to specify a specific attribute. For example:

docker inspect -f '{{ .State.Status }}' 07a

This will show specific properties without the need to scroll through the full JSON.