ASP.NET Core + Docker + Azure Container Registry
ASP.NET Core + Docker + Azure Container Registry
Putting an ASP.NET Core app in a Docker container is pretty easy. Microsoft's own documentation is a decent source, but where they fall short is, well, providing an actual production scenario. There's only so far a Hello World can take you. Fortunately, you've arrived here, where I'm going to go over exactly how to set up a production ready .NET Core service!
Let's start with the Dockerfile. It's heavily commented, so dive in!
# ---- Layer 1 ----
# In this first layer, we're going to use Microsoft's provided SDK image to
# build and package the binaries
FROM microsoft/dotnet:2.2-sdk-alpine AS build
# We can define custom nuget repositories when restoring packages
# Likely that in a real scenario you're going to have your own private repo somewhere,
# so replace the made up url below, or comment this line if unnecessary
ARG YOURCUSTOMNUGET=https://artifactory.madeupurl.com/api/nuget/YourCustomNuget
# Also using regular nuget source
ARG NUGET=https://api.nuget.org/v3/index.json
# We're setting the work directory (inside the container) to /app
# From now on, all commands will be executed relative to /app
WORKDIR /app
# We're copying the csproj file into the root inside the container
# Remember that as of the above WORKDIR command, 'root' actually means /app
COPY *.csproj ./
# Then, we run our plain dotnet restore specifying nuget sources
# You'll want to specify your own first before the official
RUN dotnet restore --source $YOURCUSTOMNUGET --source $NUGET
# Above, we only copied the CSPROJ file, because we wanted to do a quick restore
# Now, we're copying the rest of the files, including our actual application code
COPY . ./
# Publish with the Release profile (no debug signatures) and put it
# in the 'out' folder
RUN dotnet publish -c Release -o out
# ---- Layer 2 ----
# In this layer, we're going to use Microsoft's provided runtime image
# and start our already-built application inside it
FROM microsoft/dotnet:2.2-aspnetcore-runtime-alpine AS final
WORKDIR /app
# We're defining which port to expose. This should be the same port which
# Kestrel is running on when you run your app with `dotnet run`
EXPOSE 5000
# Here, we're using an optional flag --from which lets us copy the binaries
# from the previous build stage (Layer 1) named 'build'. These files are
# deposited in the /app/out directory
COPY --from=build /app/out
# Finally, we run a command to start the app inside the container
CMD ["dotnet", "YourApplicationBinaryLibrary.dll"]
Okay, hopefully the comments are clear. Now that you have the Dockerfile, it's time to build it! Let's just briefly discuss some basics...
Docker containers are built out of images. You may build your own images, or pull images from public sources like Docker Hub, or maybe even your own private registry. Whatever the case may be, you need to build an image to create a container. Once the container is created, you'll want to tag it. This allows you to keep the image name the same, but increase its version for example. Lastly, once tagged, you can push the container to a container registry.
There's a lot more to learn about Docker. I recommend reading the official documentation here if you want to explore more. Now, let's get buildin'!
We're going to name this imaginary container fancyapi. Feel free to replace that with whatever you want. The container name should probably reflect what's inside.
I'm also assuming you have Docker installed on your system.
The Dockerfile above should be in the same folder as your CSPROJ file. Navigate there in your terminal of choice and run this command:
docker build -t fancyapi:v1 -f Dockerfile .
This builds the image. If you now type in:
docker images
You will see your image in the list! Now that you have an image, let's create the container from it:
docker create -p 5000:80 fancyapi:v1
We're telling docker to create a container from the fancyapi:v1 image. We're also using the -p flag to expose the port inside the container to the public. The first number is the internal port and the second number is the external port. We must create the container first prior to pushing to Azure Container registry. Next, tag it!
docker tag fancyapi:v1 yourregistryname.azurecr.io/fancyapi:v1
Here, we're saying 'hey, tag fancyapi:v1 with this special tag that ACR understands'. This is a required step for ACR to recognize the container.
Alright, we're ready to push! But wait... don't we need to log in? Why yes, yes we do. In my experience there are multiple ways to do this. You can use Azure CLI to log in and run the push command, or you can use a username and password plainly, or you can store the password as a secret in your CI/CD system... I won't be diving into CI/CD details on how to do that as that's out of scope of this article, but...
- If you have control over the CI/CD server, you can install the Azure CLI and login when the server boots up. Then, running any docker commands, including push, will be automatically authenticated.
- If you don't have control, or prefer not to install the Azure CLI, you can store the password in a variable in the CI/CD system and access it within the docker login command. For example Jenkins has something called Credentials.
- Lastly, and I don't recommend this, you can send the username and password in plain text in the docker login command.
If you chose option 1, you can skip this step. If you chose 2 or 3, you can run the following command to log in:
docker login yourregistryname.azurecr.io -u <username> -p <password>
Once you're logged in, you're ready to push the image:
docker push yourregistryname.azurecr.io/fancyapi:v1
Here, we're using that tag selector we created earlier to push the container up into ACR. If you go into Azure Portal, find your container registry and check out the containers ... contained ... there in, you will find fancyapi:v1 ready and waiting.
You can likely use the same (or slightly modified) steps to push your container up to Google, AWS, Docker Hub or even your own hosted repository like Artifactory. Hope this has been useful!