Azure function — From containers. A game changer — 2?
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:
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.txtContents 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:v1We 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 tableHere’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.datYou 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.
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!
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.764187Externalize 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:v2Step 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-001Now 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-001This 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-001Option 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.
That’s it for now. Enjoy working on Azure functions!!






