Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

9 Commits

Folders and files

Repository files navigation

Azure Container Apps Service Bus Job Execution POC

Context: the problem solved

The goal is to keep one stable Azure Container Apps Job—with its name, identity, permissions, networking, resources, and lifecycle settings—while varying approved settings for each execution instead of modifying ore creating new Container Apps Jobs.

job-orchestrator receives a Service Bus message, updates the live data-processor job, using it as a template, applies execution-scoped overrides, and starts an isolated execution. The main use case is selecting an approved container image and tag per message, equivalent to a manual Portal override but automated and safe for concurrent requests.

This POC currently overrides message and correlation values only. It retains the image from the live template until registry allowlisting and image validation are implemented.

It confirms the pattern and platform are valid.

Technical details overview

This proof of concept uses Azure Container Apps Jobs, Azure Service Bus, and Azure Blob Storage to demonstrate event-driven orchestration and one-shot data processing.

The repository contains both Python 3.13 job implementations. job-orchestrator receives and settles Service Bus messages and starts a message-specific data-processor execution. data-processor captures the execution environment, redacts sensitive values, serializes the snapshot as JSON, and uploads it to a private Blob container using managed identity.

Architecture

Component Purpose
job-orchestrator Container Apps Job Python event-driven worker scaled from the Service Bus data-processing queue. It receives and settles one message, starts a correlated data-processor execution, and waits for its result.
data-processor Container Apps Job One-shot processor started manually or by job-orchestrator to create and upload an execution environment snapshot.
Azure Container Registry Builds and stores uniquely tagged data-processor and job-orchestrator images.
Azure Service Bus Hosts the queue used by the event-driven orchestration POC.
Azure Blob Storage Stores data-processor execution snapshots in data-processor-execution-snapshots.
User-assigned managed identities Authenticate the jobs without credentials in source code or configuration.
Virtual network and private endpoint Provide private connectivity from the Container Apps environment to Blob Storage.
Log Analytics Collects Container Apps Job system and console logs.

The intended processing flow is:

  1. A message is added to the Service Bus queue.
  2. KEDA activates up to ten job-orchestrator executions.
  3. Each orchestrator receives one message in PeekLock mode and validates its producer-supplied MessageId and JSON body.
  4. The orchestrator starts data-processor with an execution-specific template containing MESSAGE_CONTENT and correlation metadata.
  5. data-processor creates a redacted JSON snapshot of its environment.
  6. data-processor uploads an idempotent, MessageId-addressed file to Blob Storage and verifies it with Get Blob Properties.
  7. The orchestrator completes the Service Bus message only after that exact data-processor execution succeeds. Invalid messages are dead-lettered and transient or processor failures are abandoned for redelivery.

Runtime sequence

sequenceDiagram
    autonumber
    actor Producer
    participant SB as Azure Service Bus queue
    participant KEDA as KEDA scaler
    participant JO as job-orchestrator job
    participant ACA as Azure Container Apps API
    participant DP as data-processor execution
    participant Blob as Private Blob container

    Producer->>SB: Enqueue JSON body with unique MessageId
    KEDA->>SB: Observe queue depth
    KEDA->>JO: Start event-driven execution
    JO->>SB: Receive one message in PeekLock mode
    JO->>SB: Register automatic lock renewal
    JO->>JO: Validate MessageId, size, UTF-8, and JSON object

    Note over JO,Blob: Azure API calls use user-assigned managed identities

    alt Message is invalid
        JO->>SB: Dead-letter with validation reason
    else Message is valid
        JO->>ACA: Read the live data-processor job template
        ACA-->>JO: Return template
        JO->>ACA: Start execution with message and correlation values
        ACA->>DP: Start isolated one-shot execution
        DP->>DP: Snapshot environment and redact sensitive values
        DP->>Blob: Upload deterministic JSON blob (overwrite=false)

        alt Blob is new
            Blob-->>DP: Return upload metadata
            DP->>Blob: Get Blob Properties
            Blob-->>DP: Confirm content length and metadata
        else Expected blob already exists
            Blob-->>DP: Report existing blob
            DP->>Blob: Get Blob Properties
            Blob-->>DP: Confirm matching message hashes
        end

        DP-->>ACA: Exit and update execution status

        loop Until the exact execution is terminal or times out
            JO->>ACA: Get execution by name and resource ID
            ACA-->>JO: Return correlation values and status
            JO->>SB: Renew message lock
        end

        alt data-processor succeeded and correlation matches
            JO->>SB: Complete message
        else Processor, authentication, or timeout failure
            JO->>SB: Abandon message for redelivery
        end
    end
Loading

Operational considerations for orchestrator-started executions

Starting data-processor through the Container Apps management API provides message-specific execution templates and precise correlation, but it also makes job-orchestrator responsible for capacity control and execution lifecycle decisions that an event-driven job controller would otherwise coordinate. These responsibilities should be evaluated explicitly before moving beyond a pilot.

Current POC operating envelope

Control Current value Effect
job-orchestrator event scale 0-10 executions, one queued message per execution, polled every 30 seconds Normally limits the orchestrated path to approximately ten in-flight messages.
job-orchestrator replica 0.5 vCPU, 1 GiB, 600-second timeout, no platform replica retry One orchestrator remains allocated while it waits for one data-processor execution.
data-processor execution Manual trigger, one replica, one required completion Each management API start creates one isolated worker execution.
data-processor replica 0.5 vCPU, 1 GiB, 300-second timeout, one platform retry A failed replica can be attempted once more within the execution timeout.
Processor monitoring 300-second deadline, polled every 5 seconds job-orchestrator follows the exact execution name and resource ID.
Service Bus lock 5-minute initial lock, automatically renewed for up to 540 seconds The message remains unsettled while data-processor runs.
Queue delivery Maximum delivery count of 10 Abandoned or lock-expired messages are redelivered and eventually dead-lettered.
Management API retry Five attempts with jittered exponential backoff capped at 8 seconds Only transient HTTP failures such as throttling and service errors are retried.

The scale limit is not a hard global data-processor concurrency limit. data-processor is a manually triggered job, so starts made by another caller, overlapping redeliveries, or executions already accepted by the platform can increase concurrency beyond the ten job-orchestrator executions. A production design that retains this pattern should have a single authorized launcher or a shared admission-control mechanism, such as a durable lease or semaphore, and should reject or defer work when the environment is saturated.

At the configured maximum, ten messages can occupy ten job-orchestrator replicas and ten data-processor replicas at the same time. At 0.5 vCPU and 1 GiB per replica, that represents approximately 10 vCPU and 20 GiB before platform overhead, retries, duplicate executions, image pulls, and unrelated workloads are considered. Validate subscription quotas and Container Apps environment capacity, and load-test Service Bus connections, Container Apps management API rate limits, ACR pulls, Blob throughput, private-endpoint networking, and Log Analytics ingestion. CPU and memory should be based on measured percentiles rather than the POC defaults; out-of-memory termination, CPU throttling, and slow startup all extend the Service Bus lock duration.

Timeouts must form a deliberate hierarchy. The data-processor replica timeout, plus image startup and status propagation margin, should fit inside the job-orchestrator processor deadline. That deadline and settlement margin should fit inside lock renewal, and lock renewal should fit inside the job-orchestrator replica timeout. The current 300-second data-processor timeout and 300-second processor deadline leave no propagation margin, although the outer 540- and 600-second limits do. Tune these values together from observed startup and processing latency.

Retries, idempotency, and failure modes

Retries occur at several independent layers:

  1. job-orchestrator retries selected transient Azure management API responses up to five times and honors Retry-After.
  2. Container Apps can retry one failed data-processor replica.
  3. job-orchestrator abandons the Service Bus message after processor, authentication, or transient Azure failures.
  4. Service Bus redelivers the message until its maximum delivery count sends it to the dead-letter queue.

These layers multiply the possible amount of work for one logical message. Every side effect must therefore be idempotent, not only the final process exit. This POC uses a deterministic, MessageId-derived blob name and verifies message hash metadata before treating an existing blob as success. Any future database write, API call, notification, or downstream publication needs an equivalent idempotency key or transactional outbox.

The start of a Container Apps execution and Service Bus settlement are not one transaction. Important failure windows include:

  • job-orchestrator can lose its response after Azure accepts a start, causing a retry or redelivery to create another execution.
  • job-orchestrator can stop after data-processor starts; the message lock then expires while the original execution continues without its original monitor.
  • data-processor can succeed but message completion or lock renewal can fail, so the successful message is delivered again.
  • Image pull, scheduling, quota, managed identity, RBAC, private DNS, storage, or management API failures can prevent an execution from starting or finishing.
  • A processor deadline can expire before Container Apps publishes the terminal status, particularly when timeout values have no margin.
  • A correlation mismatch, deterministic blob collision, or malformed message is intentionally failed rather than accepted as ambiguous success.
  • Repeated transient failures eventually move a message to the dead-letter queue and require an operational replay or disposal process.

Production monitoring should correlate the message hash, orchestrator execution, data-processor execution, and blob name. Alert on queue age and depth, active execution count, startup latency, management API throttling, processor duration, retries, abandon and lock-loss rates, failed or timed-out executions, dead-letter growth, and Blob collisions. Define retention and cleanup for completed execution records and logs, and rehearse recovery from an orchestrator restart while processors are still running.

Service Bus message state management

job-orchestrator owns the Service Bus message state while data-processor runs. The queue and the Container Apps execution are separate systems, so every state transition must be intentional and observable:

Message state Transition job-orchestrator behavior
Active and available KEDA observes queue demand and an orchestrator receives one message in PeekLock mode. The message becomes locked to that receiver but remains in the queue. Prefetch is disabled and each execution receives at most one message.
Locked and validating job-orchestrator validates MessageId, body type and count, size, UTF-8, JSON syntax, object shape, and broker metadata. Invalid messages are dead-lettered immediately with a bounded validation reason; valid messages continue to processing.
Locked and processing job-orchestrator starts and polls the exact data-processor execution. AutoLockRenewer extends the lock while processing continues. The renewal budget must exceed processor startup and runtime plus settlement margin.
Completed The correlated data-processor execution reports Succeeded, and no lock-renewal failure was observed. job-orchestrator calls complete_message; Service Bus permanently removes the message from the active queue.
Abandoned or lock expired Processor start, authentication, polling, execution, correlation, or timeout fails. The same outcome occurs if the process exits before settlement and the lock later expires. job-orchestrator attempts abandon_message, making the message available for another delivery. A later delivery has an increased delivery count and can start another data-processor execution.
Dead-lettered Input validation fails, or repeated deliveries reach the queue's maximum delivery count. Service Bus moves the message to the dead-letter subqueue. Operators must inspect, replay, or dispose of it; the POC does not automatically replay dead-lettered messages.

Settlement itself can fail. If complete_message, abandon_message, or dead_letter_message fails, job-orchestrator reports a settlement error but cannot assume the broker accepted the requested transition. The message can remain locked until expiry and then reappear. Monitoring must therefore treat the broker-observed state as authoritative rather than relying only on the process exit code.

There is no atomic transaction between starting data-processor and completing the Service Bus message. A crash after the start request can leave a processor running while the message is redelivered, and a completion failure after a successful processor run can produce the same result. The deterministic blob name and hash validation make the current output idempotent, but they do not prevent duplicate executions. Any additional side effects require their own idempotency or transaction strategy.

The POC does not defer messages, use Service Bus sessions, reschedule messages, or maintain a separate workflow-state record. If production requirements need ordered processing, delayed retry classes, cancellation, manual approval, or cross-service reconciliation, add an explicit durable state machine rather than inferring workflow state only from queue delivery count and Container Apps execution status.

Controls available from the job platforms

Azure Container Apps Jobs already provides per-execution controls for replicaTimeout, replicaRetryLimit, parallelism, replicaCompletionCount, container CPU and memory, managed identity, networking, and centralized logs. An event-driven Container Apps Job adds KEDA-based polling and declarative minimum and maximum executions for a queue. Making data-processor itself event-driven could let the platform perform queue-based admission and remove the management-plane start-and-poll loop. Application code would still own payload validation, Service Bus settlement, idempotency, and business-level failure classification.

The current manual data-processor job still benefits from native timeout, replica retry, resource isolation, execution status, and logging, but its externally initiated starts are admitted by job-orchestrator rather than by its own KEDA scale rule. Container Apps does not automatically deduplicate those starts or make them transactional with Service Bus.

A Kubernetes Job exposes additional controller-level primitives when direct cluster control is appropriate:

  • parallelism, completions, and indexed completion mode for bounded or partitioned work;
  • backoffLimit, per-index backoff, pod restart policy, and pod failure policy for retry and terminal-failure classification;
  • activeDeadlineSeconds, suspension, and time-to-live cleanup for completed Jobs;
  • resource requests and limits integrated with scheduling, namespace ResourceQuota and LimitRange, priorities, node selection, affinity, taints, and topology controls;
  • durable Job and Pod status managed by the Kubernetes Job controller, with replacement after Pod or node failure; and
  • KEDA ScaledJob when Kubernetes Jobs must be created from queue demand.

Those controls reduce custom scheduling and retry code, but they do not remove the need for idempotent processing, external-system transaction design, dead-letter handling, observability, or workload-specific admission policy. Container Apps Jobs provides a managed subset of these Kubernetes concepts; Kubernetes Jobs provides more control at the cost of operating cluster capacity, policy, upgrades, and the supporting controllers.

References:

Repository layout

.
|-- azure.yaml                         azd hooks and active deployment entry point
|-- infra/
|   |-- main.bicep                     complete POC infrastructure definition
|   |-- modules/                       reusable infrastructure modules
|   `-- remediation/
|       |-- stage1/                    private storage, DNS, endpoint, and RBAC
|       |-- stage2/                    preserved-job image and environment cutover
|       |-- target.example.json        synthetic public configuration example
|       `-- target.local.json          ignored authorized Azure target
|-- scripts/remediation/               guarded deployment, validation, and rollback
|-- src/data-processor/
|   |-- data_processor/                Python application
|   |-- tests/                         unit tests
|   |-- Dockerfile                     production image
|   `-- requirements.lock              hash-locked runtime dependencies
|-- src/job-orchestrator/
|   |-- job_orchestrator/               Python orchestration application
|   |-- tests/                          orchestration and settlement tests
|   |-- Dockerfile                      production image
|   `-- requirements.lock              hash-locked runtime dependencies
`-- materials/
    `-- TriggerMessagePayload-sample.json

Prerequisites

  • PowerShell 7.
  • Azure CLI and Azure Developer CLI (azd).
  • An authenticated Azure CLI and azd session with access to the target subscription.
  • Permission to build images in the target Azure Container Registry and deploy resources in the target resource group.
  • Python 3.13 for local data-processor tests.
  • Docker only for optional local image builds.

Clone and configure

Clone the repository and authenticate the Azure tools:

git clone https://github.com/embergershared/aca-sbtriggerjoborch-execjobprocessor.git
Set-Location .\aca-sbtriggerjoborch-execjobprocessor
az login
azd auth login

Create the machine-local target configuration:

Copy-Item `
  .\infra\remediation\target.example.json `
  .\infra\remediation\target.local.json

Replace every synthetic value in target.local.json with the identifiers for the existing Azure environment. Set originalHealthyBaselinePath to the retained baseline below .azure\remediation-state. The local target file, AZD state, deployment evidence, environment files, private keys, certificates, Python environments, and mutable Squad state are excluded by the root .gitignore.

The active deployment is intentionally bound to the ignored local target. For another machine-local target, use the ignored infra\remediation\target.<name>.json convention and set REMEDIATION_TARGET_CONFIG to that repository-relative path:

$env:REMEDIATION_TARGET_CONFIG = `
  'infra\remediation\target.development.json'

Do not commit target files, .azure, exported ARM state, deployment baselines, credentials, or generated environment files. The preflight rejects a different subscription, resource group, region, identity, repository, or unauthorized live job state.

Local development

Create isolated Python environments and install the hash-locked development dependencies from the approved package feed:

python -m venv .\.venv\data-processor
& .\.venv\data-processor\Scripts\Activate.ps1
python -m pip install `
  --index-url https://packagefeedproxy.microsoft.io/pypi/simple/ `
  --requirement .\src\data-processor\requirements-dev.txt
Push-Location .\src\data-processor
python -m pytest
Pop-Location
deactivate

python -m venv .\.venv\job-orchestrator
& .\.venv\job-orchestrator\Scripts\Activate.ps1
python -m pip install `
  --index-url https://packagefeedproxy.microsoft.io/pypi/simple/ `
  --requirement .\src\job-orchestrator\requirements-dev.txt
Push-Location .\src\job-orchestrator
python -m pytest
Pop-Location
deactivate

Optionally build both images:

docker build --tag data-processor:local .\src\data-processor
docker build --tag job-orchestrator:local .\src\job-orchestrator

Deploy with azd

The active azure.yaml workflow reconciles an existing pair of Container Apps Jobs; it is not an unguarded deployment to an arbitrary subscription. A preprovision hook validates the AZD environment, Azure subscription, resource identities, and authorized job state before Stage 1 can make changes. Confirm that target.local.json and its original healthy baseline describe the target, then create or select the matching AZD environment:

azd env new <environment-name>
az account set --subscription <azure-subscription-id>

Persist the target's non-secret identifiers into the local AZD environment:

.\scripts\remediation\Set-RemediationEnvironment.ps1
$env:AZURE_RESOURCE_GROUP = azd env get-value AZURE_RESOURCE_GROUP

Run the repository checks and preview the infrastructure changes:

.\scripts\remediation\Test-AzdDeploymentWiring.ps1
.\scripts\remediation\Test-StaticPreparation.ps1
azd provision --preview

Review the preview, then build and deploy:

azd up

The active azd up workflow has two stages:

  1. Stage 1 reconciles private Blob Storage, the Blob private endpoint, private DNS, and container-scoped Storage Blob Data Contributor access.
  2. The predeploy hook builds src\data-processor and src\job-orchestrator in ACR with unique tags such as azd-YYYYMMDDTHHMMSSfffZ. Stage 2 validates both resolved manifests, captures both live job baselines, runs ARM validation and what-if, and updates the existing jobs.

The same Container Apps Job resources are preserved. Their managed identities, triggers, resource limits, and other live properties are retained except for the explicitly managed image and job-orchestrator runtime settings.

Both jobs use tagged image references rather than digest-qualified references:

<registry>.azurecr.io/data-processor:azd-YYYYMMDDTHHMMSSfffZ
<registry>.azurecr.io/job-orchestrator:azd-YYYYMMDDTHHMMSSfffZ

The resolved SHA-256 digest remains in local deployment evidence and is checked before cutover. If cutover or post-cutover verification fails after a write is attempted, the deployment restores the captured baseline.

At the end of a successful deployment, the postdeploy hook displays:

  • The new data-processor image name, tag, and resolved digest.
  • The new job-orchestrator image name, tag, and resolved digest.

Deployment evidence is written under .azure\remediation-state\azd-deploy-* and is excluded from deployment inputs.

data-processor configuration

The Container Apps Job supplies these environment variables:

Variable Required Purpose
AZURE_BLOB_ACCOUNT_URL Yes HTTPS Blob service endpoint without credentials or a SAS query string.
AZURE_BLOB_CONTAINER_NAME Yes Existing destination Blob container.
AZURE_CLIENT_ID No Client ID of the user-assigned managed identity.
DATA_PROCESSOR_IMAGE Deployed by azd up Full tagged image reference used by the execution.
DATA_PROCESSOR_IMAGE_TAG Deployed by azd up Unique ACR tag used by the execution.

The image reference and tag are included in every uploaded snapshot, allowing the JSON file to be correlated with the image that produced it.

job-orchestrator message contract

KEDA activates job-orchestrator from the data-processing queue, but the Python application receives and settles the message through the Azure Service Bus SDK. Each execution receives at most one message with prefetch disabled.

A valid message must:

  • Have a unique, producer-supplied Service Bus MessageId.
  • Contain one data body no larger than 16,384 raw bytes.
  • Decode as strict UTF-8 without NUL characters.
  • Parse as a JSON object.
  • Contain no secrets. The raw body is intentionally visible in the data-processor execution template and uploaded snapshot.

Body fields are opaque data-processor input. They cannot select a job, image, command, identity, CPU, memory, or timeout. The full data-processor execution template is copied from the live job and only these values are upserted for the new execution:

  • MESSAGE_CONTENT
  • MESSAGE_ID
  • MESSAGE_SHA256
  • DELIVERY_COUNT
  • ENQUEUED_TIME_UTC

The override applies only to the returned execution and does not change the persisted data-processor job configuration. This allows concurrent messages without a shared environment-variable update race.

Delivery is at least once because Service Bus settlement and Container Apps execution start are not one transaction. data-processor therefore writes orchestrated snapshots to a deterministic path based on the SHA-256 of MessageId. A matching existing blob is an idempotent success; a hash or content mismatch is never overwritten.

Snapshot and upload behavior

Manual executions without MESSAGE_ID create a blob with a sortable UTC name:

YYYY-MM-DDTHH-MM-SS.ffffffZ.json

Orchestrated executions use a deterministic path based on the SHA-256 hash of the UTF-8 Service Bus MessageId:

messages/<64-hex-message-id-hash>.json

The message-ID hash and message-body hash are stored as Blob metadata. A retry with matching metadata succeeds without overwriting the existing Blob; a collision with different metadata fails.

Sensitive environment values are replaced by [REDACTED:SENSITIVE_ENV_VALUE]. Redaction covers passwords, secrets, tokens, keys, connection strings, SAS values, platform identity headers, and common CI/CD credential variables. Environment values are never written to stdout or stderr.

The application authenticates with ManagedIdentityCredential. It does not accept storage account keys, connection strings, SAS tokens, embedded credentials, or interactive authentication.

An execution succeeds only after:

  1. The JSON content is serialized.
  2. The blob upload completes.
  3. Azure returns upload metadata.
  4. Get Blob Properties confirms the blob exists.
  5. The uploaded content length matches the serialized content length.

The process returns 0 on success and a nonzero exit code for configuration, authentication, upload, or unexpected failures.

End-to-end orchestration test

After azd up, send this exact raw body to the data-processing queue:

{"test":"ok"}

Set a unique Service Bus MessageId property separately, for example test-<unique-id>. Do not wrap or escape the JSON body. The producer identity needs Azure Service Bus Data Sender on the queue.

List and follow job-orchestrator executions:

az containerapp job execution list `
  --name job-orchestrator `
  --resource-group $env:AZURE_RESOURCE_GROUP `
  --output table

az containerapp job logs show `
  --name job-orchestrator `
  --resource-group $env:AZURE_RESOURCE_GROUP `
  --container job-orchestrator `
  --follow true `
  --format text

The logs identify validation, the message-body hash, the exact data-processor execution, status transitions, and settlement without printing the body or raw MessageId.

MESSAGE_CONTENT, MESSAGE_ID, MESSAGE_SHA256, DELIVERY_COUNT, and ENQUEUED_TIME_UTC exist only on the individual data-processor execution template. They intentionally do not appear in the persisted job configuration, which prevents concurrent messages from overwriting shared state. Inspect their names on a specific execution with:

az containerapp job execution show `
  --name data-processor `
  --resource-group $env:AZURE_RESOURCE_GROUP `
  --job-execution-name <execution-name> `
  --query "properties.template.containers[0].env[].name" `
  --output tsv

Send the same body with the same MessageId again to test idempotency. A retry may start another data-processor execution, but it must reuse the same deterministic Blob and report an idempotent success rather than creating a second file.

Run data-processor manually and inspect logs

Start a data-processor execution:

az containerapp job start `
  --name data-processor `
  --resource-group $env:AZURE_RESOURCE_GROUP

List recent executions:

az containerapp job execution list `
  --name data-processor `
  --resource-group $env:AZURE_RESOURCE_GROUP `
  --output table

data-processor writes progress to stdout, including:

  • Execution start and completion.
  • Blob name, serialized size, and environment variable count.
  • Credential-free destination URL.
  • Upload start and byte percentage.
  • Upload ETag, last-modified time, request ID, and version ID.
  • Final remote content-length verification.

Example completion messages:

Blob upload completed: destination=https://<account>.blob.core.windows.net/data-processor-execution-snapshots/<blob>.json size_bytes=<bytes> etag=<etag> last_modified=<time> request_id=<id> version_id=<version>.
Blob availability verified: destination=https://<account>.blob.core.windows.net/data-processor-execution-snapshots/<blob>.json content_length=<bytes> expected_length=<bytes>.
data-processor job execution completed: uploaded_blob=<blob>.json.

Private storage access

Blob Storage is configured with public network access disabled and defaultAction: Deny. The data-processor identity receives Storage Blob Data Contributor at the container scope.

Successful PutBlob and GetBlobProperties operations prove that the job uploaded and verified the blob. The Azure Portal may still show a network or authorization error when the browser is outside the approved network path. When using an IP allowlist or corporate proxy, verify the public egress IP actually presented to Azure.

Both Dockerfiles install hash-locked dependencies from the approved Microsoft package feed proxy and run as non-root UID/GID 10001.

Run the deployment wiring checks independently:

.\scripts\remediation\Test-AzdDeploymentWiring.ps1
.\scripts\remediation\Test-StaticPreparation.ps1

More application-specific information is available in src\data-processor\README.md and src\job-orchestrator\README.md.

Security characteristics

  • Managed identity is used for Blob Storage, Service Bus, Container Apps management, and ACR access.
  • Blob access is private and protected by private DNS and a private endpoint.
  • Blob RBAC is scoped to the destination container.
  • Container Registry anonymous pull and admin credentials are not used.
  • The data-processor container runs as a non-root user.
  • Runtime dependencies are version-pinned and hash-locked.
  • Deployment guards reject target drift and unauthorized job mutations.
  • job-orchestrator has queue-scoped Service Bus Data Receiver and data-processor-scoped Container Apps Jobs Operator; it does not have resource-group Contributor.
  • Message content is never persisted in the default job configuration or deployment evidence.
  • Stage 2 preserves, verifies, and can roll back both existing jobs.
  • Deployment and rollback both run ARM validation and what-if before mutation.

References

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages