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 --server.urls
flag.
The workaround I went with was using the EnvironmentVariables configuration extension and pass the ASPNETCORE_URLS
env variable in the Dockerfile CMD
.
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:
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.
Running 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.
Container Name | Size |
---|---|
microsoft/aspnetcore-build | 927.7 MB |
microsoft/dotnet | 608.5 MB |
microsoft/aspnetcore | 266.8 MB |
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:
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
You can find the full project on Github.
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 DATABASE_URL
Heroku provides to the connection string syntax.