Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ What's changed since v1.47.0:
- Azure Container Registry:
- Check that audit diagnostic logs are enabled for Container Registry by @copilot.
[#3536](https://github.com/Azure/PSRule.Rules.Azure/issues/3536)
- Container Apps:
- Check that liveness and readiness health probes use HTTP checks for HTTP-based ingress.
[#3714](https://github.com/Azure/PSRule.Rules.Azure/issues/3714)
- Updated rules:
- Azure Kubernetes Service:
- Updated `Azure.AKS.Version` to use `1.33.7` as the minimum version by @BernieWhite.
Expand Down
2 changes: 1 addition & 1 deletion docs/en/rules/Azure.ContainerApp.ExternalIngress.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
}
```

<!-- external:avm avm/res/app/container-app:0.11.0 ingressExternal -->
<!-- external:avm avm/res/app/container-app ingressExternal -->

## NOTES

Expand Down
182 changes: 182 additions & 0 deletions docs/en/rules/Azure.ContainerApp.HealthProbe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
reviewed: 2026-03-25
severity: Important
pillar: Reliability
category: RE:07 Self-preservation
resource: Container App
resourceType: Microsoft.App/containerApps
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ContainerApp.HealthProbe/
---

# Use HTTP health probes for HTTP-based ingress

## SYNOPSIS

Container app ingress that uses HTTP should have HTTP health probes configured for liveness and readiness checks.

## DESCRIPTION

Azure Container Apps supports health probes to determine the health and readiness of your containers.
Health probes can be configured as HTTP or TCP checks and support liveness, readiness, and startup probe types.

When a container app uses HTTP-based ingress (transport is `http` or `http2`, or the target port is `80`, `8080`, or `443`),
health probes should use HTTP checks (`httpGet`) for liveness and readiness probes.
HTTP health probes provide granular feedback by checking the HTTP response status code,
which gives more accurate information about whether a replica is available and ready to receive traffic compared to
a TCP port check which only determines if a port is open or closed.

The default health probes use TCP port checks when no probes are explicitly configured.
Configuring HTTP health probes instead allows the platform to better detect and respond to application-level failures.

Startup probes are excluded from this check as they are commonly configured as TCP for initial container startup purposes.

## RECOMMENDATION

Consider configuring HTTP health probes (`httpGet`) for liveness and readiness probes on containers
that use HTTP-based ingress.

## EXAMPLES

### Configure with Azure template

To deploy Container Apps that pass this rule:

- For each container in `properties.template.containers`:
- Configure a `Liveness` probe with `httpGet` in the `probes` array.
- Configure a `Readiness` probe with `httpGet` in the `probes` array.

For example:

```json
{
"type": "Microsoft.App/containerApps",
"apiVersion": "2025-07-01",
"name": "[parameters('appName')]",
"location": "[parameters('location')]",
"identity": {
"type": "SystemAssigned"
},
"properties": {
"environmentId": "[resourceId('Microsoft.App/managedEnvironments', parameters('envName'))]",
"configuration": {
"ingress": {
"external": false,
"targetPort": 8080,
"transport": "http"
}
},
"template": {
"containers": [
{
"name": "app",
"image": "[parameters('image')]",
"probes": [
{
"type": "Liveness",
"httpGet": {
"path": "/healthz",
"port": 8080
},
"initialDelaySeconds": 5,
"periodSeconds": 10
},
{
"type": "Readiness",
"httpGet": {
"path": "/healthz/ready",
"port": 8080
},
"initialDelaySeconds": 5,
"periodSeconds": 10
}
]
}
],
"scale": {
"minReplicas": 2
}
}
},
"dependsOn": [
"[resourceId('Microsoft.App/managedEnvironments', parameters('envName'))]"
]
}
```

### Configure with Bicep

To deploy Container Apps that pass this rule:

- For each container in `properties.template.containers`:
- Configure a `Liveness` probe with `httpGet` in the `probes` array.
- Configure a `Readiness` probe with `httpGet` in the `probes` array.

For example:

```bicep
resource containerApp 'Microsoft.App/containerApps@2025-07-01' = {
name: appName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
environmentId: containerEnv.id
configuration: {
ingress: {
external: false
targetPort: 8080
transport: 'http'
}
}
template: {
containers: [
{
name: 'app'
image: image
probes: [
{
type: 'Liveness'
httpGet: {
path: '/healthz'
port: 8080
}
initialDelaySeconds: 5
periodSeconds: 10
}
{
type: 'Readiness'
httpGet: {
path: '/healthz/ready'
port: 8080
}
initialDelaySeconds: 5
periodSeconds: 10
}
]
}
]
scale: {
minReplicas: 2
}
}
}
}
```

<!-- external:avm avm/res/app/container-app containers[*].probes -->

## NOTES

This rule applies to container apps where the ingress transport is `http` or `http2`,
or where the target port is `80`, `8080`, or `443`.

Startup probes are excluded from this check.

When multiple containers are defined, each container must have both liveness and readiness HTTP probes configured.

## LINKS

- [RE:07 Self-preservation](https://learn.microsoft.com/azure/well-architected/reliability/self-preservation)
- [Health probes in Azure Container Apps](https://learn.microsoft.com/azure/container-apps/health-probes)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.app/containerapps#containerappprobe)
1 change: 1 addition & 0 deletions docs/en/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,5 +553,6 @@ AZR-000531 | [Azure.ServiceFabric.ManagedNaming](Azure.ServiceFabric.ManagedNami
AZR-000532 | [Azure.EventHub.AvailabilityZone](Azure.EventHub.AvailabilityZone.md) | Use zone redundant Event Hub namespaces in supported regions to improve reliability. | GA
AZR-000533 | [Azure.Redis.MigrateAMR](Azure.Redis.MigrateAMR.md) | Azure Cache for Redis is being retired. Migrate to Azure Managed Redis. | GA
AZR-000534 | [Azure.RedisEnterprise.MigrateAMR](Azure.RedisEnterprise.MigrateAMR.md) | Azure Cache for Redis Enterprise and Enterprise Flash are being retired. Migrate to Azure Managed Redis. | GA
AZR-000537 | [Azure.ContainerApp.HealthProbe](Azure.ContainerApp.HealthProbe.md) | Container apps using HTTP-based ingress should use HTTP health probes for liveness and readiness checks. | GA

*[GA]: Generally Available &mdash; Rules related to a generally available Azure features.
2 changes: 1 addition & 1 deletion docs/examples/resources/containerapp.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ resource containerEnv 'Microsoft.App/managedEnvironments@2025-01-01' = {
}

// An example Container App using a minimum of 2 replicas.
resource containerApp 'Microsoft.App/containerApps@2025-01-01' = {
resource containerApp 'Microsoft.App/containerApps@2025-07-01' = {
name: name
location: location
identity: {
Expand Down
59 changes: 59 additions & 0 deletions src/PSRule.Rules.Azure/rules/Azure.ContainerApp.Rule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,40 @@ spec:
field: properties.template.scale.minReplicas
greaterOrEquals: 2

---
# Synopsis: Container app ingress that uses HTTP should have HTTP health probes configured for liveness and readiness checks.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: Azure.ContainerApp.HealthProbe
ref: AZR-000537
tags:
release: GA
ruleSet: 2026_06
Azure.WAF/pillar: Reliability
spec:
with:
- Azure.ContainerApp.IsHttpIngress
condition:
field: properties.template.containers
allOf:
- field: probes
anyOf:
- allOf:
- field: type
equals: Liveness
- field: httpGet
hasValue: true
count: 1
- field: probes
anyOf:
- allOf:
- field: type
equals: Readiness
- field: httpGet
hasValue: true
count: 1

#endregion Rules

#region Selectors
Expand All @@ -224,4 +258,29 @@ spec:
- field: properties.configuration.ingress
exists: true

---
# Synopsis: Get container apps with HTTP-based ingress.
apiVersion: github.com/microsoft/PSRule/v1
kind: Selector
metadata:
name: Azure.ContainerApp.IsHttpIngress
spec:
if:
allOf:
- type: '.'
equals: Microsoft.App/containerApps
- field: properties.configuration.ingress
exists: true
- anyOf:
- field: properties.configuration.ingress.transport
in:
- http
- http2
- field: properties.configuration.ingress.targetPort
equals: 80
- field: properties.configuration.ingress.targetPort
equals: 8080
- field: properties.configuration.ingress.targetPort
equals: 443

#endregion Selectors
42 changes: 29 additions & 13 deletions tests/PSRule.Rules.Azure.Tests/Azure.ContainerApp.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ Describe 'Azure.ContainerApp' -Tag 'ContainerApp' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'capp-A', 'capp-C';
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'capp-A', 'capp-C', 'capp-E', 'capp-F', 'capp-G';
}

It 'Azure.ContainerApp.ManagedIdentity' {
Expand All @@ -68,8 +68,8 @@ Describe 'Azure.ContainerApp' -Tag 'ContainerApp' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'capp-C', 'capp-D';
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'capp-C', 'capp-D', 'capp-E', 'capp-F', 'capp-G';
}

It 'Azure.ContainerApp.PublicAccess' {
Expand Down Expand Up @@ -102,8 +102,8 @@ Describe 'Azure.ContainerApp' -Tag 'ContainerApp' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'capp-A', 'capp-D';
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'capp-A', 'capp-D', 'capp-E', 'capp-F', 'capp-G';
}

It 'Azure.ContainerApp.Storage' {
Expand Down Expand Up @@ -136,8 +136,8 @@ Describe 'Azure.ContainerApp' -Tag 'ContainerApp' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'capp-B', 'capp-C', 'capp-D';
$ruleResult.Length | Should -Be 6;
$ruleResult.TargetName | Should -BeIn 'capp-B', 'capp-C', 'capp-D', 'capp-E', 'capp-F', 'capp-G';
}

It 'Azure.ContainerApp.RestrictIngress' {
Expand All @@ -155,8 +155,8 @@ Describe 'Azure.ContainerApp' -Tag 'ContainerApp' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'capp-D';
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -BeIn 'capp-D', 'capp-E', 'capp-F', 'capp-G';
}

It 'Azure.ContainerApp.APIVersion' {
Expand All @@ -171,8 +171,8 @@ Describe 'Azure.ContainerApp' -Tag 'ContainerApp' {

# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'capp-C', 'capp-D';
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'capp-C', 'capp-D', 'capp-E', 'capp-F', 'capp-G';
}

It 'Azure.ContainerApp.MinReplicas' {
Expand All @@ -186,8 +186,24 @@ Describe 'Azure.ContainerApp' -Tag 'ContainerApp' {

# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'capp-C', 'capp-D', 'capp-E', 'capp-F', 'capp-G';
}

It 'Azure.ContainerApp.HealthProbe' {
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.ContainerApp.HealthProbe' };

# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'capp-A', 'capp-B', 'capp-C', 'capp-D', 'capp-F';

# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'capp-C', 'capp-D';
$ruleResult.TargetName | Should -BeIn 'capp-E', 'capp-G';
}

It 'Azure.ContainerApp.AvailabilityZone' {
Expand Down
Loading
Loading