So that's not really fair to Microsoft. They mean well, but their black magic around docker and Visual Studio integration makes this harder than it need be.
I'm using SkiaSharp in a web api. Most of the documentation for SkiaSharp is focused on Xamarin development for mobile devices so you need to interpolate a little if your relying on their documentation.
This is quite a long post detailing my discoveries and solution. If you just want the cheese in this sandwich, press
To get SkiaSharp working in a windows solution you just need to add the SkiaSharp nuget to your chosen project and away you go.
For Linux (with or without a container) it's a bit trickier.
Firstly you need to install 1 pre-req on Linux,
libfontconfig1. If your using a container, add this to the top of your docker file, just under the first (base)
RUN apt-get update;apt-get install libfontconfig1 -y
Otherwise, update your OS in whatever way you choose.
Visual Studio Docker Project
If your using a docker project in Visual Studio to develop & debug then you also need to copy the shared library,
libSkiaSharp.so, to the docker image. This shared library is not included with the SkiaSharp nuget package. You need to get it from the git releases page.
You could do this copy in your deployment script, as part of the docker image build or add it as a content file to your Visual Studio project (which may break your windows testing). But it gets worse - or at least it did for me.
When building using the default docker compose project and docker build files, the application could not find the .so file, even when I manually copied it to the bin folder (bin/Release/netcore2.0). I got:
System.DllNotFoundException: Unable to load DLL 'libSkiaSharp'
Microsoft black magic
When you create a docker project for a web api project, Visual Studio creates a dockerfile for you that looks something like this:
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base WORKDIR /app EXPOSE 53486 EXPOSE 44389 FROM microsoft/dotnet:2.1-sdk AS build WORKDIR /src COPY WebApplication4/WebApplication4.csproj WebApplication4/ RUN dotnet restore WebApplication4/WebApplication4.csproj COPY . . WORKDIR /src/WebApplication4 RUN dotnet build WebApplication4.csproj -c Release -o /app FROM build AS publish RUN dotnet publish WebApplication4.csproj -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "WebApplication4.dll"]
This is a multi-stage build. Note the various
FROM lines. You might naturally, logically, rationally think any changes you make to this file will be reflected in your image when you press
F5 to run the docker compose. Unfortunately, you would be incorrect.
Building the docker project in debug mode does not use all of your dockerfile.
Take note of this line during the build:
docker-compose -f "c:\Dev\WebApplication4\docker-compose.yml" -f "c:\Dev\WebApplication4\docker-compose.override.yml" -f "c:\Dev\WebApplication4\obj\Docker\docker-compose.vs.debug.g.yml" -p dockercompose16087381738953648249 --no-ansi up -d --build --force-recreate --remove-orphans
Notice the docker-compose file in the obj\Docker folder? Visual Studio generates this temporary docker-compose file as part of the build and sets this during the build. It overrides your solutions docker-compose file. If you look at this generated file you will see a few interesting things:
version: '3.4' services: webapplication4: image: webapplication4:dev build: target: base environment: - DOTNET_USE_POLLING_FILE_WATCHER=1 - NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages volumes: - c:\Dev\WebApplication4\WebApplication4:/app - C:\Users\peter\vsdbg\vs2017u5:/remote_debugger:ro - C:\Users\peter\.nuget\packages\:/root/.nuget/packages:ro - C:\Program Files\dotnet\sdk\NuGetFallbackFolder:/root/.nuget/fallbackpackages:ro entrypoint: tail -f /dev/null labels: com.microsoft.visualstudio.debuggee.program: "dotnet" com.microsoft.visualstudio.debuggee.arguments: " --additionalProbingPath /root/.nuget/packages --additionalProbingPath /root/.nuget/fallbackpackages bin/Debug/netcoreapp2.1/WebApplication4.dll" com.microsoft.visualstudio.debuggee.workingdirectory: "/app" com.microsoft.visualstudio.debuggee.killprogram: "/bin/bash -c \"if PID=$$(pidof -x dotnet); then kill $$PID; fi\""
build you will see the
base. So, anything you have for other targets gets ignored during a debug build.
Next, the volumes are set to override the /app folder but since it's only using the
base target, any customisations to you add to the
final target in your dockerfile are not used in debug.
Lastly, note the volume set for the nuget pages that map to your user folder.
The voodoo that they did here makes the debug experience very nice. You get to see changes in the source files instantly in the running container which is great for web apps and web api's, you don't need to wait too long for the build and of course you get the debugger attached to the running instance.
Getting Debug Container Working
Back to SkiaSharp. The missing piece of the puzzle here is you need to copy
libSkiaSharp.so to your local packages folder
C:\Users\\[me]\\.nuget\packages\skiasharp\1.60.0\lib\netstandard1.3 - replacing the version and your user name of course. And don't forget to install libfontconfig1 of course.
Deployment and Release Builds
If you look at the release version of the docker-compose file that Visual Studio generates you will see that this is much less aggressive:
version: '3.4' services: webapplication4: volumes: - C:\Users\peter\vsdbg\vs2017u5:/remote_debugger:ro entrypoint: tail -f /dev/null labels: com.microsoft.visualstudio.debuggee.program: "dotnet" #com.microsoft.visualstudio.debuggee.arguments: " $debuggee_arguments_probing_paths_webapplication4$ WebApplication4.dll" com.microsoft.visualstudio.debuggee.workingdirectory: "/app" com.microsoft.visualstudio.debuggee.killprogram: "/bin/bash -c \"if PID=$$(pidof -x dotnet); then kill $$PID; fi\""
Most importantly here, it does not override the target.
However, this is sort of moot as you are probably/hopefully not using your local builds for releases to testing and live. Right? YES ??
Heres the Cheese
If you use the standard dockerfile and managed to get
libSkiaSharp.so setup - your application will still not run. My solution was to customise the dockerfile thusly:
1: Use the dotnet dependencies-only base image and install
libFontConfig1 to it:
FROM microsoft/dotnet:2.0-runtime-deps as base RUN apt-get update;apt-get install libfontconfig1 -y
- In the publish target, create a self-contained deployment and copy the
FROM build AS publish RUN dotnet publish -c Release -r debian-x64 -o /app WORKDIR /src COPY ./linuxlibs/libSkiaSharp.so /app
Note above I built for
debian-x64. There are many other RID's you can use. If you read the instructions from Microsoft about creating a self-contained deployment it mentions updating the project file with the RID, e.g.
<RuntimeIdentifiers>win10-x64;debian-x64</RuntimeIdentifiers>. I found this wasn't necessary when using the CLI to publish.
3: Change the
ENTRYPOINT for the SCD build:
FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["./app output name (exe with no .exe :)"]
4: Optional. I create multiple dockerfiles for my app's - one for each config - so I have dockerfile, dockerfile.development and dockerfile.test.
Moral of this story
Only trust the black magic and voodoo you never actually see or experience. This is true magic. Everything else is smoke and mirrors.