Making use of the new BC artifacts
As you probably have seen (1, 2, 3, 4 1) if you are following what is happening with Business Central on Docker, Microsoft has overhauled the way all of this works. While it stays very similar to what we had before from an end user perspective2, the whole structure underneath has changed a lot. I personally feel like this is a bit of a mixed bag because I can see why Microsoft doesn’t want to support all the needed images, but at the same time, if you take away the concept of an (immutable) image, the whole Docker story becomes a lot less appealing. So while I can see the benefits and again, the end user should probably not even notice anything has changed, I also see some drawbacks, especially around support in case of issues and how it works with container orchestrators like Kubernetes or Swarm.
But as always, let’s look at the bright side of things and dig into what we can now do more conveniently which was a lot more complicated before. There are a couple of interesting scenarios but the first one I decided to tackle is this: Compiling a Business Central app in a container was very slow in the old way as the whole infrastructure like SQL and BC needed to start before you could start compiling. And if you ran this as part of a pipeline on a hosted machine where you didn’t have control over the images, it got even slower, because the very big BC image had to be pulled. You could work around that and I have blogged about that a couple of years ago, but with the new artifacts, it gets a lot easier.
Building an “SDK” image for BC is a three-step process:
- Create a navcontainerhelper image because the cmdlets to get artifacts are very handy
- Create an artifact image with all the artifacts you might need, mainly the compiler and symbols
- Create the SDK image with the compiler and applications symbols so that you can compile.
How quick is that? I can bring it to 5.5 seconds for the standard sample created with “AL: Go!” in VS Code:
Of course, this will be slower with real life projects but for the hosted CI/CD scenario, pulling the image alone takes a couple of minutes and even starting a container if the image is already there, takes between 40 and 60 seconds for the standard image, so this really is A LOT quicker! Pulling also would be quicker because my SDK image is based on the nanoserver Windows container, which means it has a grand total size of 587 MB if it runs on 1809:
On a side note, as you can see in the screenshot as well: If we go to 2004 as base image version, the end result is minimally bigger because while the servercore image has become a lot smaller, the nanoserver one has grown by 12 MB.
As I am not yet sure, if and how I can share those images on the Docker hub or on an Azure container registry, I will only share the Dockerfiles at the moment. You can find the on Github.
The details, part 1: The navcontainerhelper image
As I already wrote, I decided to go with the artifact cmdlets in navcontainerhelper instead of just using the code because it’s more convenient. But because I need the artifacts in a container image and I don’t want to rely on anything on the host, navcontainerhelper needs to run in a container. Fortunately, creating am image for that purpose is quite easy by using the following Dockerfile:
1 2 3 4 5 6 7 8 # escape=` ARG BASE FROM mcr.microsoft.com/windows/servercore:$BASE ARG NCHVERSION SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"] RUN Install-PackageProvider -Name NuGet -MinimumVersion 188.8.131.52 -Force; ` Install-Module navcontainerhelper -MinimumVersion $env:NCHVERSION -MaximumVersion $env:NCHVERSION -Force;
The interesting part is in lines 7 and 8 where the right package provider as well as the right navcontainerhelper version is installed. With that in place, we can run a command like
docker build -t tobiasfenster/navcontainerhelper:0.7.0.9-1809 --build-arg BASE=1809 . to create the image. Navcontainerhelper online adds 40MB to the image, so that doesn’t really hurt, especially considering how big the standard Windows image is.
The details, part 2: The artifacts image
With navcontainerhelper in place, we can now go ahead and download the artifacts that we need. In order to get the right artifacts, I have added build args for type, country and version (similar to the artifact cmdlets). The artifacts are downloaded, the AL language .vsix file is extracted to get the compiler (alc.exe) and the System.app file is put in the same folder as the other symbol files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # escape=` ARG BASE ARG NCHVERSION FROM tobiasfenster/navcontainerhelper:$NCHVERSION-$BASE ARG TYPE ARG COUNTRY ARG VERSION SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"] RUN Import-Module navcontainerhelper; ` Download-Artifacts -artifactUrl (Get-BCArtifactUrl -type $env:TYPE -country $env:COUNTRY -version $env:VERSION) -includePlatform; RUN Copy-Item "C:\bcartifacts.cache\$env:TYPE\$env:VERSION\platform\ModernDev\program` files\Microsoft` Dynamics` NAV\*\AL` Development` Environment\ALLanguage.vsix" "C:\bcartifacts.cache\$env:TYPE\$env:VERSION\ALLanguage.zip"; ` Copy-Item "C:\bcartifacts.cache\$env:TYPE\$env:VERSION\platform\ModernDev\program` files\Microsoft` Dynamics` NAV\*\AL` Development` Environment\System.app" "C:\bcartifacts.cache\$env:TYPE\$env:VERSION\$env:COUNTRY\Applications.$env:COUNTRY"; ` Expand-Archive "C:\bcartifacts.cache\$env:TYPE\$env:VERSION\ALLanguage.zip" "C:\bcartifacts.cache\$env:TYPE\$env:VERSION\ALLanguage";
Again, to build the image, all that is needed is a build command with all the necessary args:
docker build -t tobiasfenster/bc-artifacts:sandbox-16.2.13509.14155-de-1809 --build-arg NCHVERSION=0.7.0.9 --build-arg BASE=1809 --build-arg TYPE=Sandbox --build-arg COUNTRY=de --build-arg VERSION=16.2.13509.14155 .. The artifacts (including the platform) are rather big and add 2.35 GB to the image size.
The details, part 3: The “SDK” image
Now everything is prepared for the last step, the “SDK” image. It needs to have the alc compiler as well as the symbols, so those need to be copied from an artifacts image to the target image:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # escape=` ARG BASE ARG TYPE ARG COUNTRY ARG VERSION FROM tobiasfenster/bc-artifacts:$TYPE-$VERSION-$COUNTRY-$BASE AS artifacts ARG TYPE ARG COUNTRY ARG VERSION SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"] RUN Remove-Item "C:\bcartifacts.cache\$env:TYPE\$env:VERSION\$env:COUNTRY\Applications.$env:COUNTRY\*.zip" FROM mcr.microsoft.com/windows/nanoserver:$BASE ARG TYPE ARG COUNTRY ARG VERSION COPY --from=artifacts C:\bcartifacts.cache\$TYPE\$VERSION\ALLanguage\extension\bin c:\bin COPY --from=artifacts C:\bcartifacts.cache\$TYPE\$VERSION\$COUNTRY\Applications.$COUNTRY c:\symbols CMD c:\bin\win32\alc.exe /project:c:\src /packagecachepath:c:\symbols /out:c:\src\app.app
The last line shows the compilation command that is executed when you run the image, so you need to make sure to bind mount your project folder to c:\src.
After running something like
docker build -t tobiasfenster/bc-sdk:sandbox-16.2.13509.14155-de-1809 --build-arg BASE=1809 --build-arg TYPE=sandbox --build-arg COUNTRY=de --build-arg VERSION=16.2.13509.14155 -f Dockerfile ., we now have an image with the alc compiler and all the standard symbols we might need. Because I have used nanoserver, the image is very small in total with the Business Central stuff adding 330 MB to the image.
Because of this and as we don’t need to wait for SQL and BC to start, the compilation is very quick. Of course, to run e.g. automated tests, you still need all the other components, but if you only want to compile, maybe because it is just a push to a feature branch and you run the whole story including tests only nightly but are fine with just a compilation for the push. And 5.5 seconds to find out if your code still compiles in a pristine environment seems quite fair.
No webmentions were found.