Docker for beginners - docker file, docker build, docker run, docker compose, what does it all mean?
Tags: docker, dotnet, dotnetcore
Published: 2019-08-27 10:52:51

In my [previous post](https://dev.to/ch_lee99/getting-started-with-docker-for-c-developers-45hc), I talked about my learnings of getting started with Docker in Visual studio and a brief intro into the Docker ecosystem.

Visual Studio can get you up and running very fast with Docker, without having to create Docker files and using the Docker command lines. However, I am going to try and explain what a Docker file is, Docker build, Docker run and also Docker Compose and how it all comes together.

**Tl;dr:** 
* Docker files describe how to build a Docker image.
* The Docker build command builds images based on Docker files
* The Docker run command starts a Docker image
* Docker compose files combine docker building and running in a single file.


## So...What is a Docker file?
A Docker file is in the form of a YAML file that contains a set of instructions that describes what the application needs in order to build it. If you are new to YAML, [here's a nice intro](https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html).


Taking the sample docker file from [this sample](https://github.com/ch-lee/DockerAspNetCoreDemo/blob/master/DockerAspNetCoreDemo/Dockerfile) which was generated by Visual Studio, it can be broken into 4 stages.

Let's try and break it down.

#### Stage 1: Specify the runtime
```yaml
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
```
The first line uses the [FROM](https://docs.docker.com/engine/reference/builder/#from) syntax to define what base docker image to use. In this case, it specifies the dotnet 2.1 runtime.

The next line uses the [`WORKDIR`](https://docs.docker.com/engine/reference/builder/#workdir) command which sets the working directory.

The final line of the first step uses the [`EXPOSE`](https://docs.docker.com/engine/reference/builder/#expose) feature which exposes port 80.

#### Stage 2: Building
The next stage is more involved.
```yaml
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY DockerAspNetCoreDemo/DockerAspNetCoreDemo.csproj DockerAspNetCoreDemo/
RUN dotnet restore DockerAspNetCoreDemo/DockerAspNetCoreDemo.csproj
COPY . .
WORKDIR /src/DockerAspNetCoreDemo
RUN dotnet build DockerAspNetCoreDemo.csproj -c Release -o /app
```
Similar to the first stage, it again uses the `FROM` syntax to define an image. Notice this time it specifies the dotnet 2.1 sdk rather than the runtime. This is so that it can build the app.

The next line sets the working directory to `src`. It then uses the [copy](https://docs.docker.com/engine/reference/builder/#copy) syntax to copy the csproj file into a new folder within docker called `DockerAspNetCoreDemo`.

It runs `dotnet restore` using the copied proj file. It then copies everything and from the current source folder to the docker folder of `src`. Finally, it runs `dotnet build` with the release parameter and outputs the files into the `app` directory which was created in stage 1.

#### Stage 3: Publishing
The penultimate step below executes the `dotnet publish` command and has a parameter that specifies the output to the `app` folder.
```
FROM build AS publish
RUN dotnet publish DockerAspNetCoreDemo.csproj -c Release -o /app
```

#### Stage 4: Setting the entry point
Finally, these steps instruct Docker where the entry point of how to start the application. I suppose it's equivalent to the `dotnet-run` command.
```yaml
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "DockerAspNetCoreDemo.dll"]
```


## Docker build
Now that we have a Docker file, one can run the `docker build` command to build a docker image.

Now, if navigate to the root directory of the demo application and run:
```shell
docker build DockerAspNetCoreDemo -f DockerAspNetCoreDemo/Dockerfile -t aspnetdemo
```

You may get an error like this:

```shell
C:\Users\chlee\Documents\Github\DockerAspNetCoreDemo>docker build DockerAspNetCoreDemo -f DockerAspNetCoreDemo/Dockerfile -t aspnetdemo
Sending build context to Docker daemon  4.209MB
Step 1/16 : FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
 ---> f8297fe48f0c
Step 2/16 : WORKDIR /app
 ---> Using cache
 ---> c16c3d21ce14
Step 3/16 : EXPOSE 80
 ---> Using cache
 ---> 6fd93b472bcc
Step 4/16 : FROM microsoft/dotnet:2.1-sdk AS build
 ---> f4bc69f831aa
Step 5/16 : WORKDIR /src
 ---> Using cache
 ---> 782546a1fbc5
Step 6/16 : COPY DockerAspNetCoreDemo/DockerAspNetCoreDemo.csproj DockerAspNetCoreDemo/
COPY failed: GetFileAttributesEx \\?\C:\WINDOWS\TEMP\docker-builder185318480\DockerAspNetCoreDemo\DockerAspNetCoreDemo.csproj: The system cannot find the path specified.
```

Not great. Why? Well after some research and discovering this [git issue comment](https://github.com/moby/moby/issues/34893#issuecomment-330930534) it turns out that its the way Visual Studio generates the docker file. However, running this in Visual Studio will have no issues.

### A Simpler Docker file
With the above docker file specific to Visual Studio, below is a simpler version. This was taken and modified from the [docker site](https://docs.docker.com/engine/examples/dotnetcore/)

```yaml
FROM microsoft/dotnet:2.1-sdk AS build-env
WORKDIR /app
EXPOSE 80

# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM microsoft/dotnet:2.1-aspnetcore-runtime
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "DockerAspNetCoreDemo.dll"]

```

Now if you run:
```shell
docker build DockerAspNetCoreDemo -f DockerAspNetCoreDemo/Dockerfile-alternative -t aspnetdemo
```

* `-f` is the filename of the new simpler docker file.
* `-t` specifies a tag for the image, in our case its `aspnetdemo`.


Then the output will be:
```shell
C:\Users\chlee\Documents\Github\DockerAspNetCoreDemo>docker build DockerAspNetCoreDemo -f DockerAspNetCoreDemo/Dockerfile-alternative -t aspnetdemo
Sending build context to Docker daemon  4.209MB
Step 1/11 : FROM microsoft/dotnet:2.1-sdk AS build-env
 ---> f4bc69f831aa
Step 2/11 : WORKDIR /app
 ---> Using cache
 ---> 9025461ed217
Step 3/11 : EXPOSE 80
 ---> Using cache
 ---> 0e3c072cc161
Step 4/11 : COPY *.csproj ./
 ---> Using cache
 ---> 3eadd1126142
Step 5/11 : RUN dotnet restore
 ---> Using cache
 ---> 481f505ae094
Step 6/11 : COPY . ./
 ---> Using cache
 ---> 570fb6690fb4
Step 7/11 : RUN dotnet publish -c Release -o out
 ---> Using cache
 ---> 382bad5ea05e
Step 8/11 : FROM microsoft/dotnet:2.1-aspnetcore-runtime
 ---> f8297fe48f0c
Step 9/11 : WORKDIR /app
 ---> Using cache
 ---> c16c3d21ce14
Step 10/11 : COPY --from=build-env /app/out .
 ---> Using cache
 ---> 07c15508103a
Step 11/11 : ENTRYPOINT dotnet DockerAspNetCoreDemo.dll
 ---> Using cache
 ---> 0eeff78b4f71
Successfully built 0eeff78b4f71
Successfully tagged aspnetdemo:latest
```

This should have created a docker image, and to confirm this, you can run `docker images` to list all the images.

You should get something like this:
```shell
REPOSITORY          TAG                      IMAGE ID            CREATED             SIZE
aspnetdemo          latest                   0eeff78b4f71        18 minutes ago      506MB
microsoft/dotnet    2.1-aspnetcore-runtime   f8297fe48f0c        45 hours ago        503MB
```

## Docker run 
Now that we have an image, we can run the `docker run` command to start the image and load it into a container.

```shell
docker run -d -p 8080:80 --name aspnetapp aspnetdemo
```

The docker run parameters used can be described below: 
* `-d` specifies it's in detach mode, which means it will run in the background.
* `-p` specifies the external port 8080 and the internal port of the image which is 80
* `--name` specifies the name of the container
* last parameter, `aspnetdemo` is the image name that we want to run.

Run the command `docker ps` to verify it is running. You should see something like this:

```shell
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
5a04e20efb39        aspnetdemo          "dotnet DockerAspN..."   38 seconds ago      Up 34 seconds       0.0.0.0:8080->80/tcp   aspnetapp
```

Finally, if you load in a browser `https://localhost:8080` you should see something like this:

![docker-aspnetdemo-localhost](https://thepracticaldev.s3.amazonaws.com/i/t02hd8of48rheuv5mr2a.png)

## What is Docker Compose?
Docker compose is another command where it can be used in conjunction with Docker files. A Docker compose file will tend to have instructions on how to start the application. In general, it will just refer to the Docker file or an existing docker image and also include any app-specific parameters. For instance, in a web app, we can specify ports and any environment variables. 

Here is an example of a docker-compose file:

```yaml
version: '3.4'

services:
  dockeraspnetcoredemo:
    build:
      context: .
      dockerfile: DockerAspNetCoreDemo/Dockerfile
    ports:
        - "8080:80"

```

Notice that it specifies the docker file `DockerAspNetCoreDemo/Dockerfile`. If we wanted to specify an image, the compose file will look something like this:

```yaml
version: '3.4'

services:
  dockeraspnetcoredemo:
    image: aspnetdemo
    ports:
        - "8080:80"

```

Now, if we run:
```shell
docker-compose -f docker-compose.yml up -d
```

* `-f` specifies the filename
* `up` is the command to "start" the image
* `-d` is the detach mode which runs it in the background

You should see the demo site again if you navigate to `https://localhost:8080`



## Summary
I talked about what is a Docker file is, how to build a docker image from a docker file. Then I walked through how to start a docker image either via the docker run command or via a docker compose file.

I hope this has been helpful and some insight into the Docker world.

You can grab the code from [https://github.com/ch-lee/DockerAspNetCoreDemo](https://github.com/ch-lee/DockerAspNetCoreDemo)