Quickstart: Use Docker with a React Single-page App in Visual Studio

With Visual Studio, you can easily build, debug, and run containerized ASP.NET Core apps, including those with client-side JavaScript such as React.js single-page app, and publish them to Azure Container Registry, Docker Hub, Azure App Service, or your own Container Registry. In this article, we publish to Azure Container Registry.

Prerequisites

  • Docker Desktop
  • Visual Studio 2022 with the Web Development, Azure Tools workload, and/or .NET Core cross-platform development workload installed
  • To publish to Azure Container Registry, an Azure subscription. Sign up for a free trial.
  • Node.js
  • For Windows containers, Windows 10 version 1809 or later, to use the Docker images referenced in this article.

Installation and setup

For Docker installation, first review the information at Docker Desktop for Windows: What to know before you install. Next, install Docker Desktop.

Create a project and add Docker support

  1. Create a new project using the ASP.NET Core with React.js template.

    Screenshot of creating a new React.js project.

  2. On the Additional information screen, you can't select Enable Docker Support, but don't worry, you can add that support later.

    Screenshot of creating a new React.js project - Additional information screen.

  3. Right-click on the project node, and choose Add > Docker Support to add a Dockerfile to your project.

    Screenshot of Add Docker support menu item.

  4. Select the container type.

Follow these steps if you're using Visual Studio 2022 version 17.8 or later:

  1. Create a new project using the React and ASP.NET Core template.

    Screenshot of creating a new React and ASP.NET Core project.

  2. On the Additional information screen, you can't select Enable Docker Support, but don't worry, you can add that support later.

    Screenshot of creating a React and ASP.NET Core project - Additional information screen.

    Visual Studio creates two projects - one for the React JavaScript client code, and another for the ASP.NET Core C# server code.

  3. Right-click on the server project node, and choose Add > Docker Support to add a Dockerfile to your project.

    Screenshot of Add Docker support menu item.

  4. Select the container type.

Use the following steps for Visual Studio 2022 version 17.0 to 17.7:

  1. Create a new project using the ASP.NET Core with React.js template.

    Screenshot of creating a new React.js project.

  2. On the Additional information screen, you can't select Enable Docker Support, but don't worry, you can add that support later.

    Screenshot of creating a new React.js project - Additional information screen.

  3. Right-click on the project node, and choose Add > Docker Support to add a Dockerfile to your project.

    Screenshot of Add Docker support menu item.

  4. Select the container type.

The next step is different depending on whether you're using Linux containers or Windows containers.

Modify the Dockerfile (Linux containers)

A Dockerfile, the recipe for creating a final Docker image, is created in the project. Refer to Dockerfile reference for an understanding of the commands within it.

The default Dockerfile uses a base image to run the container, but when you want to also be able to run a Node.js application on it, you need to install Node.js, which means adding some installation commands in a couple of places in the Dockerfile. The installation commands require elevated permissions, since the changes affect the container's privileged system files and folders.

When the new project dialog's Configure for HTTPS checkbox is checked, the Dockerfile exposes two ports. One port is used for HTTP traffic; the other port is used for HTTPS. If the checkbox isn't checked, a single port (80) is exposed for HTTP traffic.

If you're targeting .NET 8 or later, the default Dockerfile that Visual Studio creates using the normal user account (look for the line USER app), but that account doesn't have the elevated permissions required to install Node.js. To account for this situation, do the following:

  1. In the Dockerfile, delete the line USER app.
  2. Change the ports that are exposed in the first section of the Dockerfile to port 80 is for HTTP requests and (if you chose to support HTTPS when you created the project) 443 for HTTPS requests.
  3. Edit launchSettings.json to change the port references there to 80 and 443; replace 8080 with 80 for HTTP, and 8081 with 443 for HTTPS.

For all .NET versions, use the following steps to update the Dockerfile to install Node.js:

  1. Add the following lines to install curl, Node.js 14.x, and certain required Node libraries in the container. Be sure to add these lines both in the first section, to add the installation of the Node package manager npm.exe to the base image, as well as in the build section.
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y libpng-dev libjpeg-dev curl libxi6 build-essential libgl1-mesa-glx
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs

The Dockerfile should now look something like this:

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y libpng-dev libjpeg-dev curl libxi6 build-essential libgl1-mesa-glx
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs

FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y libpng-dev libjpeg-dev curl libxi6 build-essential libgl1-mesa-glx
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs
WORKDIR /src
COPY ["ProjectSPA1/ProjectSPA1.csproj", "ProjectSPA1/"]
RUN dotnet restore "ProjectSPA1/ProjectSPA1.csproj"
COPY . .
WORKDIR "/src/ProjectSPA1"
RUN dotnet build "ProjectSPA1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ProjectSPA1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProjectSPA1.dll"]
#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.

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y libpng-dev libjpeg-dev curl libxi6 build-essential libgl1-mesa-glx
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y libpng-dev libjpeg-dev curl libxi6 build-essential libgl1-mesa-glx
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["reactapp1.client/nuget.config", "reactapp1.client/"]
COPY ["ReactApp1.Server/ReactApp1.Server.csproj", "ReactApp1.Server/"]
COPY ["reactapp1.client/reactapp1.client.esproj", "reactapp1.client/"]
RUN dotnet restore "./ReactApp1.Server/./ReactApp1.Server.csproj"
COPY . .
WORKDIR "/src/ReactApp1.Server"
RUN dotnet build "./ReactApp1.Server.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ReactApp1.Server.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ReactApp1.Server.dll"]

In Visual Studio 2022 version 17.0 to 17.7, it should resemble the following:

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y libpng-dev libjpeg-dev curl libxi6 build-essential libgl1-mesa-glx
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y libpng-dev libjpeg-dev curl libxi6 build-essential libgl1-mesa-glx
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get install -y nodejs
WORKDIR /src
COPY ["ProjectSPA1/ProjectSPA1.csproj", "ProjectSPA1/"]
RUN dotnet restore "ProjectSPA1/ProjectSPA1.csproj"
COPY . .
WORKDIR "/src/ProjectSPA1"
RUN dotnet build "ProjectSPA1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "ProjectSPA1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProjectSPA1.dll"]

The preceding Dockerfile is based on the mcr.microsoft.com/dotnet/core/aspnet image, and includes instructions for modifying the base image by building your project and adding it to the container.

Modify the Dockerfile (Windows containers)

Open the project file by double-clicking on the project node, and update the project file (*.csproj) by adding the following property as a child of the <PropertyGroup> element:

 <DockerfileFastModeStage>base</DockerfileFastModeStage>

Note

The change to DockerfileFastModeStage is required, because the Dockerfile here adds a stage to the beginning of the Dockerfile. In order to optimize performance, Visual Studio uses Fast mode, but it only works if the right stage is used. The default is the first stage in the Dockerfile, which in this example, is changed from base to something else in order to download Node.js. For more explanation of Fast mode, see Customize Docker containers in Visual Studio.

Update the Dockerfile by adding the following lines. These lines will copy Node and `npm`` to the container.

  1. Add # escape=` to the first line of the Dockerfile

  2. Add the following lines before FROM ... base

    FROM mcr.microsoft.com/powershell AS downloadnodejs
    ENV NODE_VERSION=14.16.0
    SHELL ["pwsh", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]
    RUN Invoke-WebRequest -OutFile nodejs.zip -UseBasicParsing "https://nodejs.org/dist/v$($env:NODE_VERSION)/node-v$($env:NODE_VERSION)-win-x64.zip"; `
        Expand-Archive nodejs.zip -DestinationPath C:\; `
        Rename-Item "C:\node-v$($env:NODE_VERSION)-win-x64" c:\nodejs
    
  3. Add the following line before and after FROM ... build

    COPY --from=downloadnodejs C:\nodejs\ C:\Windows\system32\
    
  4. The complete Dockerfile should now look something like this:

    # escape=`
    #Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed.
    #For more information, please see https://aka.ms/containercompat
    FROM mcr.microsoft.com/powershell AS downloadnodejs
    ENV NODE_VERSION=14.16.0
    SHELL ["pwsh", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]
    RUN Invoke-WebRequest -OutFile nodejs.zip -UseBasicParsing "https://nodejs.org/dist/v$($env:NODE_VERSION)/node-v$($env:NODE_VERSION)-win-x64.zip"; \
        Expand-Archive nodejs.zip -DestinationPath C:\; \
        Rename-Item "C:\node-v$($env:NODE_VERSION)-win-x64" c:\nodejs
    
    FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
    WORKDIR /app
    EXPOSE 80
    EXPOSE 443
    COPY --from=downloadnodejs C:\\nodejs C:\\Windows\\system32
    
    FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
    COPY --from=downloadnodejs C:\\nodejs C:\\Windows\\system32
    WORKDIR /src
    COPY ["ProjectSPA1/ProjectSPA1.csproj", "ProjectSPA1/"]
    RUN dotnet restore "ProjectSPA1/ProjectSPA1.csproj"
    COPY . .
    WORKDIR "/src/ProjectSPA1"
    RUN dotnet build "ProjectSPA1.csproj" -c Release -o /app/build
    
    FROM build AS publish
    RUN dotnet publish "ProjectSPA1.csproj" -c Release -o /app/publish
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app/publish .
    ENTRYPOINT ["dotnet", "ProjectSPA1.dll"]
    
    #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images   for faster debugging.
    
    #Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed.
    #For more information, please see https://aka.ms/containercompat
    # escape=`
    FROM mcr.microsoft.com/powershell:nanoserver-1809 AS downloadnodejs
    ENV NODE_VERSION=14.16.0
    SHELL ["pwsh", "-Command", "$ErrorActionPreference = 'Stop';$ProgressPreference='silentlyContinue';"]
    RUN Invoke-WebRequest -OutFile nodejs.zip -UseBasicParsing "https://nodejs.org/dist/v$($env:NODE_VERSION)/node-v$($env:NODE_VERSION)-win-x64.zip"; Expand-Archive nodejs.zip -DestinationPath C:\; Rename-Item "C:\node-v$($env:NODE_VERSION)-win-x64" c:\nodejs
    
    FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
    WORKDIR /app
    EXPOSE 80
    EXPOSE 443
    COPY --from=downloadnodejs C:\\nodejs C:\\Windows\\system32
    
    FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
    COPY --from=downloadnodejs C:\\nodejs C:\\Windows\\system32
    WORKDIR /src
    COPY ["Project1-SPA-Windows/Project1-SPA-Windows.csproj", "Project1-SPA-Windows/"]
    RUN dotnet restore "Project1-SPA-Windows/Project1-SPA-Windows.csproj"
    COPY . .
    WORKDIR "/src/Project1-SPA-Windows"
    RUN dotnet build "Project1-SPA-Windows.csproj" -c Release -o /app/build
    
    FROM build AS publish
    RUN dotnet publish "Project1-SPA-Windows.csproj" -c Release -o /app/publish /p:UseAppHost=false
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app/publish .
    ENTRYPOINT ["dotnet", "Project1-SPA-Windows.dll"]
    
  5. Update the .dockerignore file by removing the **/bin.

