Using wkhtmltopdf with Docker

Recently I needed to get wkhtmltopdf working in a Docker container with ASP.NET Core. The wonderful thing about Docker containers is that all dependencies are packaged up so you don’t have to worry about installs, versions, deployments, etc. But it does mean setting up the initial environment is a little trickier.

TL:DR Gimme the Dockerfile

For the impatient the full Dockerfile is as follows:

FROM microsoft/aspnetcore:1.1.2

COPY lib/wkhtmltox /usrtmp
RUN (cd /usrtmp && tar c .) | (cd /usr && tar xf -) && \
    chmod 555 /usr/bin/wkhtmltopdf && \
    apt-get update && \
    apt-get install -y --no-install-recommends zlib1g fontconfig libfreetype6 libx11-6 libxext6 libxrender1 && \
    rm -rf /var/lib/apt/lists/*

COPY bin/Release/PublishOutput .
ENTRYPOINT ["dotnet", "Site.dll"]

Version Issues

You need to run a new enough version to operate correctly in headless mode on newer versions of Linux. 0.12.1-2 is the current version used by Debian, and this will not work. I used the latest 0.12.4 from the main site. Older versions also have heavy dependencies on X11, severely bloating your container.

The default package available in apt will not work.

Permissions Issues

By default, copying files into the Docker container will result in them being read-only, no execute permissions. Additionally, you can’t just do a RUN chmod and expect the files to have the updated permissions. This is due to a bug (maybe?) where you can’t really amend the permissions of another layer – it just doesn’t work. Instead you need to copy the files to one place, then apply your chmod changes:

COPY lib/wkhtmltox /usrtmp
RUN (cd /usrtmp && tar c .) | (cd /usr && tar xf -) && \
    chmod 555 /usr/bin/wkhtmltopdf

This copies the binaries from lib/wkhtmltox to the /usrtmp directory in the image, then moves the files to the real location of /usr. Finally, we set execute permissions for the binary itself.

Dependency Issues

Finally, wkhtmltopdf requires certain dependencies to be present to function. These can be installed as usual via apt:

RUN (apt-get update && \
    apt-get install -y --no-install-recommends zlib1g fontconfig libfreetype6 libx11-6 libxext6 libxrender1 && \
    rm -rf /var/lib/apt/lists/*

We have a few extra options here above what you’d normally run on your own personal Linux install:


We only want the libraries we absolutely must have to make the tool work. So, this flag ensures only the minimum is installed, and not any extra utility packages that might be useful in a full-install. Remember this is a container, so the bare minimum is all we’ll ever need.

rm -rf /var/lib/apt/lists/*

Once we’ve got all the packages installed, we won’t be using the package cache again. So, remove it to keep the container size down.

Posted by Dan in Guides, Programming, 1 comment

Pre-compress static web files using GZip and Brotli automatically

If you’ve worked with the web for any amount of time, you’ll know that compression is one of the very best ways of improving page load times. You might also be annoyed by the fact that you’re wasting CPU cycles on compressing the same files over and over – not to mention the added latency waiting for the compression to complete. The best option is to pre-compress all of your static files as part of the build or deploy process of your web application. For just this requirement, I’ve created a small Node script that’ll recurse through a directory compressing all of the files it locates.

The Code

You can find the Gist here:

Simply update the last line to point to the directory you want to compress. By default, I’ve got the script compressing ‘dist’.



Next, you’ll need to configure your web server to use the pre-compressed files instead of compressing them on the fly. For Nginx you’ll need to do the following:

Then add the following lines to your Nginx config for either the http configuration or the location configuration:

  • gzip_static on;
  • brotli_static on;

And that’s it!


If you want to use Nginx in a Docker container with Brotli, you can use this very cool Github project: Then in the Dockerfile for your website, ensure you use your custom Nginx image instead of the official one.

My configuration for Nginx looks like the following:

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        gzip_static   on;
        brotli_static on;
Posted by Dan in Programming, 0 comments

Building an ASP.NET Core Docker Image on Linux

ASP.NET Core is super awesome, especially since it plays really well with the latest deployment methods. Microsoft has even gone as far as to pre-package an optimised .NET Core Docker image with the core libraries pre-compiled for a super-fast startup. So let’s get started, but first you’ll need to install Docker and the .NET Tools on your Linux machine if you haven’t already.

Add a Dockerfile

Add a new text file called ‘Dockerfile’ (case sensitive) to the root of your project, make sure it doesn’t have any extension (such as .txt). In the Dockerfile add the following code:

FROM microsoft/aspnetcore:1.1.0
COPY ./output .
ENTRYPOINT ["dotnet", "MySite.dll"]

Update the ‘MySite.dll’ reference to the name of your project with a .dll extension. Also while you’re at it, change the version number of .NET Core if you’re not using 1.1 like I am. I highly recommend 1.1 or later with Linux due to much better performance.

Build from command line

Run the following commands in the project directory:

dotnet restore
dotnet publish -o output -c release

This will get all dependencies, compile a release build, and put the result into the ‘output’ directory. The reason we’re using a sub-directory is due to a bug in the tooling, if we use a parent relative path (../) the ‘publishOptions/include’ config setting in project.json will be ignored and you’ll be missing a chunk of your project!

Build the docker image

Now let’s get that code into a Docker image! Run the following command:

sudo docker build -t myapp .

Feel free to rename myapp to something a little more descriptive. You can also specify multiple tags, such as:

sudo docker build -t myapp -t myapp:1.0 .

It’s really up to you with regards to tagging. If you’re publishing to a repository (likely), make sure to add the applicable repository tag to the list.

Running the image

Run the following command to start up a new container:

sudo docker run -p 8000:80 myapp 

Make sure to update myapp to the name you used earlier when building. Your site should now be accessible on port 8000: http://localhost:8000

You may be wondering why we forwarded to port 80, and not 5000 or whatever port you’ve specified in launchSettings.json. As part of the aspnetcore image, an environment variable is set to tell Kestrel to host on port 80. This can be overridden either in your Dockerfile, or using app.UseUrl in your Program.cs file.

Don’t connect directly to the container

Remember, the site is still running in Kestrel so it’s not magically secure by virtue of running in a container. Make sure to use a reverse-proxy such as nginx when running your site in a production environment. Microsoft are working on hardening Kestrel so that you can use it directly in the future – but we’re not there yet.

Possible errors

Did you mean to run dotnet SDK commands?

You might get the following error upon execution:

Did you mean to run dotnet SDK commands? Please install dotnet SDK from:

This basically means the .dll you referenced couldn’t be found. Double-check all of your paths and filenames. It’s easy to include the source instead of the output, or even save to / from the wrong directory.

Could not load <dependency>.dll

If you get dependency errors on execution, it means the output of the publish wasn’t saved to the /app folder within the container. The .dll files from the publish operation must all go directly into the root of the /app folder in the container. This means you can’t rename /app to something else.

More information

You can get more information from:


Official MS Github: Navigate to the version of .NET Core you want (1.0/jessie, 1.1/jessie, etc.). If you’re using the output of a publish, you’ll want the ‘runtime’ subfolder.


Official MS Docker Hub: A quick overview of the base image, although it looks like MS want to spend most of their focus on the Github account.

Posted by Dan in Guides, 0 comments