From 64cf5eb0107fa8c51872b57753231d59f8d2a91b Mon Sep 17 00:00:00 2001 From: Pratap Ladhani Date: Mon, 9 Feb 2026 19:33:51 -0800 Subject: [PATCH 1/2] fix: enable container deployment for Python Agent Framework sample Problem: The Python Agent Framework sample bound to 'localhost' which prevented the agent from receiving external traffic when running in containers. Health checks failed and Bot Framework messages couldn't reach the agent. Solution: - Changed network binding from 'localhost' to '0.0.0.0' in host_agent_server.py - Added Dockerfile for containerized deployments - Added .dockerignore to exclude dev files from container builds - Added comprehensive Container Deployment section to README explaining: - Why container deployment is recommended for production - Azure integration benefits (Container Apps, AKS, ACR) - Network binding requirements for containerized agents - Build and deployment instructions for Docker and Azure Container Apps This fix was discovered while deploying to Azure Container Apps for a shared demo tenant. The agent worked locally but failed all health checks when containerized due to the localhost binding limitation. --- .../sample-agent/.dockerignore | 18 ++++++ .../agent-framework/sample-agent/Dockerfile | 39 ++++++++++++ python/agent-framework/sample-agent/README.md | 61 +++++++++++++++++++ .../sample-agent/host_agent_server.py | 4 +- 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 python/agent-framework/sample-agent/.dockerignore create mode 100644 python/agent-framework/sample-agent/Dockerfile diff --git a/python/agent-framework/sample-agent/.dockerignore b/python/agent-framework/sample-agent/.dockerignore new file mode 100644 index 00000000..a2cbcd0c --- /dev/null +++ b/python/agent-framework/sample-agent/.dockerignore @@ -0,0 +1,18 @@ +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ + +# IDE +.vscode/ +.idea/ + +# Local files +.env +.env.local +*.log + +# Git +.git/ +.gitignore diff --git a/python/agent-framework/sample-agent/Dockerfile b/python/agent-framework/sample-agent/Dockerfile new file mode 100644 index 00000000..38b77a98 --- /dev/null +++ b/python/agent-framework/sample-agent/Dockerfile @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Python Agent Framework - Container Deployment +FROM python:3.12-slim + +WORKDIR /app + +# Suppress apt-get warnings in Docker +ENV DEBIAN_FRONTEND=noninteractive + +# Install system dependencies (gcc needed for some Python packages) +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# Upgrade pip +RUN pip install --no-cache-dir --upgrade pip + +# Copy application code +COPY . . + +# Install Python dependencies from pyproject.toml +RUN pip install --no-cache-dir --root-user-action=ignore . + +# Expose port +EXPOSE 3978 + +# Set environment variables +ENV PORT=3978 +ENV PYTHONUNBUFFERED=1 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:3978/api/health')" || exit 1 + +# Run the agent +CMD ["python", "start_with_generic_host.py"] diff --git a/python/agent-framework/sample-agent/README.md b/python/agent-framework/sample-agent/README.md index e315def9..87ece078 100644 --- a/python/agent-framework/sample-agent/README.md +++ b/python/agent-framework/sample-agent/README.md @@ -24,6 +24,67 @@ To set up and test this agent, refer to the [Configure Agent Testing](https://le For a detailed explanation of the agent code and implementation, see the [Agent Code Walkthrough](AGENT-CODE-WALKTHROUGH.md). +## Container Deployment + +Container deployment is the recommended approach for production Agent 365 workloads. Here's why: + +### Why Container Deployment? + +**Production Readiness** +- **Consistency**: Containers ensure identical behavior across development, staging, and production environments, eliminating "works on my machine" issues +- **Isolation**: Each agent runs in its own isolated environment with explicit dependencies, preventing conflicts with other services +- **Scalability**: Container orchestrators (Kubernetes, Azure Container Apps) can automatically scale agent instances based on demand + +**Azure Integration** +- **Azure Container Apps**: Purpose-built for microservices and agents with built-in autoscaling, managed certificates, and seamless Azure service integration +- **Azure Kubernetes Service (AKS)**: Enterprise-grade orchestration for complex multi-agent deployments +- **Azure Container Registry**: Private registry for secure image storage with geo-replication + +**Operational Benefits** +- **Health Checks**: Container runtimes monitor `/api/health` to automatically restart unhealthy agents +- **Rolling Updates**: Deploy new versions with zero downtime using blue-green or canary strategies +- **Resource Limits**: Define CPU/memory boundaries to prevent runaway processes + +### Network Binding Fix + +This sample binds to `0.0.0.0` (all network interfaces) instead of `localhost`. This is **required** for container deployments because: + +- `localhost` (127.0.0.1) only accepts connections from inside the container +- External traffic (Bot Framework messages, health checks) comes from outside the container network +- Binding to `0.0.0.0` allows the agent to receive requests on any network interface + +**Symptoms if using localhost in containers:** +- Container starts but health checks fail with "Connection refused" +- Agent works locally but fails when deployed to Docker/Kubernetes/Container Apps +- Bot Framework cannot reach the `/api/messages` endpoint + +### Build and run with Docker + +```bash +docker build -t python-agent . +docker run -p 3978:3978 \ + -e AZURE_OPENAI_ENDPOINT=https://your-endpoint.openai.azure.com/ \ + -e AZURE_OPENAI_API_KEY=your-key \ + -e AZURE_OPENAI_DEPLOYMENT=gpt-4o \ + python-agent +``` + +### Azure Container Apps + +```bash +# Build and push to Azure Container Registry +az acr build --registry --image python-agent:latest . + +# Create Container App +az containerapp create \ + --name python-agent \ + --resource-group \ + --environment \ + --image .azurecr.io/python-agent:latest \ + --target-port 3978 \ + --ingress external +``` + ## Support For issues, questions, or feedback: diff --git a/python/agent-framework/sample-agent/host_agent_server.py b/python/agent-framework/sample-agent/host_agent_server.py index 00a80375..653d903d 100644 --- a/python/agent-framework/sample-agent/host_agent_server.py +++ b/python/agent-framework/sample-agent/host_agent_server.py @@ -332,12 +332,12 @@ async def anonymous_claims(request, handler): print(f"šŸ¢ {self.agent_class.__name__}") print("=" * 80) print(f"šŸ”’ Auth: {'Enabled' if auth_configuration else 'Anonymous'}") - print(f"šŸš€ Server: localhost:{port}") + print(f"šŸš€ Server: 0.0.0.0:{port}") print(f"šŸ“š Endpoint: http://localhost:{port}/api/messages") print(f"ā¤ļø Health: http://localhost:{port}/api/health\n") try: - run_app(app, host="localhost", port=port, handle_signals=True) + run_app(app, host="0.0.0.0", port=port, handle_signals=True) except KeyboardInterrupt: print("\nšŸ‘‹ Server stopped") From cf92e3e4c781b8f36d440bcfde48b1187bf12075 Mon Sep 17 00:00:00 2001 From: Pratap Ladhani Date: Mon, 9 Feb 2026 20:42:57 -0800 Subject: [PATCH 2/2] fix: address Copilot review comments for container deployment - Dockerfile: Add non-root user for security, fix layer caching order, use curl with PORT env var for healthcheck - host_agent_server.py: Update copyright header to standard format, fix print statements to show 0.0.0.0 binding - .dockerignore: Add Python build artifacts (.pytest_cache, .mypy_cache, .ruff_cache, build/, dist/, *.egg-info) - README.md: Add cd instructions, Azure CLI login steps, and env vars for Container Apps deployment with secret reference example Related to #187 --- .../sample-agent/.dockerignore | 11 ++++++++ .../agent-framework/sample-agent/Dockerfile | 18 ++++++++++--- python/agent-framework/sample-agent/README.md | 25 +++++++++++++++++-- .../sample-agent/host_agent_server.py | 8 +++--- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/python/agent-framework/sample-agent/.dockerignore b/python/agent-framework/sample-agent/.dockerignore index a2cbcd0c..645c5929 100644 --- a/python/agent-framework/sample-agent/.dockerignore +++ b/python/agent-framework/sample-agent/.dockerignore @@ -1,8 +1,15 @@ # Python __pycache__/ *.py[cod] +*.pyo .venv/ venv/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +build/ +dist/ +*.egg-info/ # IDE .vscode/ @@ -16,3 +23,7 @@ venv/ # Git .git/ .gitignore + +# Documentation +*.md +!README.md diff --git a/python/agent-framework/sample-agent/Dockerfile b/python/agent-framework/sample-agent/Dockerfile index 38b77a98..c279ef4b 100644 --- a/python/agent-framework/sample-agent/Dockerfile +++ b/python/agent-framework/sample-agent/Dockerfile @@ -2,6 +2,7 @@ # Licensed under the MIT License. # Python Agent Framework - Container Deployment +# Related to: https://github.com/microsoft/Agent365-Samples/pull/187 FROM python:3.12-slim WORKDIR /app @@ -12,17 +13,26 @@ ENV DEBIAN_FRONTEND=noninteractive # Install system dependencies (gcc needed for some Python packages) RUN apt-get update && apt-get install -y --no-install-recommends \ gcc \ + curl \ && rm -rf /var/lib/apt/lists/* \ && apt-get clean # Upgrade pip RUN pip install --no-cache-dir --upgrade pip +# Copy dependency metadata first for better layer caching +COPY pyproject.toml . + +# Install Python dependencies from pyproject.toml +RUN pip install --no-cache-dir . + # Copy application code COPY . . -# Install Python dependencies from pyproject.toml -RUN pip install --no-cache-dir --root-user-action=ignore . +# Create non-root user for security +RUN useradd --create-home --shell /bin/bash agentuser \ + && chown -R agentuser:agentuser /app +USER agentuser # Expose port EXPOSE 3978 @@ -31,9 +41,9 @@ EXPOSE 3978 ENV PORT=3978 ENV PYTHONUNBUFFERED=1 -# Health check +# Health check using PORT env var HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ - CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:3978/api/health')" || exit 1 + CMD curl --fail --silent http://127.0.0.1:${PORT}/api/health || exit 1 # Run the agent CMD ["python", "start_with_generic_host.py"] diff --git a/python/agent-framework/sample-agent/README.md b/python/agent-framework/sample-agent/README.md index 87ece078..7a02fa64 100644 --- a/python/agent-framework/sample-agent/README.md +++ b/python/agent-framework/sample-agent/README.md @@ -61,7 +61,13 @@ This sample binds to `0.0.0.0` (all network interfaces) instead of `localhost`. ### Build and run with Docker ```bash +# Navigate to the sample directory first +cd python/agent-framework/sample-agent + +# Build the container image docker build -t python-agent . + +# Run with required environment variables docker run -p 3978:3978 \ -e AZURE_OPENAI_ENDPOINT=https://your-endpoint.openai.azure.com/ \ -e AZURE_OPENAI_API_KEY=your-key \ @@ -72,17 +78,32 @@ docker run -p 3978:3978 \ ### Azure Container Apps ```bash +# Navigate to the sample directory +cd python/agent-framework/sample-agent + +# Ensure you're logged in to Azure CLI +az login +az account set --subscription + # Build and push to Azure Container Registry az acr build --registry --image python-agent:latest . -# Create Container App +# Create Container App with required environment variables az containerapp create \ --name python-agent \ --resource-group \ --environment \ --image .azurecr.io/python-agent:latest \ --target-port 3978 \ - --ingress external + --ingress external \ + --env-vars \ + AZURE_OPENAI_ENDPOINT=https://your-endpoint.openai.azure.com/ \ + AZURE_OPENAI_API_KEY=secretref:openai-key \ + AZURE_OPENAI_DEPLOYMENT=gpt-4o + +# Note: For production, use Azure Container Apps secrets for API keys: +# az containerapp secret set --name python-agent --resource-group \ +# --secrets openai-key= ``` ## Support diff --git a/python/agent-framework/sample-agent/host_agent_server.py b/python/agent-framework/sample-agent/host_agent_server.py index 653d903d..219f382f 100644 --- a/python/agent-framework/sample-agent/host_agent_server.py +++ b/python/agent-framework/sample-agent/host_agent_server.py @@ -1,4 +1,5 @@ -# Copyright (c) Microsoft. All rights reserved. +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. """Generic Agent Host Server - Hosts agents implementing AgentInterface""" @@ -333,8 +334,9 @@ async def anonymous_claims(request, handler): print("=" * 80) print(f"šŸ”’ Auth: {'Enabled' if auth_configuration else 'Anonymous'}") print(f"šŸš€ Server: 0.0.0.0:{port}") - print(f"šŸ“š Endpoint: http://localhost:{port}/api/messages") - print(f"ā¤ļø Health: http://localhost:{port}/api/health\n") + print(f"šŸ“š Endpoint: http://0.0.0.0:{port}/api/messages") + print(f"ā¤ļø Health: http://0.0.0.0:{port}/api/health") + print(" (Use localhost:PORT for local testing)\n") try: run_app(app, host="0.0.0.0", port=port, handle_signals=True)