Skip to content
Open
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
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
**/.git
**/.gitignore
**/.azure
**/.vscode
**/.vs
**/bin
**/obj
**/node_modules
README.md
project_purpose_overview.md
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

*.sh text eol=lf
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,11 @@ MigrationBackup/

# OSX
.DS_Store
# Terraform
.terraform/
*.tfstate
*.tfstate.*
crash.log
*.tfvars.local
infra-tf/terraform.exe
infra-tf/tfplan
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY DotNetCoreSqlDb.csproj ./
RUN dotnet restore

COPY . ./

# Build EF migration bundle for container startup migration.
RUN dotnet tool install --global dotnet-ef --version 8.0.6
ENV PATH="${PATH}:/root/.dotnet/tools"
RUN dotnet ef migrations bundle --target-runtime linux-x64 --force -o /src/migrationsbundle

RUN dotnet publish -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app

COPY --from=build /app/publish ./
COPY --from=build /src/migrationsbundle ./migrationsbundle
COPY docker-entrypoint.sh /app/docker-entrypoint.sh

RUN chmod +x /app/docker-entrypoint.sh /app/migrationsbundle

EXPOSE 8080

ENTRYPOINT ["/app/docker-entrypoint.sh"]
247 changes: 238 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ description: "A sample application you can use to follow along with Tutorial: De
This is an ASP.NET Core application that you can use to follow along with the tutorial at
[Tutorial: Deploy an ASP.NET Core and Azure SQL Database app to Azure App Service](https://learn.microsoft.com/azure/app-service/tutorial-dotnetcore-sqldb-app) or by using the [Azure Developer CLI (azd)](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview) according to the instructions below.

## Run the sample
This project has a [dev container configuration](.devcontainer/), which makes it easier to develop apps locally, deploy them to Azure, and monitor them. The easiest way to run this sample application is inside a GitHub codespace. Follow these steps:
## Run the sample

This project has a [dev container configuration](.devcontainer/), which makes it easier to develop apps locally, deploy them to Azure, and monitor them. The easiest way to run this sample application is inside a GitHub codespace. Follow these steps:

1. Fork this repository to your account. For instructions, see [Fork a repo](https://docs.github.com/get-started/quickstart/fork-a-repo).

Expand All @@ -35,9 +35,238 @@ This project has a [dev container configuration](.devcontainer/), which makes it
dotnet run
```

1. When you see the message `Your application running on port 5093 is available.`, click **Open in Browser**.

## Quick deploy
1. When you see the message `Your application running on port 5093 is available.`, click **Open in Browser**.

## Run with Docker (local SQL + Redis)

From the repository root:

```shell
docker compose up --build
```

Then open:

```text
http://localhost:8080
```

The `web` container runs EF migrations at startup, then launches the app. SQL Server and Redis are started by `docker-compose.yml`.

## What changed in this branch

This branch migrated the AZD deployment target from **Azure App Service** to **Azure Container Apps (ACA)** and kept local Docker workflow available.

- `azure.yaml`: service host changed to `containerapp`, Docker registry is explicitly configured.
- `infra/resources.bicep`: compute resources changed from `Microsoft.Web/*` to `Microsoft.App/*` and `Microsoft.ContainerRegistry/*`.
- `infra/main.bicep`: outputs aligned to ACA deployment (container app and registry endpoint).
- `Dockerfile` + `docker-compose.yml` + `docker-entrypoint.sh`: local containerized workflow added (web + SQL Server + Redis).

## Run modes (5 scenarios)

You can switch between these five modes independently. They do not conflict as long as you deploy to different Azure resource groups or environments.

| Mode | Main command | Compute | Data/cache | Notes |
|---|---|---|---|---|
| Local (.NET) | `dotnet run` | Local process | Local SQL (from `ConnectionStrings:MyDbConnection`), local memory cache in Development | Fastest for debugging code only. |
| Local Docker | `docker compose up --build` | Local Docker container | Local SQL Server container + local Redis container | Closer to cloud runtime than plain `dotnet run`. |
| Azure App Service (Bicep/AZD) | `azd up` (on App Service branch/template) | App Service (Linux Web App) | Azure SQL + Azure Redis + Key Vault (private endpoints) | Original template path. |
| Azure Container Apps (Bicep/AZD) | `azd up -e <env>` | Azure Container Apps | Azure SQL + Azure Redis + Key Vault (private endpoints) + ACR image pull | Current Bicep/AZD ACA path. |
| Azure Container Apps (Terraform) | `terraform apply` in `infra-tf` | Azure Container Apps | Azure SQL + Azure Redis + Log Analytics + ACR image pull | Current Terraform ACA path. |

### Recommended commands by mode

Local (.NET):

```shell
dotnet ef database update
dotnet run
```

Local Docker:

```shell
docker compose up --build
```

Azure Container Apps (current branch):

```shell
azd env new VSCodeFirstDemoAca
azd up -e VSCodeFirstDemoAca --no-prompt
```

Update ACA after code change:

```shell
azd deploy -e VSCodeFirstDemoAca --no-prompt
```

Azure Container Apps (Terraform):

```shell
cd infra-tf
terraform init
terraform plan -out tfplan
terraform apply tfplan
```

Update ACA (Terraform) after code change:

```shell
cd infra-tf
terraform plan -out tfplan
terraform apply tfplan
```

## ACA deployment troubleshooting (real issues and fixes)

These are issues encountered during the actual migration/deployment workflow and how they were resolved.

### 1) `ContainerAppInvalidName` (name must be lowercase)

Symptom:
- `Invalid ContainerApp name ... must consist of lower case ...`

Root cause:
- Environment name contained uppercase letters, and generated Container App name reused it directly.

Fix:
- Keep shared resource naming as-is, but use a dedicated lowercase variable for Container App name only:
- `var containerAppName = toLower(appName)`
- `web.name = containerAppName`

### 2) Resource provider not registered

Symptom:
- `SubscriptionIsNotRegistered ... Microsoft.App and Microsoft.ContainerService`

Fix:

```shell
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.ContainerService
```

Wait until both return `Registered`.

### 3) ACA subnet size requirement

Symptom:
- `ManagedEnvironmentInvalidNetworkConfiguration ... subnet must have a size of at least /23`

Root cause:
- ACA environment infrastructure subnet used `/24`.

Fix:
- Use a dedicated ACA infrastructure subnet with at least `/23` (example: `10.0.4.0/23`).

### 4) Existing subnet/environment conflicts

Symptoms:
- `InUseSubnetCannotBeUpdated`
- `InUseSubnetCannotBeDeleted`
- `ManagedEnvironmentInfraSubnetIdCannotBeUpdated`

Root cause:
- Previous failed/partial deployments left resources bound to old subnet settings.

Fix:
- For test environments, reset and redeploy cleanly:

```shell
azd down -e VSCodeFirstDemoAca --force --no-prompt
az keyvault purge --name <soft-deleted-kv-name> --location <region> # if needed
azd up -e VSCodeFirstDemoAca --no-prompt
```

### 5) Revision provisioning timeout (`Operation expired`)

Symptom:
- `ContainerAppOperationError ... Failed to provision revision ... Operation expired`

Root causes observed:
- Ingress `targetPort` mismatch for initial container image.
- Registry/auth configuration mismatch caused non-starting revisions.

Fixes:
- Align `targetPort` to actual container listening port.
- Ensure container registry settings are valid and deploy path can push/pull image.

### 6) `azd deploy` cannot determine registry endpoint

Symptom:
- `could not determine container registry endpoint ...`

Fix:
- Ensure `AZURE_CONTAINER_REGISTRY_ENDPOINT` is present in environment outputs.
- Set registry explicitly in `azure.yaml`:

```yaml
services:
web:
docker:
path: ./Dockerfile
registry: ${AZURE_CONTAINER_REGISTRY_ENDPOINT}
```

### 7) ACR image pull unauthorized during revision update

Symptom:
- `UNAUTHORIZED: authentication required` when ACA pulls image from ACR.

Fix:
- Configure ACA registry with explicit credentials (secret + username from ACR credentials), or ensure managed identity pull path is fully wired and propagated before deployment.

### 8) Post-provision hook parsing failure

Symptom:
- PowerShell `ConvertFrom-Json` failed because `CONNECTION_SETTINGS` was null.

Fix:
- Guard the hook:
- If value exists, parse JSON.
- Else print `(none)` and continue.

### 9) Still seeing the default "Your Azure Container Apps app is live" page

Symptom:
- ACA endpoint returns the default welcome page instead of the Todo app.

Root cause:
- `azd provision` applies infra template state. If the template uses a bootstrap image for initial provisioning, the Container App image can be reset to that default image.

Fix:
- Run deploy after provision to publish your app image revision:

```shell
azd provision -e VSCodeFirstDemoAca --no-prompt
azd deploy -e VSCodeFirstDemoAca --no-prompt
```

- Verify active revision image:

```shell
az containerapp revision list -g VSCodeFirstDemoAca_group -n vscodefirstdemoaca-koqndqit3fkna -o table
```

### 10) App starts, migrations succeed, but requests still fail with connection string errors

Symptom:
- Runtime error page with `The ConnectionString property has not been initialized.`
- Migrations may still succeed in startup.

Root cause:
- The app uses `builder.Configuration.GetConnectionString("AZURE_SQL_CONNECTIONSTRING")`, which maps to configuration key:
- `ConnectionStrings__AZURE_SQL_CONNECTIONSTRING`
- Only `AZURE_SQL_CONNECTIONSTRING` was set in container env, so EF runtime query path had empty connection string.

Fix:
- Set both env vars in ACA template to the same secret:
- `AZURE_SQL_CONNECTIONSTRING`
- `ConnectionStrings__AZURE_SQL_CONNECTIONSTRING`

## Quick deploy

This project is designed to work well with the [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview), which makes it easier to develop apps locally, deploy them to Azure, and monitor them.

Expand Down Expand Up @@ -83,6 +312,6 @@ Two types of secrets are involved: the SQL Database administrator password and t

To simplify the scenario, the AZD template generates a new database password each time you run `azd provision` or `azd up`, and the database connection string in the key vault is modified too. If you want to fully utilize `secretOrRandomPassword` in the [parameter file](infra/main.parameters.json) by committing the automatically generated password to the key vault the first time and reading it on subsequent `azd` commands, you must relax the networking restriction of the key vault to allow traffic from public networks. For more information, see [What is the behavior of the `secretOrRandomPassword` function?](https://learn.microsoft.com/azure/developer/azure-developer-cli/faq#what-is-the-behavior-of-the--secretorrandompassword--function).

## Getting help
If you're working with this project and running into issues, please post in [Issues](/issues).
## Getting help

If you're working with this project and running into issues, please post in [Issues](/issues).
3 changes: 3 additions & 0 deletions appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"MyDbConnection": "Server= (local)\\SQLEXPRESS;Database=ApplicationDB;TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Loading