How to Dockerize Your ASP.NET Core, Angular, and SQL Database App

How to Dockerize Your ASP.NET Core, Angular, and SQL Database App

Easy Steps to Dockerize Your Full-Stack ASP.NET Core, Angular, and SQL Application

Dockerizing an application involves creating lightweight, portable containers to encapsulate your application and its dependencies. This guide will walk you through the process of dockerizing an application built with ASP.NET Core, Angular, and an SQL database. Before diving in, ensure Docker is successfully installed and the application is ready to containerize.


What is Docker?

Docker is an open-source platform that allows developers to automate the deployment of applications in lightweight, portable containers. These containers contain everything required to run an application, including code, runtime, libraries, and dependencies.

Key Concepts:

  • Image: A lightweight, standalone, and executable package that includes everything needed to run a piece of software.

  • Container: A running instance of an image that isolates the application and its environment.

  • Docker Compose: A tool to define and run multi-container Docker applications using a YAML file.


Prerequisites

  1. Docker Installed: Ensure Docker Desktop (Windows/Mac) or Docker Engine (Linux) is installed.

  2. Application Structure: Have your ASP.NET Core, Angular, and SQL database application ready with clear separation of the backend, frontend, and database.

  3. Basic Knowledge: Familiarity with Docker commands and YAML syntax.


Step-by-Step Dockerization

1. Dockerize the ASP.NET Core Backend

  1. Create a Dockerfile in the backend project directory:

      # Use the official Microsoft .NET 7.0 ASP.NET Core base image
      FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
        # Set the working directory inside the container to /app (common for ASP.NET Core apps)
        WORKDIR /app
        # Expose port 5000, which is the default port for ASP.NET Core applications
        EXPOSE 5000
    
      # Separate stage for building the application
      FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
        # Set the working directory inside the container to /src (common for source code)
        WORKDIR /src
        # Copy all files from the current directory (context) to /src in the container
        COPY . .
        # Restore NuGet packages defined in the project.json or *.csproj file
        RUN dotnet restore
        # Publish the application in Release configuration with output directory set to /app
        RUN dotnet publish -c Release -o /app
    
      # Combine the built application with the base image
      FROM base AS final
        # Set the working directory inside the container to /app (same as base image)
        WORKDIR /app
        # Copy the published application from the build stage to /app in the final image
        COPY --from=build /app .
        # Set the entrypoint command to execute your application upon container startup
        ENTRYPOINT ["dotnet", "MyWebApiProject.dll"]
    

    This Dockerfile defines a three-stage build process:

    1. Base Image: Pulls the ASP.NET Core 7.0 runtime image.

    2. Build Stage: Builds your application in a separate container with the .NET SDK, restoring NuGet packages and publishing the application files.

    3. Final Image: Creates a final image by combining the base image and copying the published application files from the build stage. It sets the ENTRYPOINT to run your application upon container startup.

      EXPOSE 5000 exposes port 5000 inside the container. This is typically the port on which ASP.NET Core applications listen for incoming requests.

  2. Build and Run the Docker Image:

      docker build -t my-aspnetcore-webapi .
      docker run -d -p 5000:5000 my-aspnetcore-webapi
    

2. Dockerize the Angular Frontend

  1. Create a Dockerfile in the Angular project directory:

      # Multi-stage Dockerfile for an Angular application
    
      # Stage 1: Build the Angular application
      FROM node:18-alpine AS build  # Use a lightweight Node.js 18 Alpine image
      WORKDIR /app                   # Set the working directory inside the container
      COPY package*.json ./        # Copy package.json and package-lock.json
      RUN npm install                # Install all dependencies
      COPY . .                      # Copy the rest of the application code
      RUN npm run build --prod     # Build the Angular application in production mode
    
      # Stage 2: Serve the built application with Nginx
      FROM nginx:alpine             # Use a lightweight Nginx Alpine image
      COPY --from=build /app/dist/my-angular-app /usr/share/nginx/html # Copy the built app to Nginx's HTML directory
    
      # IMPORTANT: If you have a custom nginx.conf, copy it here:
      # COPY nginx.conf /etc/nginx/conf.d/default.conf
    
      EXPOSE 80                      # Expose port 80 for HTTP traffic
      CMD ["nginx", "-g", "daemon off;"] # Start Nginx in the foreground
    

    Explanation and Key Improvements:

    • node:18-alpine: Using the alpine variant of the Node.js image results in a significantly smaller image size.

    • package*.json Copy: Copies package.json and package-lock.json first, then runs npm install. This leverages Docker's layer caching effectively. If your dependencies haven't changed, Docker will reuse the cached layer, speeding up subsequent builds.

    • my-angular-app Placeholder: The most important change: replace my-angular-app with the actual name of your Angular application's output directory. This is the directory created by the ng build --prod command. You can find this name in your angular.json file under the projects.<your-project-name>.architect.build.options.outputPath property. The default is usually dist/<your-project-name>.

  2. Build and Run the Docker Image:

      docker build -t my-angular-app .
      # -t my-angular-app tags the image with the name my-angular-app.
      # . specifies current directory
    
      docker run -p 8080:80 my-angular-app
      # Important: -p 8080:80 maps port 8080 on your host machine to port 80 in the container. You can access your application at http://localhost:8080
    

