MediumAzure function — From containers. A game changer — 2? | by Krishnan Sriram | Medium
Sitemap
Krishnan Sriram

Krishnan Sriram

Tech enthusiast, life long learner and an enterprise architect

Azure function — From containers. A game changer — 2?

6 min readFeb 20, 2026

--

Press enter or click to view image in full size
Photo by Agê Barros on Unsplash

This article is a continuation of our previous discussion where we explored enabling an HTTP trigger Azure Function through a custom Docker container. Building on that foundation, we now shift our focus to implementing a Timer trigger — a common requirement when you need to execute functions on a scheduled basis rather than in response to an incoming HTTP request.

What you need to know about container set in Azure function

Each Azure Function App can only have one container image set at a time. So the way to think about it is:

One Function App = One Container = One or Many Triggers

So your design choices are:

Option 1: One container per Function App (what we built in our previous article)

my-python-acr-funcapp  →  my-python-func:v1  →  HTTP trigger only
my-timer-funcapp → my-timer-func:v1 → Timer trigger only

✅ Single responsibility, independent deployments, independent scaling

Option 2: Multiple triggers in one container (will see later in this article)

my-python-acr-funcapp  →  my-combined-func:v1  →  HTTP trigger 1
HTTP trigger 2
Timer trigger 1
Timer trigger 2

✅ Simpler infrastructure, fewer Function Apps to manage

The general rule of thumb for choosing between them is:

Press enter or click to view image in full size

For production, separate containers per responsibility is the preferred approach as it gives you independent deployments, scaling, and failure isolation — which is exactly the pattern we’ve been following today!

Let’s start by creating a directory for our solution

$:> mkdir TimerHelloWorldFn && cd TimerHelloWorldFn
$:> touch Dockerfile function_app.py host.json requirements.txt .dockerignore
$:> python3 -m venv venv
$:> source venv/bin/activate
$:> pip install -r requirements.txt

Contents of Dockerfile , requirements.txt , .dockerignore and host.json is the same as in previous article. We’ll have change to function_app.py

import azure.functions as func
import logging
from datetime import datetime

app = func.FunctionApp()

@app.timer_trigger(schedule="0 */5 * * * *", arg_name="myTimer", run_on_startup=True)
def timer_function(myTimer: func.TimerRequest) -> None:
logging.info(f"Timer trigger executed at: {datetime.now()}")
if myTimer.past_due:
logging.warning("Timer is running late!")

Now that we have the function our next step is build it and deliver it to ACR. Steps are exactly the same as that of our previous implementation

$:> docker build --no-cache --platform linux/amd64 -t my-timer-func .
$:> docker tag my-timer-func acreus2acrazfuncdev01.azurecr.io/my-timer-func:v1
$:> az acr login acreus2acrazfuncdev01
$:> docker push acreus2acrazfuncdev01.azurecr.io/my-timer-func:v1

We created Azure function in our previous article, steps are the same. We will take it from container set step as that’s the only change we ought to do

az functionapp config container set \
--name my-python-acr-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001 \
--image acreus2acrazfuncdev01.azurecr.io/my-timer-func:v1 \
--registry-server https://acreus2acrazfuncdev01.azurecr.io \
--registry-username acreus2acrazfuncdev01 \
--registry-password <<password>>

You should see something like this in response

Runtime docker not supported for os linux. Supported runtimes for os linux are: ['dotnet-isolated', 'dotnet-isolated', 'dotnet-isolated', 'dotnet-isolated', 'dotnet-isolated', 'dotnet', 'dotnet', 'node', 'node', 'node', 'node', 'python', 'python', 'python', 'python', 'python', 'python', 'python', 'python', 'java', 'java', 'java', 'java', 'java', 'powershell', 'powershell', 'custom']. Run 'az functionapp list-runtimes' for more details on supported runtimes. 
[
{
"name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
"slotSetting": false,
"value": "false"
},
{
"name": "DOCKER_REGISTRY_SERVER_URL",
"slotSetting": false,
"value": "https://acreus2acrazfuncdev01.azurecr.io"
},
{
"name": "DOCKER_REGISTRY_SERVER_USERNAME",
"slotSetting": false,
"value": "acreus2acrazfuncdev01"
},
{
"name": "DOCKER_REGISTRY_SERVER_PASSWORD",
"slotSetting": false,
"value": null
},
{
"name": "DOCKER_CUSTOM_IMAGE_NAME",
"value": "DOCKER|acreus2acrazfuncdev01.azurecr.io/my-timer-func:v1"
}
]

We can check to see if the function is available to test/use by executing the following command

az functionapp function list \
--name my-python-acr-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001 \
--output table

Here’s what I see

Href                                                                            IsDisabled    Language    Location    Name                                  ResourceGroup              ScriptHref                                                                                   TestData    TestDataHref
------------------------------------------------------------------------------ ------------ ---------- ---------- ------------------------------------ ------------------------- ------------------------------------------------------------------------------------------- ---------- -----------------------------------------------------------------------------------------------------------
https://my-python-acr-funcapp.azurewebsites.net/admin/functions/timer_function False python East US 2 my-python-acr-funcapp/timer_function rg-eus2-acrazfunc-dev-001 https://my-python-acr-funcapp.azurewebsites.net/admin/vfs/home/site/wwwroot/function_app.py https://my-python-acr-funcapp.azurewebsites.net/admin/vfs/home/data/Functions/sampledata/timer_function.dat

