Azure Web App 容器映像從 Docker Hub 拉取而非 ACR:診斷與修復指南

分類: Azure、DevOps、Docker
標籤: Azure App Service、Azure Container Registry、Docker、CI/CD


問題描述

將 Docker 容器部署至 Azure Web App 時,Log stream 出現以下錯誤:

Pulling image: my-frontend:latest
DockerApiException: Docker API responded with status code=InternalServerError,
  response={"message":"Head \"https://registry-1.docker.io/v2/library/my-frontend/manifests/latest\":
  unauthorized: incorrect username or password"}
Pulling docker image my-frontend:latest failed.
DockerApiException: Docker API responded with status code=NotFound,
  response={"message":"pull access denied for my-frontend, repository does not exist
  or may require 'docker login': denied: requested access to the resource is denied"}
Image pull failed. Defaulting to local copy if present.
Stopping site my-frontend-app because it failed during startup.

明明映像已成功推送至 Azure Container Registry(ACR),Azure 卻跑去 Docker Hub 拉取,導致 Web App 無法啟動。


根本原因

Azure Web App for Containers 透過 linuxFxVersion 這個設定決定要拉取哪個映像。可用以下指令查看:

az webapp show \
  --name my-frontend-app \
  --resource-group my-resource-group \
  --query "siteConfig.linuxFxVersion" -o tsv

問題環境的輸出:

DOCKER|my-frontend:latest

這個值缺少 Registry 前綴,Azure 因此回退至預設的 Docker Hub(registry-1.docker.io)。

正確值應為:

DOCKER|myacr.azurecr.io/my-frontend:latest

為何會產生這個問題?

使用 az webapp create 建立容器 Web App 時,若 --container-image-name 只傳入 IMAGE:TAG(不含 Registry 網址),Azure 會將 linuxFxVersion 設為不含 Registry 前綴的值:

# ❌ 這樣建立後,linuxFxVersion 只有 DOCKER|my-frontend:latest
az webapp create \
  --name my-frontend-app \
  --resource-group my-resource-group \
  --plan my-app-plan \
  --container-image-name "my-frontend:latest" \          # 沒有 registry 前綴
  --container-registry-url "https://myacr.azurecr.io" \
  --container-registry-user myacr \
  --container-registry-password "<password>"

此時,--container-registry-url 只用來設定驗證憑證,不會自動補入映像名稱的 Registry 前綴。

反之,若在 --container-image-name 傳入完整 URL,搭配 --container-registry-url 時又會導致另一個問題:Registry 網址被重複拼接

DOCKER|myacr.azurecr.io/myacr.azurecr.io/my-frontend:latest  # ❌ 雙重前綴

診斷步驟

1. 確認 linuxFxVersion

az webapp show \
  --name my-frontend-app \
  --resource-group my-resource-group \
  --query "{linuxFxVersion:siteConfig.linuxFxVersion, state:state}" \
  -o table

2. 確認容器設定

az webapp config container show \
  --name my-frontend-app \
  --resource-group my-resource-group \
  --query "[?name=='DOCKER_CUSTOM_IMAGE_NAME']" \
  -o table

3. 確認 App Settings 中的 Docker 憑證

az webapp config appsettings list \
  --name my-frontend-app \
  --resource-group my-resource-group \
  --query "[?name=='DOCKER_REGISTRY_SERVER_URL' || name=='DOCKER_REGISTRY_SERVER_USERNAME' || name=='DOCKER_REGISTRY_SERVER_PASSWORD']" \
  -o table

DOCKER_REGISTRY_SERVER_PASSWORD 顯示為 null,即是憑證遺失的警訊。


修復方法

方法一:立即修復(手動執行)

Step 1:用完整 ACR URL 更新容器設定

ACR_PASSWORD=$(az acr credential show --name myacr --query "passwords[0].value" -o tsv)

az webapp config container set \
  --name my-frontend-app \
  --resource-group my-resource-group \
  --container-image-name "myacr.azurecr.io/my-frontend:latest" \
  --container-registry-url "https://myacr.azurecr.io" \
  --container-registry-user myacr \
  --container-registry-password "$ACR_PASSWORD"

執行後確認 linuxFxVersion 已變為 DOCKER|myacr.azurecr.io/my-frontend:latest

Step 2:確保 App Settings 中的密碼不為 null

az webapp config container set 在某些情況下 CLI 顯示密碼為 null(實際上可能已儲存,但保險起見建議明確寫入)。使用 az rest 繞過 Windows 的 cmd.exe & 字元解析問題:

# PowerShell
$sub = "<your-subscription-id>"
$rg  = "my-resource-group"
$app = "my-frontend-app"
$ACR_PASSWORD = (az acr credential show --name myacr --query "passwords[0].value" -o tsv)

# 取出現有 App Settings 再合併
$existing = az webapp config appsettings list --name $app --resource-group $rg | ConvertFrom-Json
$props = @{}
foreach ($s in $existing) {
    if ($s.value -ne $null) { $props[$s.name] = $s.value }
}

# 覆寫 Docker 相關設定
$props["DOCKER_REGISTRY_SERVER_URL"]      = "https://myacr.azurecr.io"
$props["DOCKER_REGISTRY_SERVER_USERNAME"] = "myacr"
$props["DOCKER_REGISTRY_SERVER_PASSWORD"] = $ACR_PASSWORD
$props["DOCKER_CUSTOM_IMAGE_NAME"]        = "myacr.azurecr.io/my-frontend:latest"

$tmpFile = "$env:TEMP\appsettings_fix.json"
@{ properties = $props } | ConvertTo-Json -Depth 3 | Set-Content -Path $tmpFile -Encoding UTF8

az rest --method PUT `
  --url "https://management.azure.com/subscriptions/$sub/resourceGroups/$rg/providers/Microsoft.Web/sites/$app/config/appsettings?api-version=2022-03-01" `
  --body "@$tmpFile"

Remove-Item $tmpFile

Step 3:重啟 Web App

az webapp restart --name my-frontend-app --resource-group my-resource-group

等待 30–60 秒(Docker 容器冷啟動需要時間),再測試 HTTP 回應:

curl -o /dev/null -s -w "%{http_code}" https://my-frontend-app.azurewebsites.net
# 預期:200

方法二:從根本修正 CI/CD 部署腳本

az webapp create 之後,立即執行 az webapp config container set 以確保 linuxFxVersion 包含完整 Registry 路徑:

# PowerShell deploy script 範例

$ACR          = "myacr"
$ACR_SERVER   = "myacr.azurecr.io"
$ACR_PASSWORD = (az acr credential show --name $ACR --query "passwords[0].value" -o tsv)
$IMAGE        = "my-frontend"
$TAG          = "latest"
$APP          = "my-frontend-app"
$RG           = "my-resource-group"
$PLAN         = "my-app-plan"

# Step 1: 建立 Web App(用短名稱避免雙重前綴問題)
az webapp create `
  --name $APP `
  --resource-group $RG `
  --plan $PLAN `
  --container-image-name "${IMAGE}:${TAG}" `
  --container-registry-url "https://$ACR_SERVER" `
  --container-registry-user $ACR `
  --container-registry-password $ACR_PASSWORD

# Step 2: 立即修正 linuxFxVersion,補入完整 Registry 前綴
az webapp config container set `
  --name $APP `
  --resource-group $RG `
  --container-image-name "$ACR_SERVER/${IMAGE}:${TAG}" `
  --container-registry-url "https://$ACR_SERVER" `
  --container-registry-user $ACR `
  --container-registry-password $ACR_PASSWORD | Out-Null

# Step 3: 透過 az rest 確保 App Settings 中密碼明確寫入
$props = @{
    DOCKER_REGISTRY_SERVER_URL      = "https://$ACR_SERVER"
    DOCKER_REGISTRY_SERVER_USERNAME = $ACR
    DOCKER_REGISTRY_SERVER_PASSWORD = $ACR_PASSWORD
    DOCKER_CUSTOM_IMAGE_NAME        = "$ACR_SERVER/${IMAGE}:${TAG}"
    WEBSITES_PORT                   = "80"
}
$tmpFile = "$env:TEMP\appsettings.json"
@{ properties = $props } | ConvertTo-Json -Depth 3 | Set-Content $tmpFile -Encoding UTF8
az rest --method PUT `
  --url "https://management.azure.com/subscriptions/<sub-id>/resourceGroups/$RG/providers/Microsoft.Web/sites/$APP/config/appsettings?api-version=2022-03-01" `
  --body "@$tmpFile"
Remove-Item $tmpFile

重點整理

設定項目說明正確值範例
linuxFxVersion實際拉取的映像(最高優先)DOCKER|myacr.azurecr.io/my-app:latest
DOCKER_CUSTOM_IMAGE_NAMEApp Settings 中的映像設定myacr.azurecr.io/my-app:latest
DOCKER_REGISTRY_SERVER_URLRegistry 驗證端點https://myacr.azurecr.io
DOCKER_REGISTRY_SERVER_PASSWORDACR 密碼(可能顯示為 null)需透過 az rest 明確寫入

關鍵原則:
linuxFxVersion(即容器設定 DOCKER_CUSTOM_IMAGE_NAME)是 Azure Web App 決定拉取哪個映像的最終依據
其優先級高於 App Settings 中的 DOCKER_CUSTOM_IMAGE_NAME
因此,務必使用 az webapp config container set 確保此值包含完整的 Registry URL。


常見陷阱

  1. az webapp create + 短映像名稱linuxFxVersion 無 Registry 前綴 → 拉 Docker Hub
  2. az webapp create + 完整 URL + --container-registry-urllinuxFxVersion Registry 前綴重複
  3. az webapp config container set 後 CLI 顯示密碼為 null → 實際可能空值;建議以 az rest PUT /config/appsettings 補強
  4. Windows 上 DATABASE_URL&az 呼叫走 cmd.exe& 被解析為命令分隔符 → 改用 az rest 搭配 JSON 檔案

參考資料

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *