Resolving 404 errors when publishing ASP.NET Core for Docker

I was recently building a small ASP.NET Core app for work (yay, .NET on Linux!) and had my app running perfectly: Web API Controllers with an Angular frontend based on the SpaServices package, all working through dotnet run.

So, I did what any self-respecting developer does in 2017: threw Docker at it. Add a quick Dockerfile and build:

FROM centos:7
RUN yum install -y libunwind libicu
ADD ./bin/Release/netcoreapp1.1/centos.7-x64/publish /app
RUN chmod +x /app/App.Web
EXPOSE 5000
ENTRYPOINT ["/app/App.Web", "--server.urls", "http://0.0.0.0:5000"]

This example is using ASP.NET Core's new 'Self-Contained Deployments' (SCD); apps that deploy with the framework so they don't even need the .NET runtime installed on your target server.

You can find more info on this tech in my latest article for opensource.com.

Build runs fine, so run it with docker run -p 5000:5000 <repo/image:tag> and you'll see the server spin up on port 5000, ready to accept requests. However, if you actually hit the app, you'll get nothing but 404s!

The reasoning is simple: MVC's UseStaticFiles() middleware uses the currently configured content root. The default Program.cs includes the following snippet which sets the content root:

var host = new WebHostBuilder()
                .UseConfiguration(config)
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseStartup<Startup>()
                .Build();

So when we're running dotnet run the Directory.GetCurrentDirectory() call will return our correct application root. However, in our Docker image, when the app gets called, GetCurrentDirectory() will instead return /, not /app (where our app is actually rooted).

You can solve that with a simple addition to the Dockerfile:

FROM centos:7
RUN yum install -y libunwind libicu
ADD ./bin/Release/netcoreapp1.1/centos.7-x64/publish /app
RUN chmod +x /app/App.Web
WORKDIR /app # IMPORTANT!
EXPOSE 5000
ENTRYPOINT ["/app/App.Web", "--server.urls", "http://0.0.0.0:5000"]

Adding the WORKDIR instruction sets the current directory to /app so that when our app starts, the content root is set correctly and your assets will be correctly served again!