Traditionally the way to host applications on Heroku is with a Buildpack, and while there are a few options for hosting ASP.NET Core apps with a Buildpack, I wanted to try their new Docker Container Registry and Runtime.
Hosting an ASP.NET Core application this way is probably better in the long run because you're using an official Microsoft image as your base and your deployment isn't tied to Heroku. You could use your application image anywhere Docker is supported.
After following the guide on the Heroku Dev Center, the biggest stumbling block I had was passing the
PORT env to the application.
At first, I tried using the CommandLine configuration extension, so I could pass the port into the Dockerfile
CMD like in the Heroku demo application. Passing
--server.urls http://*:$PORT as a command line argument to the application worked in dev, but as a published .NET project in a built Docker image, the application was still trying to bind to port 80, and I wasn't immediately able to figure out why in production it was not respecting the
The workaround I went with was using the EnvironmentVariables configuration extension and pass the
ASPNETCORE_URLS env variable in the Dockerfile
FROM microsoft/aspnetcore:1.1.0 RUN adduser --disabled-password deployuser USER deployuser WORKDIR /app COPY . . CMD ASPNETCORE_URLS=http://*:$PORT dotnet HeroicHaiku.dll
The constraints that Heroku has will probably make you consider some Docker best practices when deploying your application, such as:
- Run as a non-privileged
- Be mindful of the size of your image
Commands in the Dockerfile need to be run as a non-root user when deploying to Heroku. When trying to run
dotnet restore to the Dockerfile, I ran in to a permissions issue trying to restore as a non-root user.
dotnet publish locally and then building an image from the published directory is probably a best practice, but it also gets around the permission issue.
Also, Heroku has limits on the size of the slug of an application and says that Docker images run the same way as slugs do on dynos and with the same constraints. So, I used the
microsoft/aspnetcore Docker image as a base image to keep the application image small.
The application image I pushed to the registry ended up being 276.1 MB.
Compared to the traditional Heroku workflow of
git push heroku master, deploying a Docker-based application to Heroku is a multi-step process:
- Publish the ASP.NET Core project
- Build a Docker image
- Push it to the registry
However, it can be easily scripted. First, the Heroku Toolbelt is required to get an auth token, but then logging in is just like logging in to any other Docker registry, except you use
_ for your username and your Heroku auth token for the password.
heroku login docker login --username=_ --password=$(heroku auth:token) registry.heroku.com
And finally, deploying to Heroku is just as easy as pushing your image to their registry. Here is what my deploy script looks like:
#!/usr/bin/env sh APP_NAME=heroichaiku # Build dotnet publish docker build bin/Debug/netcoreapp1.1/publish -t $APP_NAME # Publish docker tag $APP_NAME registry.heroku.com/$APP_NAME/web docker push registry.heroku.com/$APP_NAME/web
The obvious next step is adding a connection to a database. Fortunately, connecting Entity Framework Core to a PostgreSQL database is is very straightforward thanks to Npgsql. The only issue I can think of is converting the
Heroku provides to the connection string syntax.