分類: 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_NAME | App Settings 中的映像設定 | myacr.azurecr.io/my-app:latest |
DOCKER_REGISTRY_SERVER_URL | Registry 驗證端點 | https://myacr.azurecr.io |
DOCKER_REGISTRY_SERVER_PASSWORD | ACR 密碼(可能顯示為 null) | 需透過 az rest 明確寫入 |
關鍵原則:
linuxFxVersion(即容器設定DOCKER_CUSTOM_IMAGE_NAME)是 Azure Web App 決定拉取哪個映像的最終依據,
其優先級高於 App Settings 中的DOCKER_CUSTOM_IMAGE_NAME。
因此,務必使用az webapp config container set確保此值包含完整的 Registry URL。
常見陷阱
az webapp create+ 短映像名稱 →linuxFxVersion無 Registry 前綴 → 拉 Docker Hubaz webapp create+ 完整 URL +--container-registry-url→linuxFxVersionRegistry 前綴重複az webapp config container set後 CLI 顯示密碼為 null → 實際可能空值;建議以az rest PUT /config/appsettings補強- Windows 上
DATABASE_URL含&→az呼叫走cmd.exe,&被解析為命令分隔符 → 改用az rest搭配 JSON 檔案