Build your .NET image
- Build images
- Run your image as a container
- Use containers for development
- Run your tests
- Configure CI/CD
- Deploy your app
Prerequisites
Work through the Get started guide to understand Docker concepts.
Overview
Now that we have a good overview of containers and the Docker platform, let’s take a look at building our first image. An image includes everything needed to run an application - the code or binary, runtime, dependencies, and any other file system objects required.
To complete this tutorial, you need the following:
- .NET SDK version 6.0 or later. Download .NET SDK.
- Docker running locally. Follow the instructions to download and install Docker.
- An IDE or a text editor to edit files. We recommend using Visual Studio Code.
Sample application
For our sample application, let’s create a simple application from a template using .NET. Create a directory in your local machine named dotnet-docker
. Open a terminal and change to that directory. Run the following dotnet new
command to create a C# app using the ASP.NET Core Web App template.
$ mkdir dotnet-docker
$ cd dotnet-docker
$ dotnet new webapp -n myWebApp -o src --no-https
Output similar to the following appears.
The template ASP.NET Core Web App was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/aspnetcore/6.0-third-party-notices for details.
The command will create a new directory called src
. View the src
directory and verify the contents. You should see the following directories and files.
├── Pages
│ ├── Error.cshtml
│ ├── Error.cshtml.cs
│ ├── Index.cshtml
│ ├── Index.cshtml.cs
│ ├── Privacy.cshtml
│ ├── Privacy.cshtml.cs
│ ├── Shared
│ ├── _ViewImports.cshtml
│ └── _ViewStart.cshtml
├── Program.cs
├── Properties
│ └── launchSettings.json
├── appsettings.Development.json
├── appsettings.json
├── myWebApp.csproj
├── obj
│ ├── myWebApp.csproj.nuget.dgspec.json
│ ├── myWebApp.csproj.nuget.g.props
│ ├── myWebApp.csproj.nuget.g.targets
│ ├── project.assets.json
│ └── project.nuget.cache
└── wwwroot
├── css
├── favicon.ico
├── js
└── lib
Test the application
Let’s start our application and make sure it’s running properly. Open your terminal and navigate to the src
directory and use the dotnet run
command.
$ cd /path/to/dotnet-docker/src
$ dotnet run --urls http://localhost:5000
Output similar to the following appears.
Building...
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\username\dotnet-docker\src\
Read the output to verify how you can access the application. In the example above, Now listening on: http://localhost:5000
indicates that you access the application at http://localhost:5000
.
Open a web browser and access the application based on the URL in the output. The following page should appear.
Press Ctrl+C in the terminal window to stop the application.
Create a Dockerfile
In the dotnet-docker
directory, create a file named Dockerfile
.
Next, we need to add a line in our Dockerfile that tells Docker what image
we would like to use to build our application. Open the Dockerfile
in an IDE or a text editor, and add the following instructions.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:6.0 as build-env
Docker images can be inherited from other images. Therefore, instead of creating our own image, we’ll use the official .NET SDK image that already has all the tools and packages that we need to build a .NET application.
We will use a multi-stage build and define a stage for building the application.We define a build-env
stage in our Dockerfile using as
.
Note
To learn more about multi-stage builds, see Multi-stage builds.
To make things easier when running the rest of our commands, let’s create a working directory for our source files. This instructs Docker to use this path as the default location for all subsequent commands. By doing this, we do not have to type out full file paths but can use relative paths based on the working directory.
WORKDIR /src
Although not necessary, the commands below will copy only the csproj files and then run dotnet restore
. Each command creates a new container layer. To speed the building of containers, Docker caches these layers. Since these files won’t change often, we can take advantage of the caching by copying these files and running restore as separate commands.
COPY src/*.csproj .
RUN dotnet restore
Next, you’ll need to copy the rest of your source files into the image. The line below will copy the files from the src
directory on your local machine to a directory called src
in the image.
COPY src .
Next, you’ll need to run the dotnet publish
command to build the project.
RUN dotnet publish -c Release -o /publish
Next, you’ll specify the image that you’ll use to run the application, and define it as the runtime
stage.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 as runtime
Next, specify the working directory for this stage.
WORKDIR /publish
Next, copy the /publish directory from the build-env stage into the runtime image.
COPY --from=build-env /publish .
Expose port 80 to incoming requests.
EXPOSE 80
Now, all we have to do is to tell Docker what command we want to run when our image is executed inside a container. We do this using the ENTRYPOINT command.
ENTRYPOINT ["dotnet", "myWebApp.dll"]
Here’s the complete Dockerfile.
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:6.0 as build-env
WORKDIR /src
COPY src/*.csproj .
RUN dotnet restore
COPY src .
RUN dotnet publish -c Release -o /publish
FROM mcr.microsoft.com/dotnet/aspnet:6.0 as runtime
WORKDIR /publish
COPY --from=build-env /publish .
EXPOSE 80
ENTRYPOINT ["dotnet", "myWebApp.dll"]
.dockerignore file
To make your build context as small as
possible, add a .dockerignore
file
to your dotnet-docker
folder and copy the following into it.
**/bin/
**/obj/
Directory structure
Just to recap, we created a directory in our local machine called dotnet-docker
and created a simple .NET application in the src
folder. We also created a Dockerfile containing the commands to build an image as well as a .dockerignore file. The dotnet-docker
directory structure should now look like:
├── dotnet-docker
│ ├── src/
│ ├── Dockerfile
│ ├── .dockerignore
Build an image
Now that we’ve created our Dockerfile, let’s build our image. To do this, we use the docker build
command. The docker build
command builds Docker images from a Dockerfile and a “context”. A build’s context is the set of files located in the specified PATH or URL. The Docker build process can access any of the files located in this context.
The build command optionally takes a --tag
flag. The tag is used to set the name of the image and an optional tag in the format name:tag
. We’ll leave off the optional tag
for now to help simplify things. If you do not pass a tag, Docker uses “latest” as its default tag.
Let’s build our first Docker image. Change directory to the dotnet-docker
directory and run docker build
.
$ cd /path/to/dotnet-docker
$ docker build --tag dotnet-docker .
View local images
To see a list of images we have on our local machine, we have two options. One is to use the CLI and the other is to use Docker Desktop. As we are currently working in the terminal let’s take a look at listing images using the CLI.
To list images, simply run the docker images
command.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dotnet-docker latest 8cae92a8fbd6 3 minutes ago 216MB
You should see at least one image listed, the image we just built dotnet-docker:latest
.
Tag images
As mentioned earlier, an image name is made up of slash-separated name components. Name components may contain lowercase letters, digits and separators. A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator.
An image is made up of a manifest and a list of layers. Do not worry too much about manifests and layers at this point other than a “tag” points to a combination of these artifacts. You can have multiple tags for an image. Let’s create a second tag for the image we built and take a look at its layers.
To create a new tag for the image we’ve built above, run the following command.
$ docker tag dotnet-docker:latest dotnet-docker:v1.0.0
The docker tag
command creates a new tag for an image. It does not create a new image. The tag points to the same image and is just another way to reference the image.
Now, run the docker images
command to see a list of our local images.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dotnet-docker latest 8cae92a8fbd6 4 minutes ago 216MB
dotnet-docker v1.0.0 8cae92a8fbd6 4 minutes ago 216MB
You can see that we have two images that start with dotnet-docker
. We know they are the same image because if you take a look at the IMAGE ID
column, you can see that the values are the same for the two images.
Let’s remove the tag that we just created. To do this, we’ll use the rmi
command. The rmi
command stands for remove image.
$ docker rmi dotnet-docker:v1.0.0
Untagged: dotnet-docker:v1.0.0
Note that the response from Docker tells us that the image has not been removed but only “untagged”. You can check this by running the docker images
command.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dotnet-docker latest 8cae92a8fbd6 6 minutes ago 216MB
Our image that was tagged with :v1.0.0
has been removed, but we still have the dotnet-docker:latest
tag available on our machine.
Next steps
In this module, we took a look at setting up our example .NET application that we will use for the rest of the tutorial. We also created a Dockerfile that we used to build our Docker image. Then, we took a look at tagging our images and removing images. In the next module we’ll take a look at how to:
Feedback
Help us improve this topic by providing your feedback. Let us know what you think by creating an issue in the Docker Docs GitHub repository. Alternatively, create a PR to suggest updates.