Dockerizing an ASP.NET + React App

My team has started working on a new web application, and we’ve been asked to use ASP.NET for the backend. We chose to use React for the frontend because we’ve had significant success with React at Atomic, and we wanted to leverage some existing UI components that were built in React.

We try to avoid using IDEs when we can, so for this guide, I’ll be using the command line only.

Creating the App

We’ll start by using the dotnet CLI to generate an ASP.NET + React template. First, install the CLI:

$ brew cask install dotnet-sdk

If you need a specific SDK, you can use this package: dotnet-sdk versions HomeBrew Tap.

To create a template app, run the following command, subbing sample-project for your project name:

$ dotnet new react -n sample-project

cd into your new project, and let’s poke around!

A Brief Tour

In the newly created sample-project directory, you’ll see a few key folders:

  • ClientApp: Contains the React app. It’s based on Create React App, which has some excellent docs here.
  • Controllers: Defines your API endpoints. You can see an example of one in WeatherForecastController.cs.
  • Pages: Misleading; contains a default error page for your backend. The “real” frontend lives in ClientApp.

For purposes of this guide, we’ll leave the sample code mostly as-is.

Setup… Sort Of

This template provides a basic web API and frontend out of the box, so to fire things up, you should be able to run:

$ dotnet run

But…

Sigh. As of the time of writing, if I try to run the project immediately, I get a slew of errors. The first one is a warning that:

Browserslist: caniuse-lite is outdated. Please run next command `npm update`

Okay, easy enough. If you see commands that involve npm, that’s a sign you need to be working in the frontend, so pop into ClientApp and run that command:

$ cd ClientApp
$ npm update
$ cd ..

Okay, one problem down. Let’s try running again: dotnet run

You might have success! But…

I did not. Sigh. I get the following message:

There might be a problem with the project dependency tree. 
[...]

Fortunately, this message also includes instructions on a fix:

To fix the dependency tree, try following the steps below in the exact order: 
[...]

But following these steps didn’t get me anywhere.

A bit of digging around the internet regarding this template yields…almost nothing a few closed Github issues that mostly say “works for me.” So I’m not positive exactly why this issue happens.

But you can sidestep the problem by setting the package version in ClientApp/package.json to the version listed in the error message. For example:

The react-scripts package provided by Create React App requires a dependency:
        "babel-eslint": "10.0.3"

For this, I opened ClientApp/package.json and updated the line:

"babel-eslint": "10.0.1"

to

"babel-eslint": "10.0.3"

Then, I blew away my node_modules:

$ rm -rf ClientApp/node_modules

And I tried running the app again. I repeated this process for a second package, and finally:

$ dotnet run

No failures! Navigate to https://localhost:5001, and check out your new app!

Dockerization

Running the app locally is great and all, but it’s not what you came here for. To set up the app to run in Docker, add a Dockerfile at the root of the repo.

You’ll need to update instances of sample-app to your app’s name. I’ve included comments that explain each step:

# Pull down an image from Docker Hub that includes the .NET core SDK: 
# https://hub.docker.com/_/microsoft-dotnet-core-sdk
# This is so we have all the tools necessary to compile the app.
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build

# Fetch and install Node 10. Make sure to include the --yes parameter 
# to automatically accept prompts during install, or it'll fail.
RUN curl --silent --location https://deb.nodesource.com/setup_10.x | bash -
RUN apt-get install --yes nodejs

# Copy the source from your machine onto the container.
WORKDIR /src
COPY . .

# Install dependencies. 
# https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x
RUN dotnet restore "./sample-app.csproj"

# Compile, then pack the compiled app and dependencies into a deployable unit.
# https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish?tabs=netcore21
RUN dotnet publish "sample-app.csproj" -c Release -o /app/publish

# Pull down an image from Docker Hub that includes only the ASP.NET core runtime:
# https://hub.docker.com/_/microsoft-dotnet-core-aspnet/
# We don't need the SDK anymore, so this will produce a lighter-weight image
# that can still run the app.
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim

# Expose port 80 to your local machine so you can access the app.
EXPOSE 80

# Copy the published app to this new runtime-only container.
COPY --from=build /app/publish .

# To run the app, run `dotnet sample-app.dll`, which we just copied over.
ENTRYPOINT ["dotnet", "sample-app.dll"]

To build the image, run:

$ docker build -t sample-app .

And finally, to run your new Dockerized app:

$ docker run -p 80:80 sample-app

Now, you should be able to visit http://localhost:80 and see your app!

Check the “fetch data” page to make sure your frontend and backend are talking to each other.

In the future, I’d like to build out a solution that allows us to do all of our development in Docker. But for now, this relatively simple solution gets the app up and running for purposes of containerized deployment and system testing in CI.

Happy containerizing!