.NET core came with many revolutions in the way that .NET apps were written not least the ability to do cross-platform development. My main platform of choice for development currently is Linux and I wanted to setup a development workflow that included .NET core as a back-end to a Single Page JavaScript application. Whilst I was at it I figured it should use docker containers to simplify build and deployment.

The end result is here, however I want to walk through each stage and try to explain what the purpose of each configuration file is and what particular settings are used for. Whilst I am running on Linux I hope that following along on a Windows machine should not be too tricky. I have verified that the code runs on a Mac without any changes.

This is part 1 of the article focusing on the .NET container build and introducing some docker commands. Part 2 will add the UI layer and look at composing containers together.

Prerequisites

.NET Core SDK Docker Node Visual Studio Code — Or any editor, I like VS Code and it has extensions for docker that allow us a graphical view of the deployed images and containers.

What we are building

There is not much in the way of C# or JavaScript coding in this story, I am going to use the dotnet and Vue cli’s to generate basic applications and have the JavaScript app call the dotnet app and display some basic data. The point of this story is to show how the applications are encapsulated in containers and composed together.

Why use docker?

Docker containers make it incredibly easy to reproduce your application on different machines. For instance, once I’d completed development on Linux I went to my mac, cloned the repo and ran “docker-compose up”. After downloading and building, the application was available in my browser with no further installation.

Containers also provide isolation - dependencies and settings used in the containers do not impact any other software running on your machine. This helps with avoiding dependency conflicts.

Deployment can be greatly simplified with docker, images can be pulled from a central hub once built, applications do not need to be rebuilt when moving from development to staging to production.

Let’s go — building our API

First things first, create a folder called core-docker and then open it in your terminal. Enter the following:

mkdir core-api

cd core-api dotnet new webapi

dotnet restore

This will create a new webapi project and restore all its dependencies. The latest version of .NET enables HTTPS redirection by default and we don’t need it here so I made a small change in Startup.cs

Startup.cs

Once this is done, enter “dotnet run” in your terminal and the api will begin running on port 5000, if you navigate to http://localhost:5000/api/Values in your browser you should see a JSON response of [“value1”,”value2"].

Building an Image

We have a running API so let’s put it in a container. Add a file called Dockerfile and open in Visual Studio Code, this should prompt you to install the relevant extension which I recommend doing.

The Dockerfile for our API consists of 2 sections, one for building and the other for running:

Dockerfile

If you’ve not used docker before this can appear quite daunting however it is relatively straightforward. Each line begins with a command (FROM, COPY, RUN etc) and each command will be executed in order from top to bottom.

FROM specifies a docker image to use, in line 1 we specify the dotnet 2.1 SDK image which we will use to build our application. WORKDIR specifies a working directory inside the image. We will be using /app as our working directory. COPY copies files from our local file system into the image. We will copy the csproj file over initially and run restore then copy all the remaining files and run dotnet publish to build our application. The runtime portion of the file uses a different docker base image, the aspnetcore-runtime image, it copies over all the files from the build and then defines the application entrypoint.

To use this file to build execute the command:

docker build -t core-api .

This may be quite a lengthy process but each intermediate step shows its status, you should see something like this:

If you now run:

docker image ls

This will list all the images on your system and you should see the newly built core-api:

Some interesting things to note from this step, if you look at the output from docker build you can see lots of ID’s appearing after each step. This is because docker is applying each command to your image incrementally and creating a new layer. These layers are visible using the docker history command:

docker image history core-api:latest

Also, different stages of the process can interact with each other. If you look at the first FROM statement we’ve tagged this as “build”, later in the “runtime” section we are referencing “build” in the COPY statement.

If you want to remove the image, you can use docker rm, this can take the image name or ID:

docker image rm 7c67428fb17a

If you get to the point that you’re seeing hundreds of images in docker you can ask it to automatically remove them:

docker image prune

Or even ask docker to do a full clean of images, containers and networks:

docker system prune

Deploying a Container

Now we have an image, our built application, we need to get it up and running. We do this with the docker “run” command:

docker run core-api:latest

This is the simplest way to start a container, if you then run:

docker container ls

You can see the container is up and running, but in Production mode, not Development as it was when we started it using the dotnet command. Also, if you visit http://localhost/api/Values you should get an error not the JSON response we are expecting. And look at the name docker has given our container, it’s a randomly generated name of docker’s devising, not great if we want to be able to consistently refer to our container in the future.

We can fix all these things with command-line arguments. First of all stop our container from running and remove it:

docker container stop stoic_goldstine

docker container rm stoic_goldstine

(You will need to replace stoic_goldstine with the name of your container returned from docker container ls)

Now, start your container again using this command:

docker run --name core-api --env ASPNETCORE_ENVIRONMENT=Development -p 80:80 core-api:latest

We have added 3 arguments, — name is the name of the container when it’s up and running, — env allows us to pass environment variables to the running container and, perhaps most importantly, -p allows us to map ports on the container to ports on our machine.

When this container starts you can use the ls command and see the name and port mapping we provided:

And if you go to http://localhost/api/Values you should get the JSON response expected.

Job’s done

That is it for this part. Part 2 will go into creating a simple web UI that talks to the API, adding it to its own container and then composing our containers together to complete our development environment.