Security change on December 1, 2026: TLS 1.2 will become the minimum standard for 365 business Print Agent. Details and system requirements
365 business development
CLI

Get started with the CLI

1. Install

The CLI ships as a .NET global tool. It targets .NET 8 (LTS) and runs on the .NET 8, 9 or 10 runtime.

TerminalCode
dotnet tool install --global businessdev.ALbuild.Cli # later: dotnet tool update --global businessdev.ALbuild.Cli

This installs the albuild command. Make sure the global tools folder is on your PATH (~/.dotnet/tools on macOS/Linux; %USERPROFILE%\.dotnet\tools on Windows).

Need the .NET SDK?

The tool needs a .NET runtime present. Install the SDK or runtime from dotnet.microsoft.com if dotnet --version fails.

2. Prerequisites

The CLI drives the businessdev.ALbuild PowerShell module, so it needs:

  • PowerShell 7+ (pwsh) on PATH.
  • The businessdev.ALbuild module: install it with the CLI itself:
TerminalCode
albuild module install # installs businessdev.ALbuild from the PowerShell Gallery albuild doctor # verifies PowerShell + module + Docker
  • Docker in the right mode only on a host that will run BC containers (Windows containers on Windows). A Mac/Linux client that talks to a remote MCP host does not need Docker locally.

macOS: 'You must install or update .NET to run this application' from pwsh

If albuild commands fail with a runtime error pointing at pwsh.dll (e.g. it asks for Microsoft.NETCore.App 8.0.0 while you only have 9.x), your PowerShell was installed as a net8 dotnet-tool but only a newer .NET runtime is present. Fixes, best first:

  1. Install PowerShell as a real package so it's self-contained: brew install powershell.
  2. Or let it roll forward onto the newer runtime: export DOTNET_ROLL_FORWARD=Major (add to ~/.zshrc). Recent CLI builds set this for the pwsh child automatically.

3. Configure dependencies — albuild.json

This is the step that pays off the most. ALbuild's headline feature is a real transitive dependency resolver: it reads your project's app.json dependencies, resolves a mutually-compatible, transitively-complete set of packages from your configured feeds (plus Microsoft's first-party feeds, registered automatically), downloads them into .alpackages, and writes a dependencies.lock.json for reproducible builds. No hand-curated .alpackages, no "works on my machine".

You drive it with one committed file at the repo root — albuild.json — which ALbuild reads automatically, so you never repeat --country/--bc-version/feeds on the command line or in a pipeline.

Scaffold it, then add your feeds:

TerminalCode
albuild init --country de --bc-version 28.2 albuild feeds add https://api.nuget.org/v3/index.json albuild feeds add https://pkgs.dev.azure.com/365businessdev/Public/_packaging/MSDyn365BCRuntimeApps/nuget/v3/index.json \ --name 365businessdev --kind runtime albuild feeds list

The result is a small, source-controlled file:

JSONCode
{ "country": "de", "artifactType": "Sandbox", "bcVersion": "28.2", "select": "Latest", "feeds": [ // a plain string is the simplest form (defaults to kind 'apps') "https://api.nuget.org/v3/index.json", // an object lets you name the feed and set its kind { "url": "https://pkgs.dev.azure.com/365businessdev/Public/_packaging/MSDyn365BCRuntimeApps/nuget/v3/index.json", "name": "365businessdev", "kind": "runtime" } ] }
  • kind tells the resolver what a feed serves: apps (default, installable apps), symbols (compile-only symbol packages) or runtime (sealed runtime packages). Here, NuGet.org carries app/symbol packages and the 365 business development feed serves runtime packages.
  • Microsoft's feeds are automatic. The resolver always registers Microsoft's public symbol/app feeds (MSSymbols, AppSourceSymbols, MSApps), so you only list your ISV/partner feeds.
  • Private feeds keep their key out of the file. Add --api-key-env <VAR> (or "tokenEnv": "<VAR>" in the object) — it records the name of an environment variable holding the token, resolved at run time:
TerminalCode
albuild feeds add https://pkgs.dev.azure.com/acme/_packaging/private/nuget/v3/index.json \ --name acme --kind apps --api-key-env ACME_PAT

Preview what would be resolved (downloads nothing), then restore for real:

TerminalCode
albuild deps plan --project ./app --workspace . --bc-version 28.2 # dry-run: the exact set + versions albuild deps install --project ./app --workspace . --container <name> # resolve + install into a container

Mixed sources, one resolver

The same resolver also pulls Azure DevOps Universal Packages and committed local .app files, alongside NuGet v3 feeds, and pins Microsoft first-party apps to the target build. See the dependency resolver deep dive and the full albuild.json reference.

4. Check AL logic without a container

Before any of the walkthroughs below: not every question needs a container. albuild probe interprets your AL locally, so "what does this actually return?" is answered in a fraction of a second instead of a publish-and-test cycle.

TerminalCode
albuild probe scope --project ./app # what can be probed here? albuild probe call --project ./app \ --target '"bdev.BNK Bank Formatter"::FormatIBAN' \ --args '["DE89370400440532013000"]' # call one procedure albuild probe eval --vars 'd: Date' \ --code "d := CalcDate('<CM>', 20260115D);" # evaluate a few statements

Probe refuses rather than guesses: anything it cannot model exactly returns a stable error code instead of a plausible-looking wrong answer. Treat it as a fast pre-check, then confirm with the real test run below. See Probing AL.


5. Walkthroughs

5.1 Local Windows developer — a container for local development

On a Windows box with Docker (Windows-container mode), everything runs locally — no --server.

TerminalCode
albuild doctor # PowerShell + module + Docker all green? albuild init --country de --bc-version 28.2 # write albuild.json (commit it) — see §3 albuild container new --country de --bc-version 28.2

container new prints how to connect the moment the container is ready:

Code
Connect to 'albd2030d4': Web client: https://albd2030d4/BC/ Container IP: 172.19.126.46 (add '172.19.126.46 albd2030d4' to your hosts file if the name does not resolve) Username: admin Password: Bc!JXyggABrCJqLCRKkhA5C

Open the web client in your browser and sign in with those credentials. To restore your app's dependencies into the container and run the inner loop, then tear it down when you're done:

TerminalCode
albuild container new --project ./app --workspace . # provision + install the dependency closure albuild run pipeline --workspace . --container albd2030d4 # build → publish → test albuild app test --container albd2030d4 --coverage # re-run tests with code coverage albuild coverage threshold --path ./CodeCoverage --workspace ./app --min 80 # gate (exit 1 if below) albuild container remove --container albd2030d4 # tear down when finished

Prefer the editor? Use the VS Code extension

The ALbuild VS Code extension creates and inspects BC containers, restores dependencies and runs the pipeline from the sidebar — the same engine, without leaving the editor. Great if you'd rather click than type.

5.2 GitHub Copilot (VS Code) — let the agent drive the CLI

Give Copilot's agent mode an instructions file so it runs the build/test loop through albuild instead of hand-writing pwsh/docker, and manages a single warm container per task. Commit .github/copilot-instructions.md:

MarkdownCode
## Validating AL changes (Business Central) — use the albuild CLI Never hand-write pwsh or docker; use the `albuild` CLI and read its `--json` output. 0. **Check the cheap way first.** For a question about what AL logic returns, run `albuild probe call ... --json` or `albuild probe eval ... --json` — no container, answered in a fraction of a second. Only fall back to the container loop when the answer depends on the real platform, or to confirm the final state. 1. **One warm container per task.** Create it once and remember its name: `albuild container new --country de --bc-version 28.2 --json` → read `data.containerName`. Reuse that **same** container for every iteration below — it stays warm, do not recreate it. 2. **After each change:** `albuild run pipeline --workspace . --container <name> --json` then read `data.tests` ({ passed, failed, failures[] }). Fix failures and repeat. 3. **When the task is finished:** `albuild container remove --container <name> --yes`.

Open the repo in VS Code, switch Copilot Chat to Agent mode, and ask it to implement and validate a change. It runs the commands in the integrated terminal and reads the structured results — create once, iterate on the warm container, drop it at the end.

Want an automatic warm pool — or the native VS Code path?

For a real warm pool across sessions (automatic reuse + LRU eviction), point the CLI at a local MCP server with --backend mcp: container new then warm-reuses a matching container from the pool instead of building a new one. Alternatively, the ALbuild VS Code extension contributes the MCP server to Copilot automatically, so the agent gets typed ensure-container/deploy-and-test tools (warm pool + auto-cleanup) — see the MCP server guide.

5.3 Claude Code — let the agent drive the CLI

Claude Code drives the CLI through its shell. Commit a CLAUDE.md at the repo root with the same loop:

MarkdownCode
## Business Central inner loop — use the albuild CLI - Before reaching for a container, check whether `albuild probe` can answer it: `albuild probe call --project ./app --target '"<object>"::<procedure>' --args '[...]' --json`. It interprets the AL locally in a fraction of a second and refuses rather than guessing. - Create one container at the start and reuse it for the whole task (do not recreate it each iteration): `albuild container new --country de --bc-version 28.2 --json` → keep `data.containerName`. - Validate a change: `albuild run pipeline --workspace . --container <name> --json` → read `data.tests`. - Tear down when finished: `albuild container remove --container <name> --yes`.

Claude Code reads CLAUDE.md at session start and runs these via its shell tool, iterating build → publish → test against the one warm container and removing it when the task is done.

Or use the MCP server directly

Claude Code can also drive the ALbuild MCP server (typed tools, a warm pool and removal governance) instead of the CLI — drop a .mcp.json pointing at npx -y @365businessdev/albuild-mcp. See MCP server → Get started. On a Mac, point both the CLI and the MCP server at a remote host (next walkthrough).

5.4 macOS / Linux — drive a remote MCP host

You can't run a BC container on a Mac, but you can still compile. Run the heavy container work on a shared Windows MCP host and point the CLI at it. Two sides — the host (Windows server) and each client. The bearer token is generated on the host and handed to the client out-of-band; it is never written into mcp.json.

On the host (Windows + Docker):

TerminalCode
albuild mcp install # npm i -g @365businessdev/albuild-mcp (needs Node.js) albuild agents add linus --tools build-app,run-tests,publish-app albuild agents token linus # prints linus's bearer token → copy it to the client albuild mcp serve --http --port 5020 # host it; tokens + agent roster wired in automatically # or as an auto-starting Windows service (needs NSSM + an elevated shell): albuild mcp service install --port 5020

On each client (your Mac/Linux box):

TerminalCode
albuild mcp configure --client claude-code --url http://winsrv:5020/mcp # writes mcp.json with "Authorization": "Bearer ${ALBUILD_MCP_TOKEN}" export ALBUILD_MCP_TOKEN='<linus token from the host>' # PowerShell: $env:ALBUILD_MCP_TOKEN = '...' albuild mcp status --server http://winsrv:5020/mcp # verify the client can reach the host albuild run pipeline --workspace . --container <name> --server http://winsrv:5020/mcp # build local → test remote

The token is read from ALBUILD_MCP_TOKEN at run time (--token-env <VAR> to choose another variable). Keep it in the environment / a secret store, not in the committed mcp.json.

Compilation is always local

Even when you target a remote host, albuild app build (and the build step of run pipeline) compile on the client: your AL source and the compiler stay on your machine. Only the container ops (publish, install, test, unpublish) go to the host, and the locally-built .app files travel there as uploads. This is the classic macOS flow: compile here, run there.

Make the remote host the default

So you don't repeat --server on every command, save it as the default. albuild mcp configure --server <name> --url <url> records a named profile in ~/.albuild/config.json and the first one configured becomes the default; pass --default to force it. When --server is omitted, the CLI resolves it in this order: --server → ALBUILD_SERVER env var → configured default → local.

TerminalCode
albuild mcp configure --server prod --url http://winsrv:5020/mcp --default albuild mcp default # show the current default + known profiles albuild run pipeline --workspace . # no --server needed → uses 'prod' albuild app build --workspace . # still compiles LOCALLY (server only redirects container ops) albuild mcp status --server local # force local for a one-off, ignoring the default albuild mcp default --unset # clear the default → commands run local again

This gives you the natural split: on a Mac, set the default once and every command goes remote; on Windows, leave it unset to run local by default and opt into a host per-command with --server prod (or tell your agent to). ALBUILD_SERVER is the per-shell override, handy in CI.


Next: the full command reference.

Last modified on