Azure Functions & Container Apps for Go
Microsoft Azure hosts Go through two main paths: Azure Functions with a custom handler process, and Azure Container Apps (ACA) for OCI images with scale rules.
Search across all documentation pages
Microsoft Azure hosts Go through two main paths: Azure Functions with a custom handler process, and Azure Container Apps (ACA) for OCI images with scale rules.
Both accept the same static Go binary patterns you use on AWS and GCP - the differences are hosting contracts and identity wiring.
Functions custom handlers let Go own an HTTP server on a platform-assigned port while the Functions host routes trigger events to it.
Container Apps run your image like Cloud Run with optional scale-to-zero and KEDA triggers (HTTP, queues, cron).
Use managed identity and Key Vault references instead of connection strings in app settings.
Quick-reference recipe card - copy-paste ready.
// cmd/functions/main.go - custom handler skeleton
http.HandleFunc("/api/HttpExample", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"message":"hello from go"}`))
})
port := os.Getenv("FUNCTIONS_CUSTOMHANDLER_PORT")
log.Fatal(http.ListenAndServe(":"+port, nil))# Container Apps deploy sketch
az containerapp up --name go-api --source . --ingress external --target-port 8080When to reach for this:
package main
import (
"encoding/json"
"log/slog"
"net/http"
"os"
)
type response struct {
Message string `json:"message"`
}
func main() {
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
mux := http.NewServeMux()
mux.HandleFunc("POST /api/process", func(w http.ResponseWriter, r *http.Request) {
var payload map[string]any
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
slog.Info("event", "keys", len(payload))
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(response{Message: "processed"})
})
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
port := os.Getenv("FUNCTIONS_CUSTOMHANDLER_PORT")
if port == "" {
port = os.Getenv("PORT")
}
if port == "" {
port = "8080"
}
slog.Info("listening", "port", port)
if err := http.ListenAndServe(":"+port, mux); err != nil {
slog.Error("listen failed", "err", err)
os.Exit(1)
}
}What this demonstrates:
FUNCTIONS_CUSTOMHANDLER_PORT for Functions, PORT for Container Apps.| Dimension | Azure Functions (custom handler) | Azure Container Apps |
|---|---|---|
| Deploy unit | Binary + function.json host config | Container image |
| Triggers | Built-in bindings catalog | KEDA scalers (HTTP, SB, cron) |
| Scale | Consumption or Premium plans | Min/max replicas, scale to zero |
| Go style | net/http on custom port | Same as Cloud Run patterns |
| Identity | System-assigned managed identity | Managed identity on app |
{
"bindings": [{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post"],
"route": "process"
}, {
"type": "http",
"direction": "out",
"name": "res"
}]
}Pair each trigger with a route your Go mux implements; mismatched routes return 404 from your process while the host still bills the invocation.
FUNCTIONS_CUSTOMHANDLER_PORT - Functions host assigns the port; hardcoding 8080 fails locally under func start. Fix: Always bind os.Getenv("FUNCTIONS_CUSTOMHANDLER_PORT").signal.Notify + http.Server.Shutdown.127.0.0.1 breaks ingress health probes. Fix: Listen on 0.0.0.0 / : + port.| Alternative | Use When | Don't Use When |
|---|---|---|
| Azure App Service | Simple Windows/Linux web apps | You need KEDA queue scaling |
| AKS | Kubernetes operators and CRDs | Single Go API with minimal ops |
| AWS Lambda / Cloud Run | Multi-cloud standard | Azure identity and networking are fixed |
| Azure API Management only | Gateway policies without compute | You still need a Go backend somewhere |
Azure's first-class in-process languages differ by runtime generation.
Production Go on Functions typically uses the custom handler model with your own HTTP server.
Yes with HTTP or event-driven KEDA scalers configured to allow zero replicas.
Cold starts resemble Cloud Run - keep images lean.
Enable OpenTelemetry or use the Application Insights Go SDK; emit JSON logs to stdout for automatic correlation when configured in the host.
ACA is usually simpler for always-available HTTP with standard middleware.
Functions excel when you need the bindings catalog for Timer/Queue/Blob triggers alongside small HTTP endpoints.
Often yes - change the deploy command to az containerapp up or pipeline tasks and verify port/health check settings.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026