.NET Core with Docker Containers
Dockerizing your .NET Core application
Let’s create a production build with Docker
Hey guys!
Today I’m writing about creating a production build for a .NET Core application using Docker. These are super exciting technologies and see many people wanting to learn more about how to use them together.
For those that don’t know, Docker allows you to easily create, deploy, and run applications. You pack your app and all its dependencies together and then ship into a docker image. Then you can run a container with this image in any machine running docker and be assured that your application will work fine, regardless of the machine’s configurations.
Requirements
Docker Windows requires Hyper-V to run and it is only shipped with the Pro version, so you are on a Windows that is not a Pro version you might have to use Docker Toolbox.
- Any Code Editor of your choice (I use Visual Studio Code)
You might want to add this Docker extension that adds syntax highlighting and other features to docker in VS Code
Set sail!
Now that we have our environment set, we can start by initializing a new .NET app. For this example, I took the liberty to choose a Blazor Server app, just for fun. :)
Let’s create our application with:
dotnet new blazorserver -o DockerDotnetApp
Awesome! Now we have a .NET app ready.
We can test it by running
dotnet run
When you navigate to https://localhot:5001 you should see this:
Yay! It works!
Let’s Docker it!
Now that we have a working app we can head to docker!
First, add a new file name Dockerfile
(without extension) to the solution folder.
Now we can open and start configuring it!
Dockerfile
is a set of instructions so docker can know what it needs and what it needs to do to build a proper image for your application.
The basic instructions for a Dockerfile are:
- FROM — It is gonna tell Docker which is the base image to be used
- WORKDIR — Set the working directory inside our Docker image
- COPY — Copy files from the context directory to our Docker image
- RUN — Run some command inside our Docker image in the build step
- CMD — Sets the command to be run when running our Docker image
- ENTRYPOINT — It is similar to CMD with a difference that is harder to override its initial command while starting your container
First, we need to specify which image we are going to use as a base. This base image is a simple Linux OS that contains the minimum required to build our app. You can find all Microsoft’s .NET images here.
The images we are going to use first is for the .NET Core SDK: mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine
. So let’s tell that in our Dockerfile with:
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine AS build-env
The AS
build part just tags this build part with the name build-env
. Internally it creates a temporary container to be used later.
Then we set our work directory to be /app
so we don’t end up using the root folder for our app:
WORKDIR /app
Now that we have this set, we can copy our solution and project files to the container.
The reason why we copy them first is that when you build an image, Docker automatically caches this step so if you need to rebuild the image, it doesn’t need to execute everything again. This only happens if nothing changed in this step or previous steps before it. So, as we these steps don’t change too often and we don’t want to download everything again, because it will cost time, we make sure to put it right at the beginning of our Dockerfile.
To do so, we can run the following step:
COPY *.csproj ./
Now we tell docker to run dotnet restore
RUN dotnet restore
After that we copy everything else to our image with:
COPY . ./
Now we just need to Publish our .NET app with
RUN dotnet publish -c Release -o out
It tells us to publish our app in Release
mode and store it in the out
folder.
With that now we can go to the that is gonna build our final image.
For it, we are gonna use the runtime
image because we will just run our app. and set the working directory to /app
.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.5-alpine
WORKDIR /app
Then we need to copy everything from the previously published app to this new container:
COPY --from=build-env /app/out .
Notice that here we use
--from=build-env
to tell docker to get these files from the temporary container.
With that now we just need to tell docker to expose the port 80 (because it is the port that .NET uses in debug mode) and tell docker to run it when the container starts:
EXPOSE 80
ENTRYPOINT ["dotnet", "DockerDotnetApp.dll"]
Your Dockerfile
should be like this:
Now let’s build our image using the command line. The command to build it is:
docker build -t APP_TAG APP_CONTEXT
- -t is the switch to tag our image. It adds a name to the image.
- APP_TAG is the tag
- APP_CONTEXT is the folder that contains your Dockerfile, basically
So, in the end, we can run it like this:
docker build -t andrevitorlopes/dotnetapp .
By convention, tags should start with your Docker username followed by your docker image name, for example andrevitorlopes/dotnetapp.
You can see all the built images with
docker image list
Now let’s run our container:
docker run andrevitorlopes/dotnetapp
You should see something like this in the console:
Which means that the Docker container is running.
Now type CTRL + C
to exit the container. (Don’t worry, your container will continue running on the background)
If you run docker ps
you can see your container running like:
But you will not be able to connect to your app just yet.
The port we exposed with the Dockerfile is just the container port, we need to map it to one of our local machine ports to be able to connect to it.
First, kill the container with:
docker kill CONTAINER_ID
You can find the container image in docker ps
. Mine here is bd8eab8dd1cb
.
docker kill bd8eab8dd1cb
Then we need to run docker with the port switch that is gonna map the container port to our local port -p LOCAL_PORT:CONTAINER_PORT
docker run -p 8080:80 andrevitorlopes/dotnetapp
Now if you go to http://localhost:8080
in your browser, you should see your application running.
Conclusion
Docker is such a powerful tool to generate application deployment builds. The concepts of images and containers make it super helpful and simple when dealing with deployment to multiple environments and you can be assured that you won’t have issues about differences between environment infrastructures and configurations.
So you saw how it is simple to create this .NET Core application production build using Docker and how you can see it running on your local machine.
What’s next?
Next, it is very useful to know how to use docker-compose
for development build, it is a tool that allows you to easily run and integrate multiple containers.
Also, you can surely dive into Kubernetes, the next generation of application deployment. It is an orchestrator that helps you easily escalate your application when needed, among other great features.