3. Dockerize the SQL Database

  1. Use an official SQL Server image from Docker Hub and include the configuration in your docker-compose.yml.

      # Version specification for Docker Compose format
      version: '3.8'
    
      # Define services to be deployed and managed
      services:
    
        # Define a service named sql-db
        sql-db:
    
          # Use the official Microsoft SQL Server 2019 image from MCR
          image: mcr.microsoft.com/mssql/server:2019-latest
    
          # Assign a descriptive name to the container for easier identification
          container_name: sql-db
    
          # Set environment variables for SQL Server configuration within the container
          environment:
    
            # Accept the Microsoft SQL Server license agreement (required)
            - ACCEPT_EULA=Y
    
            # Set a strong password for the SA account (replace with your actual password)
            - SA_PASSWORD=YourStrongPassword123  # Update with a strong, unique password
    
          # Map container port 1433 (default SQL Server port) to host port 1433
          # This allows connections to the SQL Server instance from the host machine
          ports:
            - "1433:1433"
    
          # Define a volume to persist SQL Server data outside the container
          volumes:
            - sql_data:/var/opt/mssql
    
      # Define a named volume for data persistence
      volumes:
        sql_data:
          # No additional configuration needed here, the volume name is referenced above
    

4. Use Docker Compose for Multi-Container Setup

  1. Create a docker-compose.yml file:

      # Version specification for Docker Compose format
      version: '3.8'
    
      # Define services to be deployed and managed
      services:
    
        # Backend service definition
        backend:
          # Build the backend service image from the ./Backend directory
          build:
            context: ./Backend
    
          # Expose port 5000 on the container to port 5000 on the host machine
          # This allows communication with the backend service from the outside world
          ports:
            - "5000:5000"
    
          # Specify that the backend service depends on the sql-db service
          # This ensures the backend service starts only after the SQL database is ready
          depends_on:
            - sql-db
    
        # Frontend service definition
        frontend:
          # Build the frontend service image from the ./Frontend directory
          build:
            context: ./Frontend
    
          # Expose port 80 on the container to port 80 on the host machine
          # This allows access to the frontend application through a web browser
          ports:
            - "80:80"
    
        # SQL Server database service definition
        sql-db:
          # Use the official Microsoft SQL Server 2019 image from MCR
          image: mcr.microsoft.com/mssql/server:2019-latest
    
          # Set environment variables for SQL Server configuration within the container
          environment:
            - ACCEPT_EULA=Y  # Accept the Microsoft SQL Server license agreement (required)
            - SA_PASSWORD=YourStrongPassword123  # Set a strong password for the SA account (replace with your actual password)
    
          # Map container port 1433 (default SQL Server port) to host port 1433
          # This allows connections to the SQL Server instance from the host machine
          ports:
            - "1433:1433"
    
          # Define a volume to persist SQL Server data outside the container
          volumes:
            - sql_data:/var/opt/mssql
    
      # Define a named volume for data persistence
      # Volumes are the mechanism for persisting data generated by and used by Docker containers. They are essentially directories on the host filesystem (or managed by Docker) that are mounted into containers.
      # This means that data stored in a volume is not deleted when the container is stopped or removed.
      volumes:
        sql_data:
          # No additional configuration needed here, the volume name is referenced above
    
  2. Start all Services:

      docker-compose up --build
      # Please do read about other docker-compose commands
    

Additional Tips

  1. Environment Variables:

    Use .env files to manage environment-specific configurations:

      ASPNETCORE_ENVIRONMENT=Development
      SA_PASSWORD=YourStrongPassword123
    
  2. Debugging and Logs

    - View logs with docker logs <container-name>.

    - Use interactive mode for debugging: docker exec -it <container-name> /bin/bash.


Conclusion

Dockerizing your ASP.NET Core, Angular, and SQL database application improves portability, scalability, and ease of deployment. With Docker Compose, managing multiple services becomes simpler, allowing for efficient development and production setups.