Update 2021/2/10: BcContainerHelper has replaced NavContainerHelper. This blog post still reference NavContainerHelper, which is outdated.
Quite a few partners have build their CI/CD pipelines based on the HelloWorld CI/CD sample repository here: https://dev.azure.com/businesscentralapps/HelloWorld (the one used in the Hands On Lab – http://aka.ms/cicdhol).
I have just finished the upgrade of this repository to use artifacts instead of docker images. This blog post describes the changes done.

Settings.json
https://dev.azure.com/businesscentralapps/HelloWorld/_git/HelloWorld?path=%2Fscripts%2Fsettings.json
Settings.json was changed to include artifacts instead of container images. Also I have removed the alwaysPull setting, which is really designed to indicate whether or not to check if there is a newer image available – that is not needed anymore.
Create-Container.ps1
Create-Contailer was changed to look at the artifact setting instead of the ContainerImage setting. Furthermore, if the imagename is set in settings, it transfers this to the container to pre-build a specific image for subsequent usages.
Read-Settings.ps1
Read-Settings.ps1 was changed to use the new settings and set devops variables accordingly.
CI.yaml
https://dev.azure.com/businesscentralapps/HelloWorld/_git/HelloWorld?path=%2Fscripts%2FCI.yml
In the pipeline, I removed the task, which performs the insider docker login and I changes the pool to use azure hosted agents.
Local-Build and AzureVM-Build
https://dev.azure.com/businesscentralapps/HelloWorld/_git/HelloWorld?path=%2Fscripts%2FLocal-Build.ps1 and https://dev.azure.com/businesscentralapps/HelloWorld/_git/HelloWorld?path=%2Fscripts%2FAzureVM-Build.ps1
These functions are used to simulate/run a pipeline locally or in an Azure VM. The functions have been changed to use the new settings (and cache docker images if imageName was specified)
Local-Sandbox and AzureVM-Sandbox
https://dev.azure.com/businesscentralapps/HelloWorld/_git/HelloWorld?path=%2Fscripts%2FLocal-Sandbox.ps1 and https://dev.azure.com/businesscentralapps/HelloWorld/_git/HelloWorld?path=%2Fscripts%2FAzureVM-Sandbox.ps1
These functions are used to create a sandbox container for the project locally or in an Azure VM. The functions have been changed to use the new settings (and cache docker images if imageName was specified)
Initialize.ps1
Initialize is used by the functions which locally or in Azure VMs creates sandboxes or runs build pipelines to read settings and initialize values. Kind of the local version of Read-Settings.
Adopt the changes
I updated the scripts for all three versions (hybrid, 14.x and 16.x). If you haven’t modified the PowerShell scripts, you can probably just download new versions and override the old. The settings and yaml file you will have to change by hand.
The hands on lab will also be updated to adopt the changes to artifacts.
Hope this helps
Enjoy
Freddy Kristiansen
Technical Evangelist
Hello and thanks for the great bunch of post about CI/CD, however could you explain how do you declare a license file stored in the repos, in the pipelines variable? Hands on Lab is using a local agent if i’m not mistaken so license store locally.
LikeLike
This blogs post: https://freddysblog.com/2017/02/26/create-a-secure-url-to-a-file/ talks about how to get a secure url to a file.
LikeLike
Hi! I do have a question here regarding dependencies. I have been trying to figure out why my builds are failing to compile. After some time i found that this example of yours increases the app build version for each run. So for the first run everything is on 1.0.0.0 and works. For second run the version of “base” will be 1.0.1.0, and this will cause the compilation of the extension “app” to fail because it depends on “base” 1.0.0.0 still. Note you will only get this out if you remove “base” 1.0.0.0 from the build symbols folder.
So for this example you have, should we not also increase the dependency automatically? If we have a multiple extensions that is not the same version i under stand the current code. However if we would like to increase all extensions versions to the same we should also increase the dependency version. Or am i maybe thinking about this in the wrong way?
LikeLike
The way I am going around things here are just a sample – you do not need to update versions numbers in the same way. I think dependencies should download higher versions if the exact version isn’t available.
LikeLike
Hey Freddy, I am currently on my first attempts go get your sample up and running and I think I figured it out now. I also see that you just recently adjusted the whole sample scripts to switch from images to artifacts. We curate our own registry to speed up access to the containers on our development machines since the artifact way takes longer and the pipelines could run on different VMs. Pulling the images from our central registry is much faster and I wonder if it is still possible to use images instead of artifacts in the latest versions by manipulating some variables (the -imagename parameter in the Create-Container.ps1 maybe)?
LikeLike
I am actually working on a new version of the HOL document as we speak – and the repo will also change a lot. It will include a way to use images as well as artifacts – but scripts have changed a lot….
LikeLike
Wow, that sounds awesome! Thank you so much for sharing your hard work with us 🙂
I´m really looking forward to the new version. In the meantime I will have time to gain some experience with the current version and the processes involved to set up proper ci/cd pipelines 🙂
LikeLike
Hello, is there some kind of access to a preview version of your scripts where I could test the usage of images instead of artifacts? Or am I asking too early? 🙂
LikeLike
Sorry no. The HOL is complete with artifacts and it is basically just transferring an imagename to run-alpipeline instead of artifactUrl and imagename – and then read that from settings.
Note that you would have to build these images yourself, we don’t ship images anymore.
LikeLike
I am not sure I am interpreting your answer correctly. The no sounds like it is not possible, The rest of your answer creates hope 😉
I would suppose if it works, I should add another “version” in the settings.json file, leave the $version.artifact parameter empty and define the $version.imageName parameter. But the latter one does not seem to be valid for the settings.json. I don´t see how else it could be implemented. Altering the call of Run-ALPipeline in the DevOps-Pipeline.ps1 could do, but I think I am altering a Script in a spot that will break anything else. And as far as my current test is running it seems I am right 🙂
Do you have a hint for me how to alter the settings.json(?) so an empty artifact parameter is created and I can still fill the imagename parameter for the Run-AlPipeline cmdlet when it is called later in the process?
As for the images, that´s fine. We build images on the fly for the sandbox environments, where you need to pull the latest artifacts all the time anyway. But for the stable versions and on-prem releases, we build the images based on the artifacts for our own registry so we can re-use them very quickly. Downloading images from there is multiple times faster than downloading artifacts and building the images on the fly. So this saves a lot of time for build runs. That is why I am investing time to also be able to use images in the pipelines where it helps us and using artifacts where it makes no sense to pre-build images for our registry.
LikeLike
Thanks for putting this together.
In the Run Pipeline, after the
Removing C:\ProgramData\BcContainerHelper\Extensions\hostedagent-helloworld-ci
I’m receiving the following error?
##[error]Download-File : Exception calling “DownloadFile” with “2” argument(s): “The remote server returned an error: (403) Forbidden.”
LikeLike
please create an issue on http://www.github.com/microsoft/navcontainerhelper/issues and include the full output of the pipeline (including container generation and all)
LikeLike
Hi Freddy, awsome piece of work http://aka.ms/cicdhol
I want to develop an app for a NL-NL BC version and also for a US version. How to set this up?
Do I need 2 repositories and sync app code ? Do I need two Containers ?
LikeLike
If it is the same app – it is one repo and supportedcountries = “a,b,c’
If it is different apps – it is more repos
That is how I would set it up.
LikeLiked by 1 person
Hello Freddy, I am following the lab, but get an error when running Local-DevEnv.ps1: ‘No app folders found’. See output below. Any idea?
PS C:\Users\MaartenGerritsenMagi\Documents\AL\CICD\CICD\scripts> .\Local-DevEnv.ps1
Set artifact = //17.3.20469.21555/us
Set pipelineName = HelloWorld.AppSource-ci
Set containerName = helloworld-appsource-ci
Set installApps = ”
Set previousApps = ‘https://businesscentralapps.azureedge.net/githubhelloworld-appsource/latest/apps.zip’
Set appSourceCopMandatoryAffixes = ‘hw_’
Set appSourceCopSupportedCountries = ‘us,dk,gb’
Set appFolders = ‘app,base’
Set testFolders = ‘test’
Set memoryLimit = ‘6G’
Set additionalCountries = ”
Set genericImageName = ”
Set vaultNameForLocal = ‘IgoBuildVariables’
Set bcContainerHelperVersion = ‘preview’
Set installTestRunner = True
Set installTestFramework = False
Set installTestLibraries = False
Set installPerformanceToolkit = False
Set enableCodeCop = False
Set enableAppSourceCop = True
Set enablePerTenantExtensionCop = False
Set useDefaultAppSourceRuleSet = True
Set doNotSignApps = True
Set doNotRunTests = False
Set cacheImage = True
Set CreateRuntimePackages = True
BcContainerHelper version 2.0.9
Get Secret buildpasswordSecret
Get Secret insidersastokenSecret
Get Secret licensefileSecret
No app folders found
At C:\Program Files\WindowsPowerShell\Modules\BcContainerHelper\2.0.9\AppHandling\Run-AlPipeline.ps1:333 char:5
+ throw “No app folders found”
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (No app folders found:String) [], RuntimeException
+ FullyQualifiedErrorId : No app folders found
LikeLike
Just tried to clone the repo and run local-devenv myself and it works fine here. Please use https://github.com/microsoft/navcontainerhelper/issues for creating issues, it is easier to keep track of answers and request more info, thanks
LikeLike
Thanks Freddy. Now it works. It must have been a setting somewhere. The other time(s) I changed the files manually. Now I used your script MySolution.ps1
BTW. the script also seems to require a secret for the Insider token. This is not mentioned in the hol pd.
LikeLike
Only if you build against next major release I think.
Else please create an issue on github
LikeLike