Debug

With Visual Studio 2022 version 17.8 and later and the React and ASP.NET Core template, the projects are already configured to start both the client and server projects with debugging support.

If you're using an earlier version of Visual Studio, continue reading to set up debugging with the single-page application (SPA) proxy server.

The project uses the SPA Proxy during debugging. See Improved single-page app (SPA) templates. When debugging, the JavaScript client runs on the host machine, but the ASP.NET Core server code runs in the container. When published, the proxy isn't run, and the client code runs on the same server as the ASP.NET Core code. You already have a Debug profile *Docker that you can use to debug the server code. To debug the JavaScript client code, you can create an additional debug profile. You also need to start the proxy manually from a command prompt when debugging JavaScript. You can leave it running through multiple debug sessions.

  1. Build the project, if not already built.

  2. Open a Visual Studio dev command prompt, go to the ClientApp folder in your project, and then give the command, npm run start. You should see something like this:

    Compiled successfully!
    
    You can now view project3_spa in the browser.
    
      Local:            https://localhost:44407
      On Your Network:  https://192.168.1.5:44407
    
    Note that the development build isn't optimized.
    To create a production build, use npm run build.
    
    webpack compiled successfully
    

    Note

    Note the local URL. You need to provide this in a debug launch profile, which is stored in your launchSettings.json file.

  3. Open the dropdown list that contains debug profiles (next to the green triangle icon or Start button), and choose {ProjectName} Debug Properties, and choose the Docker profile.

  4. Check the Environment variables section and add the following environment variables if not already present:

    ASPNETCORE_ENVIRONMENT=Development,ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=Microsoft.AspNetCore.SpaProxy

  5. Set the URL to https://localhost:{proxy-port} where {proxy-port} is the port from the proxy server (from step 1).

    Screenshot of Debug Launch Profile settings for client debugging.

    This action changes the Docker entry in the launchSettings.json file and launches the correct URL for the local proxy running on the host. Find the launchSettings.json file in Solution Explorer under Properties.

  6. You should see something like the following code:

    "profiles": {
       "Docker": {
          "commandName": "Docker",
          "launchBrowser": true,
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development",
            "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
          },
          "launchUrl": "https://localhost:44407",
          "useSSL": true
       }
    }
    

    Important

    Do not set the launch settings option publishAllPorts to true if you're using a proxy. That option publishes all exposed ports to a random port, which won't work when you set a specific port in the SPA proxy.

  7. Open the file ClientApp/src/setupProxy.js and change the line that sets the target to use the localhost address and port on the container. You can find the port on the Ports tab of the Containers window.

    const target =  'https://localhost:{container-port}';
    

    If you're using HTTPS, be sure to choose the right port for HTTPS, which is 443 in this tutorial.

  8. Launch the app with debugging (F5).

    Screenshot of running app.

    If you get a build error trying to write the output assemblies, you might have to stop a previously running container to unlock the files.

  9. Verify that you can hit a breakpoint in client-side JavaScript code by setting a breakpoint in ClientApp/src/components/Counter.js in the incrementCounter function, and then try hitting the breakpoint by clicking the Increment button on the Counters page.

    Screenshot showing ebugging client-side JavaScript.

  10. Next, try hitting a breakpoint in the server-side ASP.NET Core code. Set a breakpoint in WeatherController.cs in the Get method and try appending /weatherforecast to the base localhost and port URL to activate that code.

    Screenshot showing debugging server-side ASP.NET Core code.

  11. If the container port changes, which can happen if you make a significant change, such as updating launchSettings.json or updating the debug launch profile in the IDE, you need to update the port in setupProxy.js and also restart the proxy. Terminate the current proxy (Ctrl+C in the command window where it's running), and then restart it using the same command npm run start.

Select Docker from the debug dropdown list in the toolbar, and start debugging the app. You might see a message with a prompt about trusting a certificate; choose to trust the certificate to continue. The first time you build, Docker downloads the base images, so it might take a bit longer.

The Container Tools option in the Output window shows what actions are taking place. You should see the installation steps associated with npm.exe.

The browser shows the app's home page.

Screenshot of running app.

Containers window

Open the Containers tool window. You can find it on the menu under View > Other Windows > Containers, or press Ctrl+Q and start typing containers in the search box, then choose Containers window from the results. When the window comes up, dock it on the bottom under the editor pane.

The Containers window shows the running containers and lets you view information about them. You can view the environment variables, labels, ports, volumes, the file system, and logs. The toolbar buttons let you create a terminal (shell prompt) inside the container, attach the debugger, or prune unused containers. See Use the Containers window.

Screenshot of Containers window.

Click on the Files tab, and expand the app folder to see your published application files.

You can also view the images and inspect information about them. Choose the Images tab, locate the one for your project, and then choose the Details tab to view a json file that contains information about an image.

Screenshot of Containers window showing images and details.

Note

The dev image does not contain the app binaries and other content, as Debug configurations use volume mounting to provide the iterative edit and debug experience. To create a production image containing all contents, use the Release configuration.

Publish Docker images

Once the develop and debug cycle of the app is completed, you can create a production image of the app.

  1. Change the configuration dropdown list to Release and build the app.

  2. Right-click your project in Solution Explorer and choose Publish.

  3. On the publish target dialog, select Docker Container Registry.

    Choose Docker Container Registry.

  4. Next, choose Azure Container Registry.

    Choose Azure Container Registry.

  5. Choose Create a new Azure Container Registry.

  6. Fill in your desired values in the Create new Azure Container Registry screen.

    Setting Suggested value Description
    DNS Prefix Globally unique name Name that uniquely identifies your container registry.
    Subscription Choose your subscription The Azure subscription to use.
    Resource Group myResourceGroup Name of the resource group in which to create your container registry. Choose New to create a new resource group.
    SKU Standard Service tier of the container registry
    Registry Location A location close to you Choose a Location in a region near you or near other services that use your container registry.

    Screenshot of Visual Studio's create Azure Container Registry dialog.

  7. Select Create, and then select Finish.

    Screenshot showing Select or create a new Azure container registry.

    When the publish process ends, you can review the publish settings, and edit them, when needed, or publish the image again using the Publish button.

    Screenshot showing successful publish.

    To start again using the Publish dialog, delete the publish profile by using the Delete link on this page, and then choose Publish again.

  1. Change the configuration dropdown list to Release and build the app.

  2. Right-click your project in Solution Explorer and choose Publish.

  3. On the publish target dialog, select Docker Container Registry.

    Screenshot showing Choose Docker Container Registry.

  4. Next, choose Azure Container Registry.

    Screenshot showing Choose Azure Container Registry.

  5. Choose Create a new Azure Container Registry.

  6. Fill in your desired values in the Create new Azure Container Registry screen.

    Setting Suggested value Description
    DNS Prefix Globally unique name Name that uniquely identifies your container registry.
    Subscription Choose your subscription The Azure subscription to use.
    Resource Group myResourceGroup Name of the resource group in which to create your container registry. Choose New to create a new resource group.
    SKU Standard Service tier of the container registry
    Registry Location A location close to you Choose a Location in a region near you or near other services that use your container registry.

    Screenshot of Visual Studio's create Azure Container Registry dialog.

  7. Select Create, and then select Finish.

    Screenshot showing Select or create a new Azure container registry.

    When the publish process ends, you can review the publish settings, and edit them, when needed, or publish the image again using the Publish button.

    Screenshot showing successful publish

    To start again using the Publish dialog, delete the publish profile by using the Delete link on this page, and then choose Publish again.

Next steps

You can now pull the container from the registry to any host capable of running Docker images, for example Azure Container Instances.

Additional resources