/ Docker Stuff

Recipe: Publish a .NET Core 3 ASP.NET project on Heroku using Docker

My $0.02

Heroku is great. You can deploy a concept for free and the next highest tier is a few bucks away. When you're ready to go to scale, it's ready and prorated to the second. Neat.

Docker is great. You can build, test, and deploy in any configuration on a local stack. Everything is ephemeral awesome.

.NET Core is great. I can write great apps with the framework I prefer and not be stuck on Windows. If you're an old, crusty Win32 developer, run dotnet new winforms && dotnet run from the .NET Core 3 CLI and tell me I'm wrong. Gnarly?

Recipe

This recipe assumes you got the tools setup already and have some experience in the tooling.

Tooling

  • .NET Core 3 SDK
    • I'm using the Blazor Server option for my example.
  • Heroku CLI
    • Login heroku login then login heroku container:login.
  • Docker
    • I'm using Engine 19.x currently to take advantage of that new, sweet buildkit system. Should work on at least on 18.x as well.

Prepare Dockerfile

  1. Create a Dockerfile file at the root of your project (it isn't required to be there but for this example I'll put it next to the csproj).* You can use Visual Studio 2017/2019 to produce a Dockerfile for you by right-clicking the project and adding container support.)
  2. Inside the Dockerfile add the following:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
WORKDIR /src
COPY blazor-server.csproj .
RUN dotnet restore "blazor-server.csproj"
COPY . .
RUN dotnet build "blazor-server.csproj" -c Release -o /app/build

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

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

Now you got a buildable image script. The caveat for Heroku containers is that it has to be able to inject the port number as an environment variable. That'll come in as an environment variable named PORT. Let's update the Dockerfile to reflect that.

  1. Update
    ENTRYPOINT ["dotnet", "blazor-server.dll"]
    to
    CMD ASPNETCORE_URLS=http://*:$PORT dotnet blazor-server.dll

You should look like this now:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
WORKDIR /src
COPY blazor-server.csproj .
RUN dotnet restore "blazor-server.csproj"
COPY . .
RUN dotnet build "blazor-server.csproj" -c Release -o /app/build

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

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
CMD ASPNETCORE_URLS=http://*:$PORT dotnet blazor-server.dll

If you don't want to comment/uncomment your ENTRYPOINT when you need to run locally, you can run with a env flag e.g. docker run -d -e PORT=1234 -p 9876:1234 --name blazored my-blazor-server-image:neverlatest. * Use docker-compose and simplify your dev life!

Build and push to Heroku container ?service?

  1. Create a new app space in Heroku and get a redonkulous name using heroku create.
  2. Run heroku container:push web -a {redonkulous-new-name-you-just-got}.
    • web is the type: web or worker
    • -a is the app name reference
    • You can also push an already built image via docker push but this command handles everything for you as long as it can see the Dockerfile (It uses Docker to build the image, retags it, and pushes for you. Here's the Heroku docs if you need them.
  3. Run heroku container:release web to publish.
    • Should be running momentarily!

That's it!

At least, as far as I know. Mine works. :)

Bonus

This will work with anything as long as you can map the port env var at runtime. Going to scale is a different story but being able to enjoy the goodness of containers on an affordable platform is fantastic.

If you have any issues, sound off on Twitter @bitobrian and let me know if this helped.