From 020ba891520803cdb0c027ebef1266ff88ae34c8 Mon Sep 17 00:00:00 2001 From: Aman Toumaj Date: Thu, 22 Jan 2026 14:48:13 +0100 Subject: [PATCH] dockerization: adding dockerfile and docker compose yaml files --- .dockerignore | 30 ++++ DomainDrivenTutorial.sln | 6 + EShoppingTutorialWebAPI/Dockerfile | 36 +++++ .../EShoppingTutorialWebAPI.csproj | 5 +- EShoppingTutorialWebAPI/Program.cs | 32 ++++- .../Properties/launchSettings.json | 36 +++-- .../appsettings.Development.json | 4 + docker-compose.dcproj | 18 +++ docker-compose.override.yml | 7 + docker-compose.yml | 18 +++ full-stack.yaml | 128 ++++++++++++++++++ launchSettings.json | 11 ++ 12 files changed, 316 insertions(+), 15 deletions(-) create mode 100644 .dockerignore create mode 100644 EShoppingTutorialWebAPI/Dockerfile create mode 100644 docker-compose.dcproj create mode 100644 docker-compose.override.yml create mode 100644 docker-compose.yml create mode 100644 full-stack.yaml create mode 100644 launchSettings.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/DomainDrivenTutorial.sln b/DomainDrivenTutorial.sln index 7caffea..12a7ffe 100644 --- a/DomainDrivenTutorial.sln +++ b/DomainDrivenTutorial.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EShoppingTutorial.Core.Appl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EShoppingTutorial.Infrastructure", "EShoppingTutorial\EShoppingTutorial.Infrastructure\EShoppingTutorial.Infrastructure.csproj", "{F31C106C-CAA0-48B8-8046-8C2A6D394874}" EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{81DDED9D-158B-E303-5F62-77A2896D2A5A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +69,10 @@ Global {F31C106C-CAA0-48B8-8046-8C2A6D394874}.Debug|Any CPU.Build.0 = Debug|Any CPU {F31C106C-CAA0-48B8-8046-8C2A6D394874}.Release|Any CPU.ActiveCfg = Release|Any CPU {F31C106C-CAA0-48B8-8046-8C2A6D394874}.Release|Any CPU.Build.0 = Release|Any CPU + {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EShoppingTutorialWebAPI/Dockerfile b/EShoppingTutorialWebAPI/Dockerfile new file mode 100644 index 0000000..d411bf3 --- /dev/null +++ b/EShoppingTutorialWebAPI/Dockerfile @@ -0,0 +1,36 @@ +# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +# This stage is used when running from VS in fast mode (Default for Debug configuration) +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 + + +# This stage is used to build the service project +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["EShoppingTutorialWebAPI/EShoppingTutorialWebAPI.csproj", "EShoppingTutorialWebAPI/"] +COPY ["EShoppingTutorial/EShoppingTutorial.Core.Application/EShoppingTutorial.Core.Application.csproj", "EShoppingTutorial/EShoppingTutorial.Core.Application/"] +COPY ["EShoppingTutorial/EShoppingTutorial.Core/EShoppingTutorial.Core.Domain.csproj", "EShoppingTutorial/EShoppingTutorial.Core/"] +COPY ["Framework.Core/GenericRepository/GenericRepository.Abstractions.csproj", "Framework.Core/GenericRepository/"] +COPY ["Framework.Core/SharedKernel/SharedKernel.csproj", "Framework.Core/SharedKernel/"] +COPY ["EShoppingTutorial/EShoppingTutorial.Core.Persistence/EShoppingTutorial.Core.Persistence.csproj", "EShoppingTutorial/EShoppingTutorial.Core.Persistence/"] +COPY ["Framework.Core/GenericRepositoryEntityFramework/GenericRepository.EntityFramework.csproj", "Framework.Core/GenericRepositoryEntityFramework/"] +COPY ["EShoppingTutorial/EShoppingTutorial.Infrastructure/EShoppingTutorial.Infrastructure.csproj", "EShoppingTutorial/EShoppingTutorial.Infrastructure/"] +RUN dotnet restore "./EShoppingTutorialWebAPI/EShoppingTutorialWebAPI.csproj" +COPY . . +WORKDIR "/src/EShoppingTutorialWebAPI" +RUN dotnet build "./EShoppingTutorialWebAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build + +# This stage is used to publish the service project to be copied to the final stage +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./EShoppingTutorialWebAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "EShoppingTutorialWebAPI.dll"] \ No newline at end of file diff --git a/EShoppingTutorialWebAPI/EShoppingTutorialWebAPI.csproj b/EShoppingTutorialWebAPI/EShoppingTutorialWebAPI.csproj index b589956..016a78e 100644 --- a/EShoppingTutorialWebAPI/EShoppingTutorialWebAPI.csproj +++ b/EShoppingTutorialWebAPI/EShoppingTutorialWebAPI.csproj @@ -1,10 +1,12 @@ - + net10.0 $(AssemblyName) enable enable + Linux + ..\docker-compose.dcproj @@ -13,6 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/EShoppingTutorialWebAPI/Program.cs b/EShoppingTutorialWebAPI/Program.cs index 49119cb..1749005 100644 --- a/EShoppingTutorialWebAPI/Program.cs +++ b/EShoppingTutorialWebAPI/Program.cs @@ -7,7 +7,16 @@ public static void Main(string[] args) var builder = WebApplication.CreateBuilder(args); // Add Database Connection - builder.Services.AddDbContext(opts => opts.UseSqlServer(builder.Configuration["ConnectionStrings:EShoppingTutorialDB"])); + // Use GetConnectionString which is safer for environment variable overrides + var connectionString = builder.Configuration.GetConnectionString("EShoppingTutorialDB"); + + if (string.IsNullOrEmpty(connectionString)) + { + // LOG it instead of THROWING it + Console.WriteLine("CRITICAL ERROR: Connection string 'EShoppingTutorialDB' was not found!"); + } + + builder.Services.AddDbContext(opts => opts.UseSqlServer(connectionString)); // Add Core Application (Assembly Scanning) builder.Services.AddCoreApplicationConfigs(); @@ -47,6 +56,27 @@ public static void Main(string[] args) app.MapControllers(); app.UseStaticFiles(); app.UseRouting(); + + // ---- Database Migration Logic Necessary for Docker/Kubernetes---- + using (var scope = app.Services.CreateScope()) + { + var services = scope.ServiceProvider; + try + { + var context = services.GetRequiredService(); + // This is the "Magic" line: It creates the DB and tables if they don't exist + // and applies any pending migrations. + context.Database.Migrate(); + Console.WriteLine("Database migration completed successfully."); + } + catch (Exception ex) + { + var logger = services.GetRequiredService>(); + logger.LogError(ex, "An error occurred while migrating the database."); + } + } + // ---------------------------------- + app.Run(); } } \ No newline at end of file diff --git a/EShoppingTutorialWebAPI/Properties/launchSettings.json b/EShoppingTutorialWebAPI/Properties/launchSettings.json index 3b386b1..f695140 100644 --- a/EShoppingTutorialWebAPI/Properties/launchSettings.json +++ b/EShoppingTutorialWebAPI/Properties/launchSettings.json @@ -1,13 +1,4 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:60087", - "sslPort": 0 - } - }, +{ "profiles": { "IIS Express": { "commandName": "IISExpress", @@ -19,10 +10,29 @@ "EShoppingTutorialWebAPI": { "commandName": "Project", "launchBrowser": true, - "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "applicationUrl": "http://localhost:5000" + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "environmentVariables": { + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": false + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:60087", + "sslPort": 0 } } -} +} \ No newline at end of file diff --git a/EShoppingTutorialWebAPI/appsettings.Development.json b/EShoppingTutorialWebAPI/appsettings.Development.json index 42f4816..4b447ae 100644 --- a/EShoppingTutorialWebAPI/appsettings.Development.json +++ b/EShoppingTutorialWebAPI/appsettings.Development.json @@ -1,5 +1,9 @@ { "ConnectionStrings": { + // Use this connection string if you are using Windows Authentication "EShoppingTutorialDB": "Data Source=.;Initial Catalog=EShoppingTutorialDB;Integrated Security=True;Pooling=False;TrustServerCertificate=True" + + // Use this connection string if you are using SQL Server Authentication + //"EShoppingTutorialDB": "Server=db;Database=EShoppingTutorialDB;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" } } \ No newline at end of file diff --git a/docker-compose.dcproj b/docker-compose.dcproj new file mode 100644 index 0000000..7a68e93 --- /dev/null +++ b/docker-compose.dcproj @@ -0,0 +1,18 @@ + + + + 2.1 + Linux + 81dded9d-158b-e303-5f62-77a2896d2a5a + LaunchBrowser + {Scheme}://localhost:{ServicePort} + eshoppingtutorialwebapi + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..69eebe1 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,7 @@ +services: + eshoppingtutorialwebapi: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_HTTP_PORTS=8080 + ports: + - "8080" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2b8ea65 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.4' + +services: + eshoppingtutorialwebapi: + image: ${DOCKER_REGISTRY-}eshoppingtutorialwebapi + build: + context: . + dockerfile: EShoppingTutorialWebAPI/Dockerfile + depends_on: + - db +# SQL Server for Linux + db: + image: mcr.microsoft.com/mssql/server:2022-latest + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=YourStrong@Passw0rd + ports: + - "1433:1433" diff --git a/full-stack.yaml b/full-stack.yaml new file mode 100644 index 0000000..1921ada --- /dev/null +++ b/full-stack.yaml @@ -0,0 +1,128 @@ +# --- THE API SERVICE (Load Balancer) --- +apiVersion: v1 +kind: Service +metadata: + name: eshopping-api-service +spec: + type: LoadBalancer + selector: + app: eshopping-api + ports: + - protocol: TCP + port: 8081 # Port you use in the browser + targetPort: 8080 # Port inside the .NET container +--- +# --- THE INGRESS (Domain Router) --- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshopping-ingress +spec: + ingressClassName: nginx + rules: + - host: eshopping.local + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: eshopping-api-service + port: + number: 8081 +--- +# --- THE DATABASE SERVICE --- +apiVersion: v1 +kind: Service +metadata: + name: sql-service +spec: + selector: + app: mssql + ports: + - protocol: TCP + port: 1433 + targetPort: 1433 +--- +# --- THE DATABASE (StatefulSet with Persistent Storage) --- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: mssql +spec: + serviceName: "sql-service" + replicas: 1 + selector: + matchLabels: + app: mssql + template: + metadata: + labels: + app: mssql + spec: + containers: + - name: mssql + image: mcr.microsoft.com/mssql/server:2022-latest + ports: + - containerPort: 1433 + env: + - name: ACCEPT_EULA + value: "Y" + - name: MSSQL_SA_PASSWORD + value: "YourStrong@Passw0rd" + volumeMounts: + - name: mssql-storage + mountPath: /var/opt/mssql # SQL Server's internal data directory + volumeClaimTemplates: + - metadata: + name: mssql-storage + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1Gi # This creates a permanent disk on your host +--- +# --- THE API DEPLOYMENT (3 Replicas) --- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: eshopping-api +spec: + replicas: 3 + selector: + matchLabels: + app: eshopping-api + template: + metadata: + labels: + app: eshopping-api + spec: + initContainers: + - name: wait-for-sql + image: busybox:latest + command: ['sh', '-c', "until nc -z sql-service 1433; do echo waiting for sql; sleep 2; done;"] + containers: + - name: api + image: eshopping-api:v1 # Uses the tag we successfully built + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Development" + - name: ASPNETCORE_URLS + value: "http://0.0.0.0:8080" + - name: ConnectionStrings__EShoppingTutorialDB + value: "Server=sql-service;Database=EShoppingTutorialDB;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True" + livenessProbe: + httpGet: + path: /index.html + port: 8080 + initialDelaySeconds: 40 + periodSeconds: 15 + readinessProbe: + httpGet: + path: /index.html + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 \ No newline at end of file diff --git a/launchSettings.json b/launchSettings.json new file mode 100644 index 0000000..e7c3053 --- /dev/null +++ b/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Docker Compose": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "serviceActions": { + "eshoppingtutorialwebapi": "StartDebugging" + } + } + } +} \ No newline at end of file