Welcome to the Wonderful World of Docker
I'll take 20 seconds of your time to say this... The world of spending weekends publishing and deploying to your company's Windows IIS Servers is coming to an end. I've spent many a weekend doing so and I'm thrilled to see the light at the end of the tunnel. That light is containerization. Containerization is nothing new, it's been there way before Docker. Why is it so great though? One of the many things that make it so great is it gives you your weekend back. It does this by setting up a complete environment that builds and deploys your .Net code in a single command. Ok, that's great, but you can do that now. The great this is it's now portable. I can create my code in Windows, deploy to a Linux Docker image and take that image and run it anywhere Docker is installed. I don't need to worry about what's installed on the destination. As long as it has Docker, you're golden. So, let's start our journey at the Dockerfile.
The Flow of Docker
When I first started with Docker, it all seemed a bit scary. What is this voodoo. What is a Dockerfile? What is a Docker image? What is a Docker container? Like with most tech things once you get your hands on it and start understanding it, you'll see it's not scary and in this case it's actually pretty awesome.
The flow for Docker is simple...
- Dockerfile
- Create Docker image from Dockerfile
- Run Docker container based on Docker image
If you're familiar with VMs, it's not really any different in how they work. You download some VM image, like Ubuntu and you run it. Same thing for Docker, just without the overhead of the whole OS emulation. There are lot's of pretty Docker vs VM pictures that explain the difference.
Step 1. The Dockerfile
When you tell Visual Studio to enable Docker on your solution, it adds a Dockerfile for you. What is a Dockerfile? It's nothing more than a scripting language like a batch file or bash, etc. You tell it to go out to Docker, grab some base image, copy some files into the image and do some stuff. It's really nothing magical at all, just a simple script of commands.
Here's a sample Dotnet Dockerfile...
# <https://hub.docker.com/_/microsoft-dotnet-core>
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /source
# copy csproj and restore as distinct layers
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore -r linux-musl-x64
# copy everything else and build app
COPY aspnetapp/. ./aspnetapp/
WORKDIR /source/aspnetapp
RUN dotnet publish -c release -o /app -r linux-musl-x64 --self-contained false --no-restore
# final stage/image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["./aspnetapp"]
Let's break down what's going on there. It's actually relatively simple once you see it.
Docker Images and Tags
# <https://hub.docker.com/_/microsoft-dotnet-core>
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /source
Docker hub is the major hub of all public Docker images. A Docker image is the package that gets created from your Dockerfile script. In this case we are saying we want to download to our local machine the image that contains the dotnet 3.1 sdk so we can use that to build our dotnet core 3.1 solution. If you go to Docker hub and look at the various dotnet sdk docker images Microsoft offers, you'll see you have quite a few options. They are broken down into dotnet sdk version and host OS. For example, if you look at our one above you'll see it end's in :3.1
. That is called a Docker Tag. It tells Docker which particular version of the image you want. In our case we want Dotnet Core SDK v3.1 running on Debian Linux. If instead I put :2.1-alpine
I'm telling docker I want the Dotnet Core SDK v2.1 running on Alpine Linux. How do I know that, because it tells me that right on the Docker hub page. When you're looking for a particular image, just head out to Docker Hub and the page for the image will list the available versions.
The AS build
at the end of the FROM
just let's us reference this image later on down in the script as we'll show in a bit. It will let us just say take the source code you just built in the build
step so we can run it.
You'll also notice we run WORKDIR /source
. That's nothing more than changing the directory in the image to /source.
Building Your Dotnet Core Solution in Docker
# copy csproj and restore as distinct layers
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore -r linux-musl-x64
# copy everything else and build app
COPY aspnetapp/. ./aspnetapp/
WORKDIR /source/aspnetapp
RUN dotnet publish -c release -o /app -r linux-musl-x64 --self-contained false --no-restore
Our next section does the actual build of our Dotnet core solution. Let's go through it.
COPY *.sln .
and COPY aspnetapp/*.csproj ./aspnetapp/
both oddly enough copy stuff. They copy it from your local machine to inside the docker image. In the first case we are copying our solution file to the root of the current directory in the Docker image (which is /source as we set above). The second command copies our csproj files to a new directory in the docker image of /source/aspnetapp
. So now we have our solution and csproj files inside of the docker image (well they will be once we build this thing).
Next we RUN dotnet restore -r linux-musl-x64
which is just running the standard dotnet command to restore our packages. In our case the -r
switch specifies to use the lightweight Alpine Linux based runtime, since we'll be using that as our runtime later on.
COPY aspnetapp/. ./aspnetapp/
now copies the rest of our source code over to the Docker image in preparation for the build. So we will actually be building inside of the Docker image. You don't need to pre-build on your machine. We then change the directory in the image to our code where the csproj file is located at /source/aspnetapp
.
Finally we build/publish our aspnet app...
RUN dotnet publish -c release -o /app -r linux-musl-x64 --self-contained false --no-restore
... We are just running the dotnet publish command outputting our published code to the /app directory inside the Docker image.
Running your Dotnet Core Project in Docker
# final stage/image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["./aspnetapp"]
We now have our application built inside the /app directory in our image. Now we want to bring down the runtime to actually run our application. The same similar FROM command...
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
... will run our application. In our case we are saying we want the dotnet core version 3.1 runtime running on Alpine Linux.
Aside: Why would we pick one host OS over the other? In most cases in honestly doesn't matter. For most of your projects they'll all work equally. Alpine Linux has become quite popular in the Docker community for the simple reason is that it is very small and lightweight. I generally try to go with Alpine first for that reason.
Now we brought down the dotnet core runtime image. Next we change our working directory to /app in the image. Next we our going to copy our built code into that directory.
COPY --from=build /app ./
This copy is different in that it specifies that we want to copy the contents of the /app directory in our build
image that we setup above to the runtime image's current directory. So we're creating a Dockerfile that will create an image of our application, but that image itself is using both the sdk and runtime image. Again, all this is happening inside Docker containers, nothing is going on with our local machine. Fantastic.
Finally the magic...We run our application.
ENTRYPOINT ["./aspnetapp"]
Entrypoint is basically like CMD (which Docker also has) in that it executes a single executable, which in our case is our published app.
Next step for another BitLeaf.io blog post is to actually run this thing and see what we can do with it.
Top comments (1)
Similar stuff on DigitalOcean App Platform can be accomplished using this sample github.com/creativefisher/aspnetco...