You may be wondering, what happened to http_trigger function. Well as explained earlier, it was replaced. We’ll come back to how we can handle it later.

Press enter or click to view image in full size

You see timer_function listed. Since run_on_startup=True is set, check the log stream immediately after restart to confirm it fired. You now have two completely independent containerized Function Apps — one for HTTP and one for the timer!

Press enter or click to view image in full size

You should see timer_function invoked in logs

Id=f845b22d-0ea6-47d0-acd1-21f32d357bc9)
2026-02-19T23:55:17.5425022Z info: Function.timer_function[0]
2026-02-19T23:55:17.5425653Z Trigger Details: UnscheduledInvocationReason: RunOnStartup, ScheduleStatus: {"Last":"0001-01-01T00:00:00+00:00","Next":"2026-02-20T00:00:00+00:00","LastUpdated":"0001-01-01T00:00:00+00:00"}
2026-02-19T23:55:17.7935588Z Hosting environment: Production
2026-02-19T23:55:17.7943496Z Content root path: /azure-functions-host
2026-02-19T23:55:17.8028057Z info: Function.timer_function.User[0]
2026-02-19T23:55:17.8028387Z Timer trigger executed at: 2026-02-19 23:55:17.764187

Externalize timer

You can move the schedule out into an Application Setting so it can be changed without rebuilding the container. Azure Functions supports this natively using the %SETTING_NAME% syntax.

Get Krishnan Sriram’s stories in your inbox

Join Medium for free to get updates from this writer.

Step 1: Update function_app.py

import azure.functions as func
import logging
from datetime import datetime
app = func.FunctionApp()

@app.timer_trigger(schedule="%TIMER_SCHEDULE%", arg_name="myTimer", run_on_startup=True)
def timer_function(myTimer: func.TimerRequest) -> None:
logging.info(f"Timer trigger executed at: {datetime.now()}")
if myTimer.past_due:
logging.warning("Timer is running late!")

The %TIMER_SCHEDULE% tells Azure Functions to look up the value from application settings at runtime.

Step 2: Set the app setting on your Function App

az functionapp config appsettings set \
--name my-timer-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001 \
--settings TIMER_SCHEDULE="0 */5 * * * *"

Step 3: Rebuild and push

docker build --no-cache --platform linux/amd64 -t my-timer-func .
docker tag my-timer-func acreus2acrazfuncdev01.azurecr.io/my-timer-func:v2
docker push acreus2acrazfuncdev01.azurecr.io/my-timer-func:v2

Step 4: Update and restart

az functionapp config container set \
--name my-python-acr-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001 \
--image acreus2acrazfuncdev01.azurecr.io/my-timer-func:v2 \
--registry-server https://acreus2acrazfuncdev01.azurecr.io \
--registry-identity <umi-resource-id>

az functionapp restart \
--name my-timer-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001

Now to change the schedule in future, just update the app setting — no rebuild needed:

# Change to run every hour
az functionapp config appsettings set \
--name my-python-acr-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001 \
--settings TIMER_SCHEDULE="0 0 * * * *"

# Restart to pick up the new schedule
az functionapp restart \
--name my-timer-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001

This is the recommended approach for production — your container stays immutable and all environment-specific configuration lives outside of it!

You deployed the timer trigger code to my-python-acr-funcapp which already had the HTTP trigger — the new container replaced the old one so only the timer function exists now.

You have two options:

Option 1: Revert my-python-acr-funcapp back to the HTTP trigger container

az functionapp config container set \
--name my-python-acr-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001 \
--image acreus2acrazfuncdev01.azurecr.io/my-python-func:v4 \
--registry-server https://acreus2acrazfuncdev01.azurecr.io \
--registry-identity <umi-resource-id>

az functionapp restart \
--name my-python-acr-funcapp \
--resource-group rg-eus2-acrazfunc-dev-001

Option 2: Keep both triggers in my-python-acr-funcapp

Update function_app.py in your HTTP trigger project to include both:

import azure.functions as func
import logging
from datetime import datetime

app = func.FunctionApp()

@app.route(route="hello", auth_level=func.AuthLevel.ANONYMOUS)
def hello(req: func.HttpRequest) -> func.HttpResponse:
logging.info("Python HTTP trigger function processed a request.")
return func.HttpResponse("Hello from Azure Functions + ACR!", status_code=200)

@app.timer_trigger(schedule="%TIMER_SCHEDULE%", arg_name="myTimer", run_on_startup=True)
def timer_function(myTimer: func.TimerRequest) -> None:
logging.info(f"Timer trigger executed at: {datetime.now()}")
if myTimer.past_due:
logging.warning("Timer is running late!")

Based on what we’ve been building I’d recommend Option 1 — keep them as separate containers since that’s the whole point of the exercise and follows the single responsibility principle.

Press enter or click to view image in full size
Photo by bady abbas on Unsplash

That’s it for now. Enjoy working on Azure functions!!

--

--

Krishnan Sriram
Krishnan Sriram

Tech enthusiast, life long learner and an enterprise architect