get-started: update .net guide (#18184)

* update dotnet guide

Signed-off-by: Craig Osterhout <craig.osterhout@docker.com>
This commit is contained in:
Craig Osterhout 2023-10-09 08:30:12 -07:00 committed by GitHub
parent 6e199de3af
commit 90cab2e23b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 706 additions and 1001 deletions

View File

@ -1,24 +1,21 @@
---
description: Containerize .NET apps using Docker
keywords: Docker, getting started, .net, language
title: What will you learn in this module?
description: Containerize and develop .NET apps using Docker
keywords: getting started, .net
title: .NET language-specific guide
toc_min: 1
toc_max: 2
---
The .NET getting started guide teaches you how to create a containerized .NET application using Docker. In this guide, you'll learn how to:
* Create a sample .NET application
* Create a new Dockerfile which contains instructions required to build a .NET image
* Build an image and run the newly built image as a container
* Set up volumes and networking
* Orchestrate containers using Compose
* Use containers for development
* Configure a CI/CD pipeline for your application using GitHub Actions
* Containerize and run a .NET application
* Set up a local environment to develop a .NET application using containers
* Run tests for a .NET application using containers
* Configure a CI/CD pipeline for a containerized .NET application using GitHub Actions
* Deploy your application to the cloud
After completing the .NET getting started modules, you should be able to containerize your own .NET application based on the examples and instructions provided in this guide.
Let's get started!
Start by containerizing an existing .NET application.
{{< button text="Build your first .NET image" url="build-images.md" >}}
{{< button text="Containerize a .NET app" url="containerize.md" >}}

View File

@ -1,295 +0,0 @@
---
title: Build your .NET image
keywords: .net, build, images, dockerfile
description: Learn how to build your first Docker image by writing a Dockerfile
---
## Prerequisites
Work through the [Get started guide](../../get-started/index.md) to understand Docker concepts.
## Overview
Now that we have a good overview of containers and the Docker platform, lets 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](https://dotnet.microsoft.com/download).
- Docker running locally. Follow the instructions to [download and install Docker](../../get-docker.md).
- An IDE or a text editor to edit files. We recommend using [Visual Studio Code](https://code.visualstudio.com/).
## Sample application
For our sample application, lets 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.
```console
$ mkdir dotnet-docker
$ cd dotnet-docker
$ dotnet new webapp -n myWebApp -o src --no-https
```
Output similar to the following appears.
```console
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.
```shell
├── 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
Lets start our application and make sure its running properly. Open your terminal and navigate to the `src` directory and use the `dotnet run` command.
```console
$ cd /path/to/dotnet-docker/src
$ dotnet run --urls http://localhost:5000
```
Output similar to the following appears.
```console
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.
![image of app page](./images/dotnet-app-verify-build.png)
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.
```dockerfile
# 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, well 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](../../build/building/multi-stage.md).
To make things easier when running the rest of our commands, lets 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.
```dockerfile
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.
```dockerfile
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.
```dockerfile
COPY src .
```
Next, you'll need to run the `dotnet publish` command to build the project.
```dockerfile
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.
```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:6.0 as runtime
```
Next, specify the working directory for this stage.
```dockerfile
WORKDIR /publish
```
Next, copy the /publish directory from the build-env stage into the runtime image.
```dockerfile
COPY --from=build-env /publish .
```
Expose port 80 to incoming requests.
```dockerfile
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.
```dockerfile
ENTRYPOINT ["dotnet", "myWebApp.dll"]
```
Here's the complete Dockerfile.
```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](../../build/building/context.md) as small as
possible, add a [`.dockerignore` file](../../engine/reference/builder.md#dockerignore-file)
to your `dotnet-docker` folder and copy the following into it.
```shell
**/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:
```shell
├── dotnet-docker
│ ├── src/
│ ├── Dockerfile
│ ├── .dockerignore
```
## Build an image
Now that weve created our Dockerfile, lets 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 builds 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`. Well 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.
Lets build our first Docker image. Change directory to the `dotnet-docker` directory and run `docker build`.
```console
$ 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](../../desktop/use-desktop/images.md). As we are currently working in the terminal lets take a look at listing images using the CLI.
To list images, simply run the `docker images` command.
```console
$ 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. Lets create a second tag for the image we built and take a look at its layers.
To create a new tag for the image weve built above, run the following command.
```console
$ 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.
```console
$ 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.
Lets remove the tag that we just created. To do this, well use the `rmi` command. The `rmi` command stands for remove image.
```console
$ 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.
```console
$ 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 well take a look at how to:
{{< button text="Run your image as a container" url="run-containers.md" >}}
## Feedback
Help us improve this topic by providing your feedback. Let us know what you think by creating an issue in the [Docker Docs](https://github.com/docker/docker.github.io/issues/new?title=[dotnet%20docs%20feedback]) GitHub repository. Alternatively, [create a PR](https://github.com/docker/docker.github.io/pulls) to suggest updates.

View File

@ -1,21 +1,149 @@
---
title: Configure CI/CD for your application
keywords: .net, CI/CD, local, development
description: Learn how to Configure CI/CD for your application
title: Configure CI/CD for your .NET application
keywords: .net, CI/CD
description: Learn how to Configure CI/CD for your .NET application
---
## Get started with GitHub Actions
## Prerequisites
{{< include "gha-tutorial.md" >}}
Complete all the previous sections of this guide, starting with [Containerize a .NET application](containerize.md). You must have a [GitHub](https://github.com/signup) account and a [Docker](https://hub.docker.com/signup) account to complete this section.
## Overview
In this section, you'll learn how to set up and use GitHub Actions to build and test your Docker image as well as push it to Docker Hub. You will complete the following steps:
1. Create a new repository on GitHub.
2. Define the GitHub Actions workflow.
3. Run the workflow.
## Step one: Create the repository
Create a GitHub repository, configure the Docker Hub secrets, and push your source code.
1. [Create a new repository](https://github.com/new) on GitHub.
2. Open the repository **Settings**, and go to **Secrets and variables** >
**Actions**.
3. Create a new secret named `DOCKER_USERNAME` and your Docker ID as value.
4. Create a new [Personal Access Token
(PAT)](/docker-hub/access-tokens/#create-an-access-token) for Docker Hub. You
can name this token `tutorial-docker`.
5. Add the PAT as a second secret in your GitHub repository, with the name
`DOCKERHUB_TOKEN`.
6. In your local repository on your machine, run the following command to change
the origin to the repository you just created. Make sure you change
`your-username` to your GitHub username and `your-repository` to the name of
the repository you created.
```console
$ git remote set-url origin https://github.com/your-username/your-repository.git
```
7. In your local repository on your machine, run the following command to rename
the branch to main.
```console
$ git branch -M main
```
8. Run the following commands to stage, commit, and then push your local
repository to GitHub.
```console
$ git add -A
$ git commit -m "my first commit"
$ git push -u origin main
```
## Step two: Set up the workflow
Set up your GitHub Actions workflow for building, testing, and pushing the image
to Docker Hub.
1. Go to your repository on GitHub and then select the **Actions** tab.
2. Select **set up a workflow yourself**.
This takes you to a page for creating a new GitHub actions workflow file in
your repository, under `.github/workflows/main.yml` by default.
3. In the editor window, copy and paste the following YAML configuration.
```yaml
name: ci
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build and test
uses: docker/build-push-action@v5
with:
context: .
target: build
load: true
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
target: final
tags: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}:latest
```
For more information about the YAML syntax used here, see [Workflow syntax for GitHub Actions](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions).
## Step three: Run the workflow
Save the workflow file and run the job.
1. Select **Commit changes...** and push the changes to the `main` branch.
After pushing the commit, the workflow starts automatically.
2. Go to the **Actions** tab. It displays the workflow.
Selecting the workflow shows you the breakdown of all the steps.
3. When the workflow is complete, go to your
[repositories on Docker Hub](https://hub.docker.com/repositories).
If you see the new repository in that list, it means the GitHub Actions
successfully pushed the image to Docker Hub.
## Summary
In this section, you learned how to set up a GitHub Actions workflow for your application.
Related information:
- [Introduction to GitHub Actions](../../build/ci/github-actions/index.md)
- [Workflow syntax for GitHub Actions](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
## Next steps
In this module, you have learnt how to set up GitHub Actions workflow to an existing Docker project, optimize your workflow to improve build times and reduce the number of pull requests, and finally, we learnt how to push only specific versions to Docker Hub. You can also set up nightly tests against the latest tag, test each PR, or do something more elegant with the tags we are using and make use of the Git tag for the same tag in our image.
Next, learn how you can deploy your application.
You can also consider deploying your application. For detailed instructions, see:
{{< button text="Deploy your app" url="deploy.md" >}}
## Feedback
Help us improve this topic by providing your feedback. Let us know what you think by creating an issue in the [Docker Docs](https://github.com/docker/docker.github.io/issues/new?title=[dotnet%20docs%20feedback]) GitHub repository. Alternatively, [create a PR](https://github.com/docker/docker.github.io/pulls) to suggest updates.
{{< button text="Deploy your app" url="./deploy.md" >}}

View File

@ -0,0 +1,148 @@
---
title: Containerize a .NET application
keywords: .net, containerize, initialize
description: Learn how to containerize an ASP.NET application.
aliases:
- /langauage/dotnet/build-images/
- /langauage/dotnet/run-containers/
---
## Prerequisites
* You have installed the latest version of [Docker
Desktop](../../get-docker.md).
* You have a [git client](https://git-scm.com/downloads). The examples in this
section use a command-line based git client, but you can use any client.
## Overview
This section walks you through containerizing and running a .NET
application.
## Get the sample applications
In this guide, you will use a pre-built .NET application. The application is
similar to the application built in the Docker Blog article, [Building a
Multi-Container .NET App Using Docker
Desktop](https://www.docker.com/blog/building-multi-container-net-app-using-docker-desktop/).
Open a terminal, change directory to a directory that you want to work in, and
run the following command to clone the repository.
```console
$ git clone https://github.com/docker/docker-dotnet-sample
```
## Test the simple application without Docker (optional)
You can test the sample application locally without Docker before you continue
building and running the application with Docker. This section requires you to
have the .NET SDK version 6.0 installed on your machine. Download and install
[.NET 6.0 SDK](https://dotnet.microsoft.com/download).
Open a terminal, change directory to the `docker-dotnet-sample/src` directory,
and run the following command.
```console
$ dotnet run --urls http://localhost:5000
```
Open a browser and view the application at [http://localhost:5000](http://localhost:5000). You should see a simple web application.
In the terminal, press `ctrl`+`c` to stop the application.
## Initialize Docker assets
Now that you have an application, you can use `docker init` to create the
necessary Docker assets to containerize your application. Inside the
`docker-dotnet-sample` directory, run the `docker init` command in a terminal.
Refer to the following example to answer the prompts from `docker init`.
```console
$ docker init
Welcome to the Docker Init CLI!
This utility will walk you through creating the following files with sensible defaults for your project:
- .dockerignore
- Dockerfile
- compose.yaml
- README.Docker.md
Let's get started!
? What application platform does your project use? ASP.NET
? What's the name of your solution's main project? myWebApp
? What version of .NET do you want to use? 6.0
? What local port do you want to use to access your server? 8080
```
You should now have the following contents in your `docker-dotnet-sample`
directory.
```
├── docker-dotnet-sample/
│ ├── .git/
│ ├── src/
│ ├── .dockerignore
│ ├── compose.yaml
│ ├── Dockerfile
│ ├── README.Docker.md
│ └── README.md
```
To learn more about the files that `docker init` added, see the following:
- [Dockerfile](../../engine/reference/builder.md)
- [.dockerignore](../../engine/reference/builder.md#dockerignore-file)
- [compose.yaml](../../compose/compose-file/_index.md)
## Run the application
Inside the `docker-dotnet-sample` directory, run the following command in a
terminal.
```console
$ docker compose up --build
```
Open a browser and view the application at [http://localhost:8080](http://localhost:8080). You should see a simple web application.
In the terminal, press `ctrl`+`c` to stop the application.
### Run the application in the background
You can run the application detached from the terminal by adding the `-d`
option. Inside the `docker-dotnet-sample` directory, run the following command
in a terminal.
```console
$ docker compose up --build -d
```
Open a browser and view the application at [http://localhost:8080](http://localhost:8080). You should see a simple web application.
In the terminal, run the following command to stop the application.
```console
$ docker compose down
```
For more information about Compose commands, see the [Compose CLI
reference](../../compose/reference/_index.md).
## Summary
In this section, you learned how you can containerize and run your .NET
application using Docker.
Related information:
- [Dockerfile reference](../../engine/reference/builder.md)
- [Build with Docker guide](../../build/guide/index.md)
- [.dockerignore file reference](../../engine/reference/builder.md#dockerignore-file)
- [Docker Compose overview](../../compose/_index.md)
## Next steps
In the next section, you'll learn how you can develop your application using
Docker containers.
{{< button text="Develop your application" url="develop.md" >}}

View File

@ -1,458 +1,380 @@
---
title: Use containers for development
keywords: .net, local, development, run,
description: Learn how to develop your application locally.
title: Use containers for .NET development
keywords: .net, development
description: Learn how to develop your .NET application locally using containers.
---
## Prerequisites
Work through the steps to build an image and run it as a containerized application in [Run your image as a container](run-containers.md).
Complete [Containerize a .NET application](containerize.md).
## Introduction
## Overview
In this module, well walk through setting up a local development environment for the application we built in the previous modules. Well use Docker to build our images and Docker Compose to make everything a whole lot easier.
In this section, you'll learn how to set up a development environment for your containerized application. This includes:
- Adding a local database and persisting data
- Configuring Compose to automatically update your running Compose services as you edit and save your code
- Creating a development container that contains the .NET Core SDK tools and dependencies
## Run a database in a container
## Update the application
First, well take a look at running a database in a container and how we use volumes and networking to persist our data and allow our application to talk with the database. Then well pull everything together into a Compose file which allows us to setup and run a local development environment with one command.
This section uses a different branch of the `docker-dotnet-sample` repository
that contains an updated .NET application. The updated application is on the
`add-db` branch of the repository you cloned in [Containerize a .NET
application](containerize.md).
Instead of downloading PostgreSQL, installing, configuring, and then running the PostgreSQL database as a service, we can use the Docker Official Image for PostgreSQL and run it in a container.
To get the updated code, you need to checkout the `add-db` branch. For the changes you made in [Containerize a .NET application](containerize.md), for this section, you can stash them. In a terminal, run the following commands in the `docker-dotnet-sample` directory.
Before we run PostgreSQL in a container, we'll create a volume that Docker can manage to store our persistent data. Lets use the managed volumes feature that Docker provides instead of using bind mounts. You can read all about [Using volumes](../../storage/volumes.md) in our documentation.
Lets create our data volume now.
Stash any previous changes.
```console
$ docker volume create postgres-data
$ git stash -u
```
Now well create a network that our application and database will use to talk to each other. The network is called a user-defined bridge network and gives us a nice DNS lookup service which we can use when creating our connection string.
Check out the new branch with the updated application.
```console
$ docker network create postgres-net
$ git checkout add-db
```
Now we can run PostgreSQL in a container and attach to the volume and network we created above. Docker pulls the image from Hub and runs it for you locally.
In the following command, option `-v` is for starting the container with the volume. For more information, see [Docker volumes](../../storage/volumes.md).
In the `add-db` branch, only the .NET application has been updated. None of the Docker assets have been updated yet.
```console
$ docker run --rm -d -v postgres-data:/var/lib/postgresql/data \
--network postgres-net \
--name db \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=example \
postgres
You should now have the following in your `docker-dotnet-sample` directory.
```
├── docker-dotnet-sample/
│ ├── .git/
│ ├── src/
│ │ ├── Data/
│ │ ├── Models/
│ │ ├── Pages/
│ │ ├── Properties/
│ │ ├── wwwroot/
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.json
│ │ ├── myWebApp.csproj
│ │ └── Program.cs
│ ├── tests/
│ │ ├── tests.csproj
│ │ ├── UnitTest1.cs
│ │ └── Usings.cs
│ ├── .dockerignore
│ ├── .gitignore
│ ├── compose.yaml
│ ├── Dockerfile
│ ├── README.Docker.md
│ └── README.md
```
Now, lets make sure that our PostgreSQL database is running and that we can connect to it. Connect to the running PostgreSQL database inside the container using the following command:
```console
$ docker exec -ti db psql -U postgres
psql (14.5 (Debian 14.5-1.pgdg110+1))
Type "help" for help.
## Add a local database and persist data
postgres=#
```
You can use containers to set up local services, like a database. In this section, you'll update the `compose.yaml` file to define a database service and a volume to persist data.
Press CTRL-D to exit the interactive terminal.
Open the `compose.yaml` file in an IDE or text editor. You'll notice it
already contains commented-out instructions for a PostgreSQL database and volume.
Open `docker-dotnet-sample/src/appsettings.json` in an IDE or text editor. You'll
notice the connection string with all the database information. The
`compose.yaml` already contains this information, but it's commented out.
Uncomment the database instructions in the `compose.yaml` file.
## Update the application to connect to the database
In the above command, we logged in to the PostgreSQL database by passing the `psql` command to the `db` container.
Next, we'll update the sample application we created in the [Build images](build-images.md#sample-application) module.
Let's add a package to allow the app to talk to a database and update the source files. On your local machine, open a terminal, change directory to the `src` directory and run the following command:
```console
$ cd /path/to/dotnet-docker/src
$ dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
```
In the `src` directory, create a `Models` folder. Inside the `Models` folder create a file named `Student.cs` and add the following code to `Student.cs`:
```c#
using System;
using System.Collections.Generic;
namespace myWebApp.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
}
```
Save and close the `Student.cs` file.
In the `src` directory, create a `Data` folder. Inside the `Data` folder create a file named `SchoolContext.cs` and add the following code to `SchoolContext.cs`:
```c#
using Microsoft.EntityFrameworkCore;
namespace myWebApp.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options) { }
public DbSet<Models.Student>? Students { get; set; }
}
}
```
Save and close the `SchoolContext.cs` file.
In the `Program.cs` file located in the `src` directory, replace the contents with the following code:
```c#
using Microsoft.EntityFrameworkCore;
using myWebApp.Models;
using myWebApp.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
// Add services to the container.
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("SchoolContext")));
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
// add 10 seconds delay to ensure the db server is up to accept connections
// this won't be needed in real world application
System.Threading.Thread.Sleep(10000);
var context = services.GetRequiredService<SchoolContext>();
var created = context.Database.EnsureCreated();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
```
Save and close the `Program.cs` file.
In the `appsettings.json` file located in the `src` directory, replace the contents with the following code:
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Host=db;Database=my_db;Username=postgres;Password=example"
}
}
```
Save and close the `appsettings.json` file.
In the `Index.cshtml` file located in the `src\Pages` directory, replace the contents with the following code:
```html
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div class="row mb-auto">
<p>Student Name is @Model.StudentName</p>
</div>
```
Save and close the `Index.cshtml` file.
In the `Index.cshtml.cs` file located in the `src\Pages` directory, replace the contents with the following code:
```c#
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace myWebApp.Pages;
public class IndexModel : PageModel
{
public string StudentName { get; private set; } = "PageModel in C#";
private readonly ILogger<IndexModel> _logger;
private readonly myWebApp.Data.SchoolContext _context;
public IndexModel(ILogger<IndexModel> logger, myWebApp.Data.SchoolContext context)
{
_logger = logger;
_context= context;
}
public void OnGet()
{
var s =_context.Students?.Where(d=>d.ID==1).FirstOrDefault();
this.StudentName = $"{s?.FirstMidName} {s?.LastName}";
}
}
```
Save and close the `Index.cshtml.cs` file.
Now we can rebuild our image. Open a terminal, change directory to the `dotnet-docker` directory and run the following command:
```console
$ docker build --tag dotnet-docker .
```
List your running containers.
```console
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
146e1cb76e71 postgres "docker-entrypoint.s…" 25 minutes ago Up 25 minutes 5432/tcp postgresqldb
72bef28b1cd4 dotnet-docker "./myWebApp" 40 minutes ago Up 40 minutes 0.0.0.0:5000->80/tcp dotnet-app
```
Inspect the image column and stop any container that is using the `dotnet-docker` image.
```console
$ docker stop dotnet-app
dotnet-app
```
Now, lets run our container on the same network as the database. This allows us to access the database by its container name.
```console
$ docker run \
--rm -d \
--network postgres-net \
--name dotnet-app \
-p 5000:80 \
dotnet-docker
```
Let's test that the application works and is connecting to the database. Using a web browser, access `http://localhost:5000`. A page similar to the following image appears.
![Image of app page](./images/dotnet-verify-db.png)
## Connect Adminer and populate the database
You now have an application accessing the database. Although the application created a database and table, it did not create any entries. Let's connect Adminer to manage our database and create a database entry.
```console
$ docker run \
--rm -d \
--network postgres-net \
--name db-admin \
-p 8080:8080 \
adminer
```
Using a web browser, access `http://localhost:8080`.
The Adminer login page appears.
![Image of app page](./images/dotnet-adminer-login.png)
Specify the following in the login page and then click **Login**:
* System: PostgreSQL
* Server: db
* Username: postgres
* Password: example
* Database: my_db
The `Schema: public` page appears.
![Image of app page](./images/dotnet-adminer-db.png)
In `Tables and views`, click `Students`. The `Table: Students` page appears.
![Image of app page](./images/dotnet-adminer-table.png)
Click `New item`. The `Insert: Students` page appears.
![Image of app page](./images/dotnet-adminer-add.png)
Specify a `LastName`, `FirstMidName`, and `EnrollmentDate`. Click `Save`.
Verify that the student name appears in the application. Use a web browser to access `http://localhost:5000`.
![Image of app page](./images/dotnet-app-verify-db-add.png)
List and then stop the application, database, and Adminer containers.
```console
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b76346800b6d adminer "entrypoint.sh docke…" 30 minutes ago Up 30 minutes 0.0.0.0:8080->8080/tcp db-admin
4ae70ac948a1 dotnet-docker "./myWebApp" 45 minutes ago Up 45 minutes 0.0.0.0:5000->80/tcp dotnet-app
75554c7694d8 postgres "docker-entrypoint.s…" 46 minutes ago Up 46 minutes 5432/tcp db
```
```console
$ docker stop db-admin dotnet-app db
db-admin
dotnet-app
db
```
## Better productivity with Docker Compose
In this section, well create a [Compose file](../../compose/index.md) to start our dotnet-docker app, Adminer, and the PostgreSQL database using a single command.
Open the `dotnet-docker` directory in your IDE or a text editor and create a new file named `docker-compose.yml`. Copy and paste the following contents into the file.
The following is the updated `compose.yaml` file.
```yaml
services:
server:
build:
context: .
target: final
ports:
- 8080:80
depends_on:
db:
condition: service_healthy
db:
image: postgres
restart: always
user: postgres
secrets:
- db-password
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: example
- POSTGRES_DB=example
- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
expose:
- 5432
healthcheck:
test: [ "CMD", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres-data:/var/lib/postgresql/data
adminer:
image: adminer
restart: always
ports:
- 8080:8080
app:
build:
context: .
ports:
- 5000:80
depends_on:
- db
volumes:
postgres-data:
db-data:
secrets:
db-password:
file: db/password.txt
```
Save and close the `docker-compose.yml` file.
> **Note**
>
> To learn more about the instructions in the Compose file, see [Compose file
> reference](/compose/compose-file/).
The `dotnet-docker` directory structure should now look like:
Before you run the application using Compose, notice that this Compose file uses
`secrets` and specifies a `password.txt` file to hold the database's password.
You must create this file as it's not included in the source repository.
```shell
├── dotnet-docker
In the `docker-dotnet-sample` directory, create a new directory named `db` and
inside that directory create a file named `password.txt`. Open `password.txt` in an IDE or text editor and add the following password. The password must be on a single line, with no additional lines in the file.
```
example
```
Save and close the `password.txt` file.
You should now have the following in your `docker-dotnet-sample` directory.
```
├── docker-dotnet-sample/
│ ├── .git/
│ ├── db/
│ │ └── password.txt
│ ├── src/
│ ├── Dockerfile
│ ├── tests/
│ ├── .dockerignore
│ ├── docker-compose.yml
│ ├── .gitignore
│ ├── compose.yaml
│ ├── Dockerfile
│ ├── README.Docker.md
│ └── README.md
```
This Compose file is super convenient as we do not have to type all the parameters to pass to the `docker run` command. We can declaratively do that using a Compose file.
We expose the ports so that we can reach the web server and Adminer inside the containers. We also map our local source code into the running container to make changes in our text editor and have those changes picked up in the container.
Another really cool feature of using a Compose file is that we have service resolution set up to use the service names. Therefore, we are now able to use “db” in our connection string. The reason we use “db” is because that is what we've named our PostgreSQL service as in the Compose file.
Now, to start our application and to confirm that it is running properly, run the following command:
Run the following command to start your application.
```console
$ docker compose up --build
```
We pass the `--build` flag so Docker will compile our image and then start the containers.
Open a browser and view the application at [http://localhost:8080](http://localhost:8080). You should see a simple web application with the text `Student name is`.
Now lets test our application. Using a web browser, access `http://localhost:5000` to view the page.
The application doesn't display a name because the database is empty. For this application, you need to access the database and then add records.
## Shutting down
## Add records to the database
To stop the containers started by Docker Compose, press Ctrl+C in the terminal where we ran `docker compose up`. To remove those containers after they have been stopped, run `docker compose down`.
For the sample application, you must access the database directly to create sample records.
## Detached mode
You can run containers started by the `docker compose` command in detached mode, just as you would with the docker command, by using the `-d` flag.
To start the stack, defined by the Compose file in detached mode, run:
You can run commands inside the database container using the `docker exec`
command. Before running that command, you must get the ID of the database
container. Open a new terminal window and run the following command to list all
your running containers.
```console
docker compose up --build -d
$ docker container ls
```
Then, you can use `docker compose stop` to stop the containers and `docker compose down` to remove them.
You should see output like the following.
## The `.env` file
Docker Compose will automatically read environment variables from a `.env` file if it is available. Since our Compose file requires `POSTGRES_PASSWORD` to be set, we create an `.env` file and add the following content:
```shell
POSTGRES_PASSWORD=example
```console
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cb36e310aa7e docker-dotnet-server "dotnet myWebApp.dll" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp docker-dotnet-server-1
39fdcf0aff7b postgres "docker-entrypoint.s…" About a minute ago Up About a minute (healthy) 5432/tcp docker-dotnet-db-1
```
Now, update the compose file to use this variable.
In the previous example, the container ID is `39fdcf0aff7b`. Run the following command to run the `psql` command inside your database container and insert a record into the database. Replace the container ID with your own container ID.
```console
$ docker exec 39fdcf0aff7b psql -d example -U postgres -c "INSERT INTO \"Students\" (\"ID\", \"LastName\", \"FirstMidName\", \"EnrollmentDate\") VALUES (DEFAULT, 'Whale', 'Moby', '2013-03-20');"
```
You should see output like the following.
```console
INSERT 0 1
```
## Verify that data persists in the database
Open a browser and view the application at [http://localhost:8080](http://localhost:8080). You should see a simple web application with the text `Student name is Whale Moby`.
Press `ctrl+c` in the terminal to stop your application.
In the terminal, run `docker compose rm` to remove your containers and then run `docker compose up` to run your application again.
```console
$ docker compose rm
$ docker compose up --build
```
Refresh [http://localhost:8080](http://localhost:8080) in your browser and verify that the student name persisted, even after the containers were removed and ran again.
Press `ctrl+c` in the terminal to stop your application.
## Automatically update services
Use Compose Watch to automatically update your running Compose services as you edit and save your code. For more details about Compose Watch, see [Use Compose Watch](../../compose/file-watch.md).
Open your `compose.yaml` file in an IDE or text editor and then add the Compose Watch instructions. The following is the updated `compose.yaml` file.
```yaml
services:
server:
build:
context: .
target: final
ports:
- 8080:80
depends_on:
db:
condition: service_healthy
develop:
watch:
- action: rebuild
path: .
db:
image: postgres
restart: always
user: postgres
secrets:
- db-password
volumes:
- db-data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?database password not set}
- POSTGRES_DB=example
- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
expose:
- 5432
healthcheck:
test: [ "CMD", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres-data:/var/lib/postgresql/data
adminer:
image: adminer
restart: always
ports:
- 8080:8080
app:
build:
context: .
ports:
- 5000:80
depends_on:
- db
volumes:
postgres-data:
db-data:
secrets:
db-password:
file: db/password.txt
```
`POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?database password not set}` means that if the environment variable `POSTGRES_PASSWORD` is not set on the host, Docker Compose will display an error. This is OK, because we dont want to hard-code default values for the password. We set the password value in the `.env` file, which is local to our machine. It is always a good idea to add `.env` to `.gitignore` to prevent the secrets being checked into the version control.
Build and run your application to confirm the changes are applied properly.
Run the following command to run your application with Compose Watch.
```console
$ docker compose up --build -d
$ docker compose watch
```
Now lets test our application. Using a web browser, access `http://localhost:5000` to view the page.
Open a browser and verify that the application is running at [http://localhost:8080](http://localhost:8080).
Any changes to the application's source files on your local machine will now be
immediately reflected in the running container.
Open `docker-dotnet-sample/src/Pages/Index.cshtml` in an IDE or text editor and update the student name text on line 13 from `Student name is` to `Student name:`.
```diff
- <p>Student Name is @Model.StudentName</p>
+ <p>Student name: @Model.StudentName</p>
```
Save the changes to `Index.cshmtl` and then wait a few seconds for the application to rebuild. Refresh [http://localhost:8080](http://localhost:8080) in your browser and verify that the updated text appears.
Press `ctrl+c` in the terminal to stop your application.
## Create a development container
At this point, when you run your containerized application, it's using the .NET runtime image. While this small image is good for production, it lacks the SDK tools and dependencies you may need when developing. Also, during development, you may not need to run `dotnet publish`. You can use multi-stage builds to build stages for both development and production in the same Dockerfile. For more details, see [Multi-stage builds](../../build/building/multi-stage.md).
Add a new development stage to your Dockerfile and update your `compose.yaml` file to use this stage for local development.
The following is the updated Dockerfile.
```Dockerfile
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS build
ARG TARGETARCH
COPY . /source
WORKDIR /source/src
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS development
COPY . /source
WORKDIR /source/src
CMD dotnet run --no-launch-profile
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS final
WORKDIR /app
COPY --from=build /app .
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
ENTRYPOINT ["dotnet", "myWebApp.dll"]
```
The following is the updated `compose.yaml` file.
```yaml
services:
server:
build:
context: .
target: development
ports:
- 8080:80
depends_on:
db:
condition: service_healthy
develop:
watch:
- action: rebuild
path: .
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:80'
db:
image: postgres
restart: always
user: postgres
secrets:
- db-password
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=example
- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
expose:
- 5432
healthcheck:
test: [ "CMD", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
```
Your containerized application will now use the `mcr.microsoft.com/dotnet/sdk:6.0-alpine` image, which includes development tools like `dotnet test`. Continue to the next section to learn how you can run `dotnet test`.
## Summary
In this section, you took a look at setting up your Compose file to add a local
database and persist data. You also learned how to use Compose Watch to automatically rebuild and run your container when you update your code. And finally, you learned how to create a development container that contains the SDK tools and dependencies needed for development.
Related information:
- [Compose file reference](/compose/compose-file/)
- [Compose file watch](../../compose/file-watch.md)
- [Multi-stage builds](../../build/building/multi-stage.md)
## Next steps
In the next module, well take a look at how to write containerized tests in Docker. See:
In the next section, you'll learn how to run unit tests using Docker.
{{< button text="Run your tests" url="run-tests.md" >}}
## Feedback
Help us improve this topic by providing your feedback. Let us know what you think by creating an issue in the [Docker Docs](https://github.com/docker/docker.github.io/issues/new?title=[dotnet%20docs%20feedback]) GitHub repository. Alternatively, [create a PR](https://github.com/docker/docker.github.io/pulls) to suggest updates.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,168 +0,0 @@
---
title: Run your image as a container
keywords: .net, run, image, container,
description: Learn how to run the image as a container.
---
## Prerequisites
Work through the steps to build a .NET image in [Build your .NET image](build-images.md).
## Overview
In the previous module, we created our sample application and then we created a Dockerfile that we used to produce an image. We created our image using the docker command docker build. Now that we have an image, we can run that image and see if our application is running correctly.
A container is a normal operating system process except that this process is isolated in that it has its own file system, its own networking, and its own isolated process tree separate from the host.
To run an image inside of a container, we use the `docker run` command. The `docker run` command requires one parameter which is the name of the image. Lets start our image and make sure it is running correctly. Run the following command in your terminal.
```console
$ docker run dotnet-docker
```
After running this command, youll notice that you were not returned to the command prompt. This is because our application is a server and runs in a loop waiting for incoming requests without returning control back to the OS until we stop the container.
Lets open a new browser and access `http://localhost:80`.
As you'll see, the connection to our server was refused. This means, we were not able to connect to the localhost on port 80. This is expected because our container is running in isolation which includes networking. Lets stop the container and restart with port 5000 published on our local network.
To stop the container, press Ctrl-C. This will return you to the terminal prompt.
To publish a port for our container, well use the `--publish` flag (`-p` for short) on the `docker run` command. The format of the `--publish` command is `[host port]:[container port]`. So, if we wanted to expose port 80 inside the container to port 5000 outside the container, we would pass `5000:80` to the `--publish` flag. Run the container using the following command:
```console
$ docker run --publish 5000:80 dotnet-docker
```
Now, let's access `http://localhost:5000` in a browser. You should see a page similar to the following image.
![Image of app page](./images/dotnet-app-verify-build.png)
Success! We were able to connect to the application running inside of our container on port 80.
Press Ctrl-C to stop the container.
## Run in detached mode
This is great so far, but our sample application is a web server and we don't have to be connected to the container. Docker can run your container in detached mode or in the background. To do this, we can use the `--detach` or `-d` for short. Docker starts your container the same as before but this time will “detach” from the container and return you to the terminal prompt.
```console
$ docker run -d -p 5000:80 dotnet-docker
ce02b3179f0f10085db9edfccd731101868f58631bdf918ca490ff6fd223a93b
```
Docker started our container in the background and printed the Container ID on the terminal.
Again, lets make sure that our container is running properly. In a web browser, access `http://localhost:5000`. You should see a page similar to the following image.
![Image of app page](./images/dotnet-app-verify-build.png)
## List containers
Since we ran our container in the background, how do we know if our container is running or what other containers are running on our machine? Well, we can run the `docker ps` command. Just like on Linux to see a list of processes on your machine, we would run the `ps` command. In the same spirit, we can run the `docker ps` command which displays a list of containers running on our machine.
```console
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce02b3179f0f dotnet-docker "./myWebApp" 6 minutes ago Up 6 minutes 0.0.0.0:5000->80/tcp wonderful_kalam
```
The `docker ps` command provides a bunch of information about our running containers. We can see the container ID, The image running inside the container, the command that was used to start the container, when it was created, the status, ports that exposed and the name of the container.
You are probably wondering where the name of our container is coming from. Since we didnt provide a name for the container when we started it, Docker generated a random name. Well fix this in a minute, but first we need to stop the container. To stop the container, run the `docker stop` command which does just that, stops the container. You need to pass the name of the container or you can use the container ID.
```console
$ docker stop wonderful_kalam
wonderful_kalam
```
Now, rerun the `docker ps` command to see a list of running containers.
```console
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
```
## Stop, start, and name containers
You can start, stop, and restart Docker containers. When we stop a container, it is not removed, but the status is changed to stopped and the process inside the container is stopped. When we ran the `docker ps` command in the previous module, the default output only shows running containers. When we pass the `--all` or `-a` for short, we see all containers on our machine, irrespective of their start or stop status.
```console
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce02b3179f0f dotnet-docker "./myWebApp" 16 minutes ago Exited (0) 5 minutes ago wonderful_kalam
ec45285c456d dotnet-docker "./myWebApp" 28 minutes ago Exited (0) 20 minutes ago agitated_moser
fb7a41809e5d dotnet-docker "./myWebApp" 37 minutes ago Exited (0) 36 minutes ago goofy_khayyam
```
You should now see several containers listed. These are containers that we started and stopped but have not been removed.
Lets restart the container that we just stopped. Locate the name of the container we just stopped and replace the name of the container below in the restart command.
```console
$ docker restart wonderful_kalam
```
Now list all the containers again using the `docker ps` command.
```console
$ docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce02b3179f0f dotnet-docker "./myWebApp" 19 minutes ago Up 8 seconds 0.0.0.0:5000->80/tcp wonderful_kalam
ec45285c456d dotnet-docker "./myWebApp" 31 minutes ago Exited (0) 23 minutes ago agitated_moser
fb7a41809e5d dotnet-docker "./myWebApp" 40 minutes ago Exited (0) 39 minutes ago goofy_khayyam
```
Notice that the container we just restarted has been started in detached mode and has port 80 exposed. Also, observe the status of the container is `Up X seconds`. When you restart a container, it starts with the same flags or commands that it was originally started with.
Now, lets stop and remove all of our containers and take a look at fixing the random naming issue. Stop the container we just started. Find the name of your running container and replace the name in the command below with the name of the container on your system.
```console
$ docker stop wonderful_kalam
wonderful_kalam
```
Now that all of our containers are stopped, lets remove them. When you remove a container, it is no longer running, nor it is in the stopped status, but the process inside the container has been stopped and the metadata for the container has been removed.
```console
$ docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce02b3179f0f dotnet-docker "./myWebApp" 19 minutes ago Exited (0) 2 minutes ago wonderful_kalam
ec45285c456d dotnet-docker "./myWebApp" 31 minutes ago Exited (0) 23 minutes ago agitated_moser
fb7a41809e5d dotnet-docker "./myWebApp" 40 minutes ago Exited (0) 39 minutes ago goofy_khayyam
```
To remove a container, simple run the `docker rm` command passing the container name. You can pass multiple container names to the command using a single command. Again, replace the container names in the following command with the container names from your system.
```console
$ docker rm wonderful_kalam agitated_moser goofy_khayyam
wonderful_kalam
agitated_moser
goofy_khayyam
```
Run the `docker ps --all` command again to see that all containers are removed.
Now, lets address the random naming issue. Standard practice is to name your containers for the simple reason that it is easier to identify what is running in the container and what application or service it is associated with.
To name a container, we just need to pass the `--name` flag to the `docker run` command.
```console
$ docker run -d -p 5000:80 --name dotnet-app dotnet-docker
1aa5d46418a68705c81782a58456a4ccdb56a309cb5e6bd399478d01eaa5cdda
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1aa5d46418a6 dotnet-docker "./myWebApp" 3 seconds ago Up 3 seconds 0.0.0.0:5000->80/tcp dotnet-app
```
Thats better! We can now easily identify our container based on the name.
## Next steps
In this module, we took a look at running containers, publishing ports, and running containers in detached mode. We also took a look at managing containers by starting, stopping, and, restarting them. We also looked at naming our containers so they are more easily identifiable. In the next module, well learn how to run a database in a container and connect it to our application. See:
{{< button text="How to develop your application" url="develop.md" >}}
## Feedback
Help us improve this topic by providing your feedback. Let us know what you think by creating an issue in the [Docker Docs](https://github.com/docker/docker.github.io/issues/new?title=[dotnet%20docs%20feedback]) GitHub repository. Alternatively, [create a PR](https://github.com/docker/docker.github.io/pulls) to suggest updates.

View File

@ -1,142 +1,117 @@
---
title: Run your tests
keywords: .NET, build, test
description: How to build and run your tests
title: Run .NET tests in a container
keywords: .NET, test
description: Learn how to run your .NET tests in a container.
---
## Prerequisites
Work through the steps to build an image and run it as a containerized application in [Use containers for development](develop.md).
Complete all the previous sections of this guide, starting with [Containerize a .NET application](containerize.md).
## Introduction
## Overview
Testing is an essential part of modern software development. In combination with 3rd party frameworks or services, Docker helps to test applications without mocks or complicated environment configurations fast and reliably.
Testing is an essential part of modern software development. Testing can mean a
lot of things to different development teams. There are unit tests, integration
tests and end-to-end testing. In this guide you take a look at running your unit
tests in Docker when developing and when building.
## Add .NET test project
## Run tests when developing locally
To test our sample application, we create a standalone test project from a template using the .NET CLI. On your local machine, open a terminal, change the directory to the `dotnet-docker` directory and run the following command:
The sample application already has an xUnit test inside the `tests` directory. When developing locally, you can use Compose to run your tests.
Run the following command in the `docker-dotnet-sample` directory to run the tests inside a container.
```console
$ cd /path/to/dotnet-docker
$ dotnet new xunit -n myWebApp.Tests -o tests
$ docker compose run --build --rm server dotnet test /source/tests
```
Next, we'll update the test project and add the Testcontainers for .NET package that allows us to run tests against Docker resources (PostgreSQL container). Switch to the `tests` directory and run the following command:
```console
$ dotnet add package Testcontainers --version 2.3.0
```
## Add a test
Open the test project in your favorite IDE and replace the contents of `UnitTest1.cs` with the following code:
```c#
using System.Net;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Networks;
public sealed class UnitTest1 : IAsyncLifetime, IDisposable
{
private const ushort HttpPort = 80;
private readonly CancellationTokenSource _cts = new(TimeSpan.FromMinutes(1));
private readonly IDockerNetwork _network;
private readonly IDockerContainer _dbContainer;
private readonly IDockerContainer _appContainer;
public UnitTest1()
{
_network = new TestcontainersNetworkBuilder()
.WithName(Guid.NewGuid().ToString("D"))
.Build();
_dbContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("postgres")
.WithNetwork(_network)
.WithNetworkAliases("db")
.WithVolumeMount("postgres-data", "/var/lib/postgresql/data")
.Build();
_appContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("dotnet-docker")
.WithNetwork(_network)
.WithPortBinding(HttpPort, true)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(HttpPort))
.Build();
}
public async Task InitializeAsync()
{
await _network.CreateAsync(_cts.Token)
.ConfigureAwait(false);
await _dbContainer.StartAsync(_cts.Token)
.ConfigureAwait(false);
await _appContainer.StartAsync(_cts.Token)
.ConfigureAwait(false);
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
public void Dispose()
{
_cts.Dispose();
}
[Fact]
public async Task Test1()
{
using var httpClient = new HttpClient();
httpClient.BaseAddress = new UriBuilder("http", _appContainer.Hostname, _appContainer.GetMappedPublicPort(HttpPort)).Uri;
var httpResponseMessage = await httpClient.GetAsync(string.Empty)
.ConfigureAwait(false);
var body = await httpResponseMessage.Content.ReadAsStringAsync()
.ConfigureAwait(false);
Assert.Equal(HttpStatusCode.OK, httpResponseMessage.StatusCode);
Assert.Contains("Welcome", body);
}
}
```
The test class picks up the configurations and lessons we learned in the previous steps. It connects our application and database through a custom Docker network and runs an HTTP request against our application. As you can see, running containerized tests allows us to test applications without mocks or complicated environment configurations. The tests run on any Docker-API compatible environments including CI.
## Run the test
Before you run the test, [stop](run-containers.md#stop-start-and-name-containers) any running containers from the previous sections.
To run the test, change directory to the `dotnet-docker` directory and run the following `dotnet test` command:
```console
$ dotnet test tests
```
You should see output like the following:
You should see output that contains the following.
```console
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - myWebApp.Tests.dll (net6.0)
Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - /source/tests/bin/Debug/net6.0/tests.dll (net6.0)
```
To learn more about the command, see [docker compose run](/engine/reference/commandline/compose_run/).
## Run tests when building
To run your tests when building, you need to update your Dockerfile. You can create a new test stage that runs the tests, or run the tests in the existing build stage. For this guide, update the Dockerfile to run the tests in the build stage.
The following is the updated Dockerfile.
```dockerfile
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS build
ARG TARGETARCH
COPY . /source
WORKDIR /source/src
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
RUN dotnet test /source/tests
FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS development
COPY . /source
WORKDIR /source/src
CMD dotnet run --no-launch-profile
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS final
WORKDIR /app
COPY --from=build /app .
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
appuser
USER appuser
ENTRYPOINT ["dotnet", "myWebApp.dll"]
```
Run the following command to build an image using the build stage as the target and view the test results. Include `--progress=plain` to view the build output, `--no-cache` to ensure the tests always run, and `--target build` to target the build stage.
```console
$ docker build -t dotnet-docker-image-test --progress=plain --no-cache --target build .
```
You should see output containing the following.
```console
#11 [build 5/5] RUN dotnet test /source/tests
#11 1.564 Determining projects to restore...
#11 3.421 Restored /source/src/myWebApp.csproj (in 1.02 sec).
#11 19.42 Restored /source/tests/tests.csproj (in 17.05 sec).
#11 27.91 myWebApp -> /source/src/bin/Debug/net6.0/myWebApp.dll
#11 28.47 tests -> /source/tests/bin/Debug/net6.0/tests.dll
#11 28.49 Test run for /source/tests/bin/Debug/net6.0/tests.dll (.NETCoreApp,Version=v6.0)
#11 28.67 Microsoft (R) Test Execution Command Line Tool Version 17.3.3 (x64)
#11 28.67 Copyright (c) Microsoft Corporation. All rights reserved.
#11 28.68
#11 28.97 Starting test execution, please wait...
#11 29.03 A total of 1 test files matched the specified pattern.
#11 32.07
#11 32.08 Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: < 1 ms - /source/tests/bin/Debug/net6.0/tests.dll (net6.0)
#11 DONE 32.2s
```
To learn more about building and running tests, see the [Build with Docker guide](../../build/guide/_index.md).
## Summary
In this section, you learned how to run tests when developing locally using Compose and how to run tests when building your image.
Related information:
- [docker compose run](/engine/reference/commandline/compose_run/)
- [Build with Docker guide](../../build/guide/index.md)
## Next steps
In the next module, well take a look at how to set up a CI/CD pipeline using GitHub Actions. See:
Next, youll learn how to set up a CI/CD pipeline using GitHub Actions.
{{< button text="Configure CI/CD" url="configure-ci-cd.md" >}}
## Feedback
Help us improve this topic by providing your feedback. Let us know what you think by creating an issue in the [Docker Docs](https://github.com/docker/docker.github.io/issues/new?title=[dotnet%20docs%20feedback]) GitHub repository. Alternatively, [create a PR](https://github.com/docker/docker.github.io/pulls) to suggest updates.

View File

@ -93,10 +93,8 @@ Guides:
section:
- title: "Overview"
path: /language/dotnet/
- title: "Build images"
path: /language/dotnet/build-images/
- title: "Run containers"
path: /language/dotnet/run-containers/
- title: "Containerize your app"
path: /language/dotnet/containerize/
- title: "Develop your app"
path: /language/dotnet/develop/
- title: "Run your tests"