Master Blueprint: Event-Driven Automation with Logic Apps & Container App Jobs

A step-by-step guide for building an event-driven security scanner. We'll build and test each component before moving to the next — that way, if something breaks, you know exactly which piece is responsible.
The Architecture
[Logic App] → [Service Bus Queue] → [Container App Job]
(trigger) (message queue) (worker)
How it works:
- •Logic App runs on a schedule and sends a message to Service Bus
- •KEDA (built into Container Apps) polls the queue and sees a message
- •KEDA spins up a Container App Job instance to process it
- •Job completes, container shuts down — you only pay when work is actually happening
The beauty of this pattern is that it's zero-cost when idle. No messages, no containers, no bill.
Step 1: Create Service Bus & Logic App
First, set up the message queue and the trigger.
1.1 Create the Service Bus
- •In Azure Portal, search Service Bus → Create a Namespace
- •Go into your namespace → Queues → Create a queue named
scan-queue - •Go to Shared access policies → RootManageSharedAccessKey → Copy the Primary Connection String (save this for later)
1.2 Create the Logic App & Workflow
- •Search Logic Apps → Create a Logic App (Consumption)
- •Once created, go to Logic App Designer
- •Add trigger: Recurrence (e.g., every day at 2 AM)
- •Add action: Service Bus - Send message
- •First time: it asks for a connection — paste your connection string from step 1.1
- •Queue name:
scan-queue - •Content:
{"target": "https://example.com", "scanType": "full"}
- •Save the workflow
1.3 Test It
- •Click Run Trigger → Run
- •Go to your Service Bus namespace → Queues →
scan-queue→ Service Bus Explorer - •Click Peek from start — you should see your message
Message in the queue? Step 1 is done.
Step 2: Build the Worker & Push to ACR
Now create the container that processes messages.
2.1 Create Azure Container Registry
- •In Azure Portal, search Container registries → Create one
- •Once created, go to Access keys → Enable Admin user
- •Note down the Login server, Username, and Password
2.2 The Worker Code (Node.js)
Create a file index.js:
const { ServiceBusClient } = require("@azure/service-bus");
async function main() {
const sbClient = new ServiceBusClient(process.env.SB_CONNECTION);
const receiver = sbClient.createReceiver("scan-queue", {
receiveMode: "peekLock"
});
const messages = await receiver.receiveMessages(1, { maxWaitTimeInMs: 5000 });
if (messages.length > 0) {
const message = messages[0];
console.log("Processing:", message.body);
const lockRenewalInterval = setInterval(async () => {
try {
await receiver.renewMessageLock(message);
console.log("Lock renewed");
} catch (err) {
console.error("Lock renewal failed:", err);
}
}, 60000);
try {
await runSecurityScan(message.body.target);
clearInterval(lockRenewalInterval);
await receiver.completeMessage(message);
console.log("Done - message completed");
} catch (err) {
clearInterval(lockRenewalInterval);
await receiver.abandonMessage(message);
console.error("Failed - message abandoned:", err);
}
} else {
console.log("No messages in queue");
}
await sbClient.close();
}
main();
2.3 Create package.json
{
"name": "security-scanner",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"@azure/service-bus": "^7.9.0"
}
}
2.4 Create Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "index.js"]
2.5 Build & Push to ACR
# Login to your registry
az acr login --name [acr-name]
# Build and push
docker build -t [acr-name].azurecr.io/security-scanner:v1 .
docker push [acr-name].azurecr.io/security-scanner:v1
Step 3: Create the Container App Job
Now create the job that KEDA will trigger when messages arrive.
3.1 Create Container Apps Environment
- •Search Container Apps Environments → Create one
- •Pick your resource group and region → Create
3.2 Create the Job
- •Search Container App Jobs → Create
- •Select your environment from 3.1
- •Job type: Event-driven
- •Image source: Azure Container Registry
- •Registry: select yours
- •Image:
security-scanner - •Tag:
v1 - •Authentication: Admin credentials
3.3 Configure Event Trigger & Secrets (during creation)
In the Event trigger section:
- •Replica timeout: 1800 (30 mins — adjust based on how long your scans actually take)
- •Polling interval: 60 seconds
- •Min executions: 0
- •Max executions: 10
Scale rule:
- •Name:
service-bus-trigger - •Type: Azure Service Bus Queue
- •Queue name:
scan-queue - •Message count: 1
- •Authentication: Connection string
- •Connection string value: paste your Service Bus connection string
3.4 Add Environment Variable
In the Container section → Environment variables:
- •Name:
SB_CONNECTION - •Source: Manual entry
- •Value: paste your Service Bus connection string
Create the job.
Step 4: Test the Pipeline
4.1 First Test
- •Run your Logic App again (to put a fresh message in the queue)
- •Go to your Container App Job → Execution history
- •Within 60 seconds (polling interval), you should see a new execution appear
- •Click on it → Console logs to see your worker output
4.2 What to Check in Logs
- •"Processing: {...}" — job received the message
- •"Lock renewed" — lock renewal is working (important for long-running jobs)
- •"Done - message completed" — success, message removed from queue
If you see all three, your event-driven pipeline is working end-to-end.
Summary
What we built:
- •Service Bus - holds messages in a queue
- •Logic App - sends scan requests on a schedule
- •Container App Job - KEDA polls the queue, spins up a container when messages arrive, shuts down when done
You only pay for compute while scans are running. When the queue is empty, you pay nothing.
For production: Switch to Managed Identity instead of connection strings. No credentials to rotate, no secrets to accidentally expose in logs.
Aziz Jarrar
Full Stack Engineer