Back to Blog
AzureLogic AppsContainer AppsEvent-DrivenSecurity

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

8 min read
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:

  1. Logic App runs on a schedule and sends a message to Service Bus
  2. KEDA (built into Container Apps) polls the queue and sees a message
  3. KEDA spins up a Container App Job instance to process it
  4. 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

  1. In Azure Portal, search Service Bus → Create a Namespace
  2. Go into your namespace → Queues → Create a queue named scan-queue
  3. Go to Shared access policiesRootManageSharedAccessKey → Copy the Primary Connection String (save this for later)

1.2 Create the Logic App & Workflow

  1. Search Logic Apps → Create a Logic App (Consumption)
  2. Once created, go to Logic App Designer
  3. Add trigger: Recurrence (e.g., every day at 2 AM)
  4. 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"}
  5. Save the workflow

1.3 Test It

  1. Click Run TriggerRun
  2. Go to your Service Bus namespace → Queuesscan-queueService Bus Explorer
  3. 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

  1. In Azure Portal, search Container registries → Create one
  2. Once created, go to Access keys → Enable Admin user
  3. 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

  1. Search Container Apps Environments → Create one
  2. Pick your resource group and region → Create

3.2 Create the Job

  1. Search Container App Jobs → Create
  2. Select your environment from 3.1
  3. Job type: Event-driven
  4. 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

  1. Run your Logic App again (to put a fresh message in the queue)
  2. Go to your Container App Job → Execution history
  3. Within 60 seconds (polling interval), you should see a new execution appear
  4. 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:

  1. Service Bus - holds messages in a queue
  2. Logic App - sends scan requests on a schedule
  3. 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.

AJ

Aziz Jarrar

Full Stack Engineer

Share this article