Simple Smooth Safe / DocGen / docs / deployment ← All docs

SSS DocGen — Deployment Runbook

The supported path for standing DocGen up in a new environment: what the solution carries, what has to be done per environment, and the platform behaviours worth knowing before you start. Read this before deploying.

Before you start

  1. Deploy from an exported solution, not from hand-authored source. The deliverable is a solution exported from an environment where everything already works — see the ALM section below. An exported solution carries its relationships and imports cleanly; that is what export/import is for.
  2. Run infra/setup-azure.ps1 in PowerShell 7 (pwsh) or Azure Cloud Shell. It is supported under Windows PowerShell 5.1 as well, but pwsh avoids a class of Windows console encoding issues in the Azure CLI's build output.

Azure side (infra/setup-azure.ps1)

The script provisions the resource group, container registry, Key Vault and container app, then grants the container app's managed identity the roles it needs.

The first deploy is expected to retry

On a brand-new environment the container app's managed identity does not exist until the first deployment creates it — so that first revision cannot pull its image (AcrPull) or read its secret (Key Vault Secrets User) yet, and Azure role assignments take time to propagate. The script handles this: the first deploy is non-fatal, it grants both roles to the new identity, waits for propagation, and redeploys. A first-run message about an expired operation followed by a successful redeploy is the normal path, not a failure.

Re-running the script

The script is safe to re-run. Resource creation is guarded by existence checks, and the container image build is skipped when the tag already exists in the registry.

What you get

  • An app registration and service principal for the environment.
  • A container app on a scale-to-zero plan. Scale-to-zero (min 0) is intentional and is what makes the run cost what it is; the trade is a ~10–20 second cold start on the first request after an idle period.
  • A Key Vault holding the Entra client secret.

Dataverse side

Import the exported solution

Import the managed solution into the target environment. It carries the two tables (sss_documenttemplate, sss_templatedataset) with their columns, both relationships and the sss_code_key alternate key; the custom API sss_GenerateDocument and its request and response parameters; the environment-variable definitions; the plugin assembly; and the SSS DocGen API security role.

Set the environment-variable values

Definitions ship in the solution; values are per environment and are set after import. There are five — see Tenant Setup for the full table.

sss_DocGenClientSecret must be a Text variable, not a Key Vault-backed secret. The plugin reads its configuration with the out-of-the-box RetrieveEnvironmentVariableValue request, which does not resolve Key Vault-backed secrets — resolving those requires RetrieveEnvironmentVariableSecretValue. A secret-typed variable therefore returns empty and the plugin gets nothing.

The environment-variable type is immutable once created: if one was created as a secret, delete and recreate it as Text, keeping the schema name sss_DocGenClientSecret. Read the value out of Key Vault to set it:

az keyvault secret show --vault-name <kv> -n entra-client-secret --query value -o tsv

Because the value is then readable by anyone who can read environment-variable values in the environment, restrict that privilege to the administrators who need it. This is a known limitation with a known fix — see Plugin Registration — and it is covered in the security and compliance review, available on request.

Create the application user

Create an application user for the app registration's client id and assign it the SSS DocGen API security role: read on the source tables, create on annotation and email, and CRUD on sss_documenttemplate and sss_templatedataset.

Register the plugin

Run pac plugin push, then bind the custom API by setting customapi.plugintypeid on sss_GenerateDocument to SssDocGen.Plugins.GenerateDocumentPlugin.


Two things that must be right, or nothing renders

Token version must be v2

The rendering API validates the v2.0 issuer, so the app registration must be created with requestedAccessTokenVersion=2 (setup-azure.ps1 and main.bicep both set this). A v1 token — iss=https://sts.windows.net/<tid>/, aud=api://<clientid> — is rejected with a 401.

Because v2 tokens carry aud=<clientid> as a bare GUID, the API's Entra__Audience must be set to the bare client id, not api://<clientid>.

Target tables need notes enabled

The API stores the generated PDF as an annotation regarding the target record, so any sss_targetentity you generate against must have notes (attachments) enabled, and the application user's role needs Create on Note and Read on that table. Generating against a table with notes disabled fails with 0x80040205 "Creating Entity with an invalid parent. Entity: Annotation". Notes are a table setting in the maker portal and cannot be enabled by the solution.


ALM: pushing DocGen to the next environment

  1. In the source environment, finish building everything and add all components to the SSSDocGen solution.
  2. Export it with pac solution export — unmanaged for dev, managed for UAT and production.
  3. Commit the unpacked export as solution/src. It round-trips through pac solution pack with relationships intact and imports cleanly into the next environment.
  4. Per environment, the following are not in the solution and are still done by hand:
    • App registration and Azure infrastructure: infra/setup-azure.ps1 -EnvName <env>.
    • Environment-variable values: the five sss_DocGen* values.
    • Application user and security role, as above.
    • Plugin: pac plugin push and the customapi.plugintypeid binding.

Verifying the environment

Call the custom API against a record on a notes-enabled table:

sss_GenerateDocument(Code=SMOKE, EntityId=<guid>, Attach=true)

A healthy environment returns HTTP 200 with a rendered PDF in fileBase64 and an annotationId for the stored note — which exercises the whole path: plugin → v2 token → API → Dataverse → Chromium → PDF → annotation. Check warnings in the response as well as the status code; a truncated dataset or an unresolved parent key reports there rather than failing the call.