Skip to content

Commit

Permalink
Update readme to include domain verification instructions when adding…
Browse files Browse the repository at this point in the history
… a custom domain name

Also added additional outputs to Bicep scripts and a script to output values required for domain verification.
  • Loading branch information
CMeeg committed Oct 18, 2023
1 parent 24ad7dc commit 3fe3673
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 16 deletions.
89 changes: 89 additions & 0 deletions .azd/scripts/read-domain-verification-vars.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
function Remove-Quotes {
param(
[Parameter(Mandatory = $true)]
[AllowEmptyString()]
[string]$value,
[string]$quoteChar = '"'
)

if ($value.StartsWith($quoteChar) -and $value.EndsWith($quoteChar)) {
return $value.Substring(1, $value.Length - 2)
}

return $value
}

function Read-EnvVars {
param(
[Parameter(Mandatory = $true)]
[string]$path
)

$envVars = @{}

if (!(Test-Path $path -PathType Leaf)) {
# File does not exist so there is nothing to do

return $envVars
}

$content = Get-Content -raw $path | ConvertFrom-StringData

$content.GetEnumerator() | Foreach-Object {
$key, $value = $_.Name, $_.Value

if (($null -eq $value) -or ($value -eq "")) {
$envVars[$key] = $null
} else {
$value = Remove-Quotes -value $value -quoteChar '"'
$value = Remove-Quotes -value $value -quoteChar "'"

$envVars[$key] = $value
}
}

return $envVars
}

function Read-AzdEnvVars {
$azdEnv = (azd env get-values)

$envVars = @{}

$azdEnv | ForEach-Object {
$entry = $_

if ($null -eq $entry -or $entry -eq "" -or $entry.Contains("=") -eq $false) {
return
}

$key, $value = $entry -split '=', 2

if ($key -eq "" -or $key.Contains(" ") -eq $true) {
return
}

if (($null -eq $value) -or ($value -eq "")) {
$envVars[$key] = $null
} else {
$value = Remove-Quotes -value $value -quoteChar '"'
$value = Remove-Quotes -value $value -quoteChar "'"

$envVars[$key] = $value
}
}

return $envVars
}

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path

$envAzdPath = Join-Path $scriptDir "../../.azure/${env:AZURE_ENV_NAME}/.env"

$envAzd = if ($null -eq $env:AZURE_ENV_NAME) { Read-AzdEnvVars } else { Read-EnvVars -path $envAzdPath }

# Output info required for domain verification
Write-Host "=== Container apps domain verification ==="
Write-Host "Static IP: $($envAzd.AZURE_CONTAINER_STATIC_IP)"
Write-Host "FQDN: $($envAzd.AZURE_WEB_APP_FQDN)"
Write-Host "Verification code: $($envAzd.AZURE_CONTAINER_DOMAIN_VERIFICATION_CODE)"
4 changes: 4 additions & 0 deletions .azdo/pipelines/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ jobs:
AZURE_LOCATION: $(AZURE_LOCATION)
AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)

- pwsh: |
npm run env:dv
displayName: Domain Verification
- task: AzureCLI@2
displayName: Deploy Application
inputs:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/azure-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ jobs:
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}

- name: Domain Verification
run: npm run env:dv
shell: pwsh
env:
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}

- name: Deploy Application
run: azd deploy --no-prompt
shell: pwsh
Expand Down
61 changes: 46 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,30 @@ You need to manually create a pipeline in Azure DevOps - the presence of the `.a
## Adding a custom domain name

To add a custom domain name to your Container App you will need to add an environment variable named `SERVICE_WEB_CUSTOM_DOMAIN_NAME`:
Azure supports adding custom domain names with free managed SSL certificates to Container Apps. The Bicep scripts included in this template are setup to provide this capability, but before we can add a custom domain name and managed certificate Azure requires that DNS records be created to verify domain ownership.

### Verify domain ownership

The verification process is described in steps 7 and 8 of the [Container Apps documentation](https://learn.microsoft.com/en-us/azure/container-apps/custom-domains-managed-certificates?pivots=azure-portal#add-a-custom-domain-and-managed-certificate), so please refer to that for specifics, but in summary you must add the following records via your DNS provider:

* A `TXT` record containing a domain verification code; and
* An `A` record containing the static IP address of the Container Apps Environment; or
* A `CNAME` record containing the FQDN of the Container App

To get the information that you require for these DNS records you can:

* When running `azd` locally
* Run `azd provision` (if you have not already)
* Run `npm run env:dv`
* When running `azd` in a pipeline
* Run the pipeline (if you have not already)
* Check the output of the `Domain Verification` task

Included in the output are the `Static IP`, `FQDN` and `Verification code` - use these values to set your DNS records as per the Container Apps documentation (linked above).

### Set your custom domain name

To set your custom domain name on your Container App you will need to add (or update) an environment variable named `SERVICE_WEB_CUSTOM_DOMAIN_NAME`:

> For example, to set the domain name for the container app to `www.example.com` you would add an environment variable `SERVICE_WEB_CUSTOM_DOMAIN_NAME=www.example.com`.
Expand All @@ -439,28 +462,24 @@ To add a custom domain name to your Container App you will need to add an enviro

You will then need to:

* Run `azd provision` for the custom domain name to be added to your container app
* Run `azd deploy` for the custom domain name to be set as your app's base URL

> The base URL is used by the [`getAbsoluteUrl`](#azure-cdn) function provided by this template.
* When running `azd` locally
* Run `azd provision`
* When running `azd` in a pipeline
* Run the pipeline

💡 When you add a custom domain name a redirect rule is automatically added so that if you attempt to navigate to the default domain of the Container App there will be a permanent redirect to the custom domain name - this redirect is configured in `next.config.js`.
💡 When you add a custom domain name a redirect rule is automatically added so that if you attempt to navigate to the default domain of the Container App there will be a permanent redirect to the custom domain name - this redirect is configured in `next.config.js`. The [`getAbsoluteUrl`](#azure-cdn) function provided by this template will also use the custom domain name you have set rather than the default domain of the Container App.

### Add a free managed certificate for your custom domain

When you add a custom domain name to your Container App there is no SSL certificate provided by default, but Azure provides a facility to add a free managed SSL certificate:

💡 To successfully complete the steps below you will need to complete all of the steps described in [adding a custom domain name](#adding-a-custom-domain-name) first otherwise the `Custom domain` name will not be available on the Container Apps Environment in step 1.
The final step is to create a free managed SSL certificate for your custom domain name and add it to your Container App:

1. Create the certificate
* Sign in to the [Azure Portal](https://portal.azure.com)
* Go to the your Container Apps Environment resource
* Go to your Container Apps Environment resource
* Go to `Certificates` -> `Managed certificate`
* Click `Add certificate`
* Select your `Custom domain` name
* Choose the appropriate `Hostname record type`
* Follow the instructions under `Domain name validation`
* If you need further instruction there is [official documentation](https://learn.microsoft.com/en-us/azure/container-apps/custom-domains-managed-certificates?pivots=azure-portal#add-a-custom-domain-and-managed-certificate)
* `Validate` the custom domain name
* `Add` the certificate
* Azure will now provision the certificate
Expand All @@ -472,10 +491,22 @@ When you add a custom domain name to your Container App there is no SSL certific
* Copy the `Resource ID`
* Create the `Certificate ID` using the pattern:
* `{Resource ID}/managedCertificates/{Certificate Name}`
3. Expose the `Certificate ID` to your `main.bicep` file through an environment variable
* Repeat the process you followed to [add the custom domain name as an environment variable](#adding-a-custom-domain-name), but `SERVICE_WEB_CUSTOM_DOMAIN_CERT_ID={Certificate ID}`

⚡ The next time that you trigger `azd provision` - either by running locally or through your pipeline - the certificate will be bound to the custom domain name that you added to your Container App.
You will then need to add (or update) an environment variable named `SERVICE_WEB_CUSTOM_DOMAIN_CERT_ID` and with the value of your `Certificate ID`:

* In your local dev environment: to your `.env.local` file
* In GitHub Actions: as an [Environment variable](#add-environment-variables) in the target Environment (e.g. `production`)
* In Azure DevOps: as a [Variable in the Variable group](#create-a-variable-group-for-your-environment) for the target Environment (e.g. `production`)

And finally you will need to:

* When running `azd` locally
* Run `azd provision`
* Run `azd deploy`
* When running `azd` in a pipeline
* Run the pipeline

⚡ The custom domain and SSL certificate will now be bound to your Container App.

> It is possible to automate the creation of managed certificates through Bicep, which would be preferable to the above manual process, but there are a few ["chicken and egg" issues](https://johnnyreilly.com/azure-container-apps-bicep-managed-certificates-custom-domains) that make automation difficult at the moment. In the context of this template it was decided that a manual solution is the most pragmatic solution.
>
Expand Down
2 changes: 2 additions & 0 deletions infra/containers/container-app-environment.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ resource environment 'Microsoft.App/managedEnvironments@2023-05-01' = {
output id string = environment.id
output name string = environment.name
output defaultDomain string = environment.properties.defaultDomain
output staticIp string = environment.properties.staticIp
output domainVerificationCode string = environment.properties.customDomainConfiguration.customDomainVerificationId
2 changes: 1 addition & 1 deletion infra/containers/container-app.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,5 @@ resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
output id string = containerApp.id
output name string = containerApp.name
output serviceBind object = !empty(serviceType) ? { serviceId: containerApp.id, name: name } : {}
output hostName string = ingressEnabled ? containerApp.properties.configuration.ingress.fqdn : ''
output fqdn string = ingressEnabled ? containerApp.properties.configuration.ingress.fqdn : ''
output uri string = ingressEnabled ? 'https://${containerApp.properties.configuration.ingress.fqdn}' : ''
3 changes: 3 additions & 0 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,12 @@ output AZURE_TENANT_ID string = tenant().tenantId
output AZURE_CONTAINER_ENVIRONMENT_NAME string = containerAppEnvironment.outputs.name
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.outputs.loginServer
output AZURE_CONTAINER_REGISTRY_NAME string = containerRegistry.outputs.name
output AZURE_CONTAINER_STATIC_IP string = containerAppEnvironment.outputs.staticIp
output AZURE_CONTAINER_DOMAIN_VERIFICATION_CODE string = containerAppEnvironment.outputs.domainVerificationCode

// Web app outputs
output APPLICATIONINSIGHTS_CONNECTION_STRING string = appInsights.outputs.connectionString
output AZURE_WEB_APP_FQDN string = webAppServiceContainerApp.outputs.fqdn
output NEXT_PUBLIC_APP_ENV string = environmentName
output NEXT_PUBLIC_APPLICATIONINSIGHTS_CONNECTION_STRING string = appInsights.outputs.connectionString
output NEXT_PUBLIC_BASE_URL string = webAppServiceUri
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"build": "next build",
"dev": "next dev",
"env:init": "pwsh ./.azd/scripts/create-env-local.ps1",
"env:dv": "pwsh ./.azd/scripts/read-domain-verification-vars.ps1",
"lint": "next lint",
"start": "next start"
},
Expand Down

0 comments on commit 3fe3673

Please sign in to comment.