Esercitazione: Containerizzare un'app .NET
Questa esercitazione illustra come inserire in contenitori un'applicazione .NET con Docker. I contenitori hanno molte funzionalità e vantaggi, ad esempio un'infrastruttura non modificabile, offrono un'architettura portabile e consentono la scalabilità. L'immagine può essere usata per creare contenitori per l'ambiente di sviluppo locale, il cloud privato o il cloud pubblico.
In questa esercitazione, tu:
- Creare e pubblicare una semplice app .NET
- Creare e configurare un Dockerfile per .NET
- Creare un'immagine Docker
- Creare ed eseguire un contenitore Docker
Si esplorano le attività di compilazione e distribuzione del contenitore Docker per un'applicazione .NET. La piattaforma Docker usa il motore Docker per creare e impacchettare rapidamente app come immagini Docker . Queste immagini vengono scritte nel formato Dockerfile da distribuire ed eseguire in un contenitore a più livelli.
Suggerimento
Se sei interessato a pubblicare la tua app .NET come contenitore senza dover usare Docker o Podman, consulta Creare un contenitore per un'app .NET con dotnet publish.
Nota
Questa esercitazione non è per le app ASP.NET Core. Se utilizzi ASP.NET Core, consulta il tutorial Scopri come containerizzare un'applicazione ASP.NET Core.
Prerequisiti
Installare i prerequisiti seguenti:
-
.NET 8+ SDK.
Se è installato .NET, usare il comandodotnet --info
per determinare quale SDK si sta usando. - Docker Community Edition.
- Cartella di lavoro temporanea per il Dockerfile e l'applicazione di esempio .NET. Il nome docker-working viene usato come cartella di lavoro in questa esercitazione.
Creare un'app .NET
È necessaria un'app .NET eseguita dal contenitore Docker. Apri il terminale, crea una cartella di lavoro se non l'hai già fatto, ed entra al suo interno. Nella cartella di lavoro eseguire il comando seguente per creare un nuovo progetto in una sottodirectory denominata App:
dotnet new console -o App -n DotNet.Docker
L'albero delle cartelle è simile alla struttura di directory seguente:
📁 docker-working
└──📂 App
├──DotNet.Docker.csproj
├──Program.cs
└──📂 obj
├── DotNet.Docker.csproj.nuget.dgspec.json
├── DotNet.Docker.csproj.nuget.g.props
├── DotNet.Docker.csproj.nuget.g.targets
├── project.assets.json
└── project.nuget.cache
Il comando dotnet new
crea una nuova cartella denominata App e genera un'applicazione console "Hello World". Ora, cambi directory e ti sposti nella cartella App dalla sessione del terminale. Usare il comando dotnet run
per avviare l'app. L'applicazione viene eseguita e stampa Hello World!
sotto il comando :
cd App
dotnet run
Hello World!
Il modello predefinito crea un'app che stampa sul terminale e quindi termina immediatamente. Per questa esercitazione si usa un'app che esegue un ciclo illimitato. Aprire il file Program.cs in un editor di testo.
Suggerimento
Se si usa Visual Studio Code, dalla sessione del terminale precedente digitare il comando seguente:
code .
Questo comando apre la cartella App che contiene il progetto in Visual Studio Code.
Il Program.cs dovrebbe essere simile al codice C# seguente:
Console.WriteLine("Hello World!");
Sostituire il file con il codice seguente che conta i numeri ogni secondo:
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
var counter = 0;
var max = args.Length is not 0 ? Convert.ToInt32(args[0]) : -1;
while (max is -1 || counter < max)
{
Console.WriteLine($"Counter: {++counter}");
await Task.Delay(TimeSpan.FromMilliseconds(1_000));
}
Salvare il file e testare di nuovo il programma con dotnet run
. Tenere presente che questa app viene eseguita per un periodo illimitato. Usare il comando cancel CTRL+C per arrestarlo. Si consideri l'output di esempio seguente:
dotnet run
Counter: 1
Counter: 2
Counter: 3
Counter: 4
^C
Se passi un numero nella riga di comando all'applicazione, limita il conteggio a tale numero e quindi esce. Prova con dotnet run -- 5
per contare fino a cinque.
Importante
Tutti i parametri dopo --
non vengono passati al comando dotnet run
e vengono invece passati all'applicazione.
Pubblicare un'app .NET
Affinché l'app sia adatta per la creazione di un'immagine, deve essere compilata. Il comando dotnet publish
è più adatto a questo, perché compila e pubblica l'app. Per informazioni di riferimento approfondite, vedere documentazione sui comandi dotnet build e dotnet publish.
dotnet publish -c Release
Mancia
Se sei interessato a pubblicare la tua app .NET come contenitore senza la necessità di Docker, vedere Containerizzare un'app .NET con dotnet publish.
Il comando
- Windows
- Linux
Dalla cartella App ottenere un elenco di directory della cartella di pubblicazione per verificare che il file DotNet.Docker.dll sia stato creato.
dir .\bin\Release\net9.0\publish\
Directory: C:\Users\default\docker-working\App\bin\Release\net9.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 1/6/2025 10:11 AM 431 DotNet.Docker.deps.json
-a---- 1/6/2025 10:11 AM 6144 DotNet.Docker.dll
-a---- 1/6/2025 10:11 AM 145408 DotNet.Docker.exe
-a---- 1/6/2025 10:11 AM 11716 DotNet.Docker.pdb
-a---- 1/6/2025 10:11 AM 340 DotNet.Docker.runtimeconfig.json
dir .\bin\Release\net8.0\publish\
Directory: C:\Users\default\docker-working\App\bin\Release\net8.0\publish
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 9/22/2023 9:17 AM 431 DotNet.Docker.deps.json
-a--- 9/22/2023 9:17 AM 6144 DotNet.Docker.dll
-a--- 9/22/2023 9:17 AM 157696 DotNet.Docker.exe
-a--- 9/22/2023 9:17 AM 11688 DotNet.Docker.pdb
-a--- 9/22/2023 9:17 AM 353 DotNet.Docker.runtimeconfig.json
Creare il Dockerfile
Il file Dockerfile viene usato dal comando docker build
per creare un'immagine di un container. Questo file è un file di testo denominato Dockerfile che non ha un'estensione.
Creare un file denominato Dockerfile nella directory contenente il .csproj e aprirlo in un editor di testo. Questa esercitazione usa l'immagine di runtime di ASP.NET Core (che contiene l'immagine di runtime .NET) e corrisponde all'applicazione console .NET.
FROM mcr.microsoft.com/dotnet/sdk:9.0@sha256:3fcf6f1e809c0553f9feb222369f58749af314af6f063f389cbd2f913b4ad556 AS build
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0@sha256:b4bea3a52a0a77317fa93c5bbdb076623f81e3e2f201078d89914da71318b5d8
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
Nota
L'immagine di runtime ASP.NET Core viene usata intenzionalmente in questo caso, anche se è possibile usare l'immagine mcr.microsoft.com/dotnet/runtime:9.0
.
FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build
WORKDIR /App
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0@sha256:6c4df091e4e531bb93bdbfe7e7f0998e7ced344f54426b7e874116a3dc3233ff
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
Nota
L'immagine di runtime ASP.NET Core viene usata intenzionalmente qui, anche se è possibile usare l'immagine mcr.microsoft.com/dotnet/runtime:8.0
.
Importante
L'inclusione di un algoritmo hash sicuro (SHA) dopo il tag immagine in un Dockerfile è una procedura consigliata. In questo modo si garantisce che l'immagine non venga manomessa e che l'immagine sia identica a quella prevista. SHA è un identificatore univoco per l'immagine. Per altre informazioni, vedere Docker Docs: Eseguire il pull di un'immagine in base al digest.
Mancia
Questo Dockerfile utilizza build a più stadi, che ottimizzano le dimensioni finali dell'immagine stratificando il processo di build e conservando solamente gli artefatti necessari. Per altre informazioni, vedere Docker Docs: compilazioni a più fasi.
La parola chiave FROM
richiede un nome completo dell'immagine del contenitore Docker. Microsoft Container Registry (MCR, mcr.microsoft.com) è un sindacato dell'hub Docker, che ospita contenitori accessibili pubblicamente. Il segmento dotnet
è il repository dei contenitori, mentre il segmento sdk
o aspnet
è il nome dell'immagine del container. L'immagine viene contrassegnata con 9.0
, che viene usata per il controllo delle versioni. Di conseguenza, mcr.microsoft.com/dotnet/aspnet:9.0
è il runtime di .NET 9.0. Accertati di ottenere la versione di runtime che corrisponde al runtime a cui è destinato l'SDK. Ad esempio, l'app creata nella sezione precedente usa .NET 9.0 SDK e l'immagine di base a cui si fa riferimento nella Dockerfile viene contrassegnata con 9.0.
Importante
Quando si usano immagini del contenitore basate su Windows, è necessario specificare il tag di immagine oltre semplicemente 9.0
, ad esempio mcr.microsoft.com/dotnet/aspnet:9.0-nanoserver-1809
anziché mcr.microsoft.com/dotnet/aspnet:9.0
. Selezionare un nome di immagine in base al fatto che si usi Nano Server o Windows Server Core e quale versione del sistema operativo.
È possibile trovare un elenco completo di tutti i tag supportati sulla pagina dell'hub Docker di .NET.
Salva il file Dockerfile. La struttura di directory della cartella di lavoro dovrebbe essere simile alla seguente. Alcuni file e cartelle di livello più approfondito vengono omessi per risparmiare spazio nell'articolo:
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └───📂 Release
│ └───📂 net9.0
│ ├───📂 publish
│ │ ├─── DotNet.Docker.deps.json
│ │ ├─── DotNet.Docker.dll
│ │ ├─── DotNet.Docker.exe
│ │ ├─── DotNet.Docker.pdb
│ │ └─── DotNet.Docker.runtimeconfig.json
│ ├─── DotNet.Docker.deps.json
│ ├─── DotNet.Docker.dll
│ ├─── DotNet.Docker.exe
│ ├─── DotNet.Docker.pdb
│ └─── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
La parola chiave FROM
richiede un nome completamente qualificato dell'immagine del contenitore Docker. Microsoft Container Registry (MCR, mcr.microsoft.com) è un sindacato dell'hub Docker, che ospita contenitori accessibili pubblicamente. Il segmento dotnet
è il repository di contenitori, mentre il segmento sdk
o aspnet
è il nome dell'immagine del contenitore. L'immagine viene contrassegnata con 8.0
, che viene usata per il controllo delle versioni. Di conseguenza, mcr.microsoft.com/dotnet/aspnet:8.0
è il runtime di .NET 8.0. Assicurati di scaricare la versione di runtime corrispondente al runtime mirato dal tuo SDK. Ad esempio, l'app creata nella sezione precedente usa .NET 8.0 SDK e l'immagine di base a cui si fa riferimento nella Dockerfile viene contrassegnata con 8.0.
Importante
Quando si usano immagini del contenitore basate su Windows, è necessario specificare il tag di immagine oltre semplicemente 8.0
, ad esempio mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-1809
anziché mcr.microsoft.com/dotnet/aspnet:8.0
. Selezionare un nome di immagine in base al fatto che si usi Nano Server o Windows Server Core e quale versione del sistema operativo. È possibile trovare un elenco completo di tutti i tag supportati sulla pagina dell'hub Docker di .NET .
Salvare il file Dockerfile. La struttura di directory della cartella di lavoro dovrebbe essere simile alla seguente. Alcuni file e cartelle di livello più approfondito vengono omessi per risparmiare spazio nell'articolo:
📁 docker-working
└──📂 App
├── Dockerfile
├── DotNet.Docker.csproj
├── Program.cs
├──📂 bin
│ └──📂 Release
│ └──📂 net8.0
│ └──📂 publish
│ ├── DotNet.Docker.deps.json
│ ├── DotNet.Docker.exe
│ ├── DotNet.Docker.dll
│ ├── DotNet.Docker.pdb
│ └── DotNet.Docker.runtimeconfig.json
└──📁 obj
└──...
L'istruzione ENTRYPOINT
imposta dotnet
come host per l'DotNet.Docker.dll
. Tuttavia, è possibile invece definire il ENTRYPOINT
come l'eseguibile dell'app stessa, facendo affidamento sul sistema operativo come host dell'applicazione.
ENTRYPOINT ["./DotNet.Docker"]
In questo modo l'app viene eseguita direttamente, senza dotnet
e si basa invece sull'host dell'app e sul sistema operativo sottostante. Per altre informazioni sulla distribuzione di file binari multipiattaforma, vedere Produrre un file binario multipiattaforma.
Per compilare il contenitore, eseguire il comando seguente dal terminale:
docker build -t counter-image -f Dockerfile .
Docker elabora ogni riga nel Dockerfile . Il .
nel comando docker build
imposta il contesto di compilazione dell'immagine. Il commutatore -f
è il percorso al Dockerfile . Questo comando costruisce l'immagine e crea un repository locale denominato counter-image che punta a tale immagine. Al termine di questo comando, eseguire docker images
per visualizzare un elenco di immagini installate:
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 1c1f1433e51d 32 seconds ago 223MB
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
counter-image latest 2f15637dc1f6 10 minutes ago 217MB
Il repository counter-image
è il nome dell'immagine. Inoltre, il tag immagine, l'identificatore dell'immagine, le dimensioni e quando è stato creato fanno parte dell'output. I passaggi finali del Dockerfile consentono di creare un contenitore dall'immagine ed eseguire l'app, copiare l'app pubblicata nel contenitore e definire il punto di ingresso:
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /App
COPY --from=build /App/out .
ENTRYPOINT ["dotnet", "DotNet.Docker.dll"]
Il comando FROM
specifica l'immagine di base e il tag da usare. Il comando WORKDIR
modifica la directory corrente all'interno del contenitore in App.
Il comando COPY
indica a Docker di copiare la directory di origine specificata in una cartella di destinazione. In questo esempio, i pubblicano contenuto nel livello build
vengono restituiti nella cartella denominata app/out , quindi è l'origine da cui copiare. Tutti i contenuti pubblicati nella directory App/out vengono copiati nella directory di lavoro corrente (App).
Il comando successivo, ENTRYPOINT
, indica a Docker di configurare il contenitore in modo che funzioni come un eseguibile. All'avvio del contenitore, viene eseguito il comando ENTRYPOINT
. Al termine di questo comando, il contenitore si arresta automaticamente.
Mancia
Prima di .NET 8, i container configurati per l'esecuzione come di sola lettura potrebbero fallire con Failed to create CoreCLR, HRESULT: 0x8007000E
. Per risolvere questo problema, specificare una variabile d'ambiente DOTNET_EnableDiagnostics
come 0
(immediatamente prima del passaggio ENTRYPOINT
):
ENV DOTNET_EnableDiagnostics=0
Per ulteriori informazioni sulle diverse variabili di ambiente .NET, vedere variabili di ambiente .NET.
Nota
.NET 6 standardizza il prefisso DOTNET_
anziché COMPlus_
per le variabili di ambiente che configurano il comportamento di runtime di .NET. Tuttavia, il prefisso COMPlus_
continuerà a funzionare. Se si usa una versione precedente del runtime .NET, è comunque consigliabile usare il prefisso COMPlus_
per le variabili di ambiente.
Creare un contenitore
Dopo aver creato un'immagine che contiene l'app, è possibile creare un contenitore. È possibile creare un contenitore in due modi. Prima, crea un nuovo contenitore che sia fermo.
docker create --name core-counter counter-image
Questo comando docker create
crea un contenitore basato sull'immagine contatore immagine. L'output del comando docker create
mostra l'ID CONTENITORE del contenitore (l'identificatore sarà diverso):
d0be06126f7db6dd1cee369d911262a353c9b7fb4829a0c11b4b2eb7b2d429cf
Per visualizzare un elenco di tutti i contenitori, usare il comando docker ps -a
:
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d0be06126f7d counter-image "dotnet DotNet.Docke…" 12 seconds ago Created core-counter
Gestire il contenitore
Il contenitore è stato creato con un nome specifico core-counter
. Questo nome viene usato per gestire il contenitore. L'esempio seguente usa il comando docker start
per avviare il contenitore e quindi usa il comando docker ps
per visualizzare solo i contenitori in esecuzione:
docker start core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf01364df453 counter-image "dotnet DotNet.Docke…" 53 seconds ago Up 10 seconds core-counter
Analogamente, il comando docker stop
arresta il contenitore. L'esempio seguente usa il comando docker stop
per arrestare il contenitore e quindi usa il comando docker ps
per indicare che non sono in esecuzione contenitori:
docker stop core-counter
core-counter
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Connettersi a un contenitore
Dopo l'esecuzione di un contenitore, è possibile connettersi a esso per visualizzare l'output. Usare i comandi docker start
e docker attach
per avviare il contenitore e visualizzare il flusso di output. In questo esempio, la sequenza di tasti Ctrl+C viene usata per scollegare il contenitore in esecuzione. Questa sequenza di tasti termina il processo nel contenitore a meno che non sia specificato diversamente; in tal caso, il contenitore verrà arrestato. Il parametro --sig-proxy=false
garantisce che Ctrl+C non arresti il processo nel contenitore.
Dopo la disconnessione dal contenitore, ricollegare per verificare che sia ancora in esecuzione e conteggio.
docker start core-counter
core-counter
docker attach --sig-proxy=false core-counter
Counter: 7
Counter: 8
Counter: 9
^C
docker attach --sig-proxy=false core-counter
Counter: 17
Counter: 18
Counter: 19
^C
Eliminare un contenitore
Per questo articolo, non vuoi che i contenitori restino inattivi. Eliminare il contenitore creato in precedenza. Se il contenitore è in esecuzione, fermalo.
docker stop core-counter
Nell'esempio seguente sono elencati tutti i contenitori. Usa quindi il comando docker rm
per eliminare il contenitore e quindi controlla una seconda volta per eventuali contenitori in esecuzione.
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2f6424a7ddce counter-image "dotnet DotNet.Dock…" 7 minutes ago Exited (143) 20 seconds ago core-counter
docker rm core-counter
core-counter
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Esecuzione singola
Docker fornisce il comando docker run
per creare ed eseguire il contenitore come singolo comando. Questo comando elimina la necessità di eseguire docker create
e quindi docker start
. È anche possibile impostare questo comando per eliminare automaticamente il contenitore all'arresto del contenitore. Ad esempio, usa docker run -it --rm
per eseguire due operazioni: innanzitutto, usa automaticamente il terminale corrente per connettersi al contenitore e poi, quando il contenitore si chiude, rimuovilo.
docker run -it --rm counter-image
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
^C
Il contenitore passa anche i parametri all'esecuzione dell'app .NET. Per indicare all'app .NET di contare solo fino a tre, passare 3.
docker run -it --rm counter-image 3
Counter: 1
Counter: 2
Counter: 3
Con docker run -it
, il comando Ctrl+C arresta il processo in esecuzione nel contenitore, che a sua volta arresta il contenitore. Poiché è stato specificato il parametro --rm
, il contenitore viene eliminato automaticamente quando il processo viene arrestato. Assicurati che non esista.
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Modificare ENTRYPOINT
Il comando bash
o cmd.exe
. Modificare il comando in base alle esigenze.
- Windows
- Linux
In questo esempio ENTRYPOINT
viene modificato in cmd.exe
.
Viene premuto CTRL+C per terminare il processo e fermare il contenitore.
docker run -it --rm --entrypoint "cmd.exe" counter-image
Microsoft Windows [Version 10.0.17763.379]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\>dir
Volume in drive C has no label.
Volume Serial Number is 3005-1E84
Directory of C:\
04/09/2019 08:46 AM <DIR> app
03/07/2019 10:25 AM 5,510 License.txt
04/02/2019 01:35 PM <DIR> Program Files
04/09/2019 01:06 PM <DIR> Users
04/02/2019 01:35 PM <DIR> Windows
1 File(s) 5,510 bytes
4 Dir(s) 21,246,517,248 bytes free
C:\>^C
Nota
Questo esempio funziona solo nei contenitori di Windows. I contenitori Linux non hanno cmd.exe
.
Comandi essenziali
Docker include molti comandi diversi che creano, gestiscono e interagiscono con contenitori e immagini. Questi comandi Docker sono essenziali per la gestione dei contenitori:
Pulire le risorse
Durante questa esercitazione sono stati creati contenitori e immagini. Se vuoi, elimina queste risorse. Usare i comandi seguenti per
Elencare tutti i contenitori
docker ps -a
Fermare i contenitori in esecuzione per nome.
docker stop core-counter
Eliminare il contenitore
docker rm core-counter
Eliminare quindi tutte le immagini che non si desiderano più nel computer. Eliminare l'immagine creata dal Dockerfile e quindi eliminare l'immagine .NET su cui si basava il Dockerfile . È possibile usare l'ID IMMAGINE o la stringa formattata REPOSITORY:TAG.
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:9.0
docker rmi counter-image:latest
docker rmi mcr.microsoft.com/dotnet/aspnet:8.0
Usare il comando docker images
per visualizzare un elenco di immagini installate.
Suggerimento
I file di immagine possono essere di grandi dimensioni. In genere, si rimuoverebbero i contenitori temporanei creati durante il test e lo sviluppo dell'app. In genere si mantengono le immagini di base con il runtime installato se si prevede di creare altre immagini in base a tale runtime.