---
title: "Azure App Service"
description: "Como executar o Servidor do TrustBridge como container Linux personalizado no Azure App Service, numa única instância, com Azure SQL, identidade gerenciada e sem senha."
canonical: "https://docs.lacunasoftware.com/articles/trustbridge/azure"
locale: "pt-BR"
product: "TrustBridge"
last_updated: "2026-10-12"
---

# Azure App Service

O Servidor como um container Linux personalizado no Azure App Service, em **uma única instância**,
falando com o Azure SQL por meio de uma identidade gerenciada e sem nenhuma senha. Leia antes a
[Instalação do Servidor](https://docs.lacunasoftware.com/articles/trustbridge/server-installation) para conhecer as quatro coisas que toda implantação
tem em comum, em particular o Protetor do Chaveiro e a regra da instância única.

Tudo o que a página do Docker diz sobre chaves de configuração, o protetor, o cofre e os backups vale
aqui sem alteração. O que muda é onde o TLS é terminado, quem agenda os reinícios e as configurações
específicas do Azure descritas abaixo.

## O que não se pode fazer aqui

**Nada de scale-out.** Uma instância, pelo motivo explicado na página de instalação. Nada no produto
vai recusar uma segunda instância; ela simplesmente vai se comportar mal. Confira o número de
instâncias depois de qualquer mudança de plano e, se puder, crie um alerta para ele.

## 1. O plano do App Service

A frota determina uma única coisa sobre o SKU: **não pode ser Free**, que limita as conexões
WebSocket a cinco e responde 429 além disso, enquanto cada Workstation Service mantém uma conexão
continuamente. Acima do Free, o teto de WebSockets não é uma restrição. O **Always On** é obrigatório
(veja abaixo) e é um recurso dos tiers dedicados, então dimensione o plano pelo trabalho de assinatura.
O P1V3 é um bom começo.

```bash
az appservice plan create -g <rg> -n <plan> --is-linux --sku P1V3 --number-of-workers 1
```

## 2. Azure SQL

```bash
az sql server create -g <rg> -n <sqlserver> --enable-ad-only-auth \
  --external-admin-name "<your-group>" \
  --external-admin-principal-type Group \
  --external-admin-sid <the-group's-object-id>
az sql db create -g <rg> --server <sqlserver> -n TrustBridge --service-objective S1
```

Use um grupo como administrador do Entra, em vez de uma pessoa que pode sair da empresa. Libere o
aplicativo no firewall: "Allow Azure services" sem integração com VNet, ou um private endpoint com ela.
**Deixar o acesso pela rede pública desativado sem um private endpoint recusa todas as conexões com o
erro 47073**, e o Servidor gasta todo o prazo de inicialização esperando por ele.

## 3. A imagem

Qualquer registry serve. O Azure Container Registry aparece aqui porque o App Service consegue baixar
imagens dele com uma identidade gerenciada, em vez de uma senha armazenada.

```bash
az acr login -n <registry>
docker tag trustbridge-server:1.6.1 <registry>.azurecr.io/trustbridge-server:1.6.1
docker push <registry>.azurecr.io/trustbridge-server:1.6.1
```

**Use uma tag imutável.** Com `DOCKER_ENABLE_CI` ligado, o App Service baixa a imagem de novo a cada
push, então uma tag móvel deixa a plataforma reiniciar o Servidor sem supervisão, e todo reinício
encerra as sessões ativas vinculadas a um Certificado do Cofre.

## 4. Crie o aplicativo

```bash
az webapp create -g <rg> -p <plan> -n <app> \
  --container-image-name <registry>.azurecr.io/trustbridge-server:1.6.1

az webapp identity assign -g <rg> -n <app>
az role assignment create --role AcrPull \
  --assignee-object-id $(az webapp identity show -g <rg> -n <app> --query principalId -o tsv) \
  --assignee-principal-type ServicePrincipal \
  --scope $(az acr show -n <registry> --query id -o tsv)
az webapp config set -g <rg> -n <app> --acr-use-identity true
az webapp restart -g <rg> -n <app>
```

Três situações que aparecem todas como um aplicativo que nunca inicia, com `UNAUTHORIZED` no log do
container:

- **Num registry com ABAC habilitado, o `AcrPull` não concede nada.** Um registry no modo
  `RBAC Registry + ABAC Repository Permissions` exige `Container Registry Repository Reader`.
  Verifique com `az acr show -n <registry> --query roleAssignmentMode -o tsv`.
- **O registry precisa aceitar tokens com audiência ARM**, ou o download com identidade gerenciada é
  recusado com `token validation failed`: `az acr config authentication-as-arm update -r <registry> --status enabled`.
- **O reinício não é mera formalidade.** O aplicativo foi apontado para uma imagem privada antes de ter
  uma identidade ou um role, então o primeiro download falhou, e nada tenta de novo sozinho.

Um aplicativo criado pelo **portal** é um aplicativo *sitecontainers*: a imagem dele é definida com
`az webapp sitecontainers`, e não com `az webapp config container`, e uma alteração de configuração
sozinha nem sempre o recicla; por isso, reinicie-o explicitamente.

## 5. Dê ao aplicativo acesso ao banco de dados

Conecte-se ao banco de dados como o administrador do Entra dele e crie a identidade como usuário:

```sql
CREATE USER [<app>] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [<app>];
ALTER ROLE db_datawriter ADD MEMBER [<app>];
ALTER ROLE db_ddladmin  ADD MEMBER [<app>];
```

O `db_ddladmin` é o que permite ao Servidor atualizar o próprio schema, o que ele faz por padrão.
Deixe-o de fora somente se também definir `Database__MigrateOnStart=false` e aplicar as migrações você
mesmo antes de cada atualização. Omitir a permissão e manter o padrão transforma a próxima atualização
numa inicialização recusada.

## 6. Configurações

```bash
az webapp config appsettings set -g <rg> -n <app> --settings \
  WEBSITES_PORT=8080 \
  WEBSITES_CONTAINER_START_TIME_LIMIT=1800 \
  ASPNETCORE_FORWARDEDHEADERS_ENABLED=true \
  KeyRingProtector__Certificate="$(base64 -w0 protector.pfx)" \
  ConnectionStrings__TrustBridge="Server=tcp:<sqlserver>.database.windows.net,1433;Database=TrustBridge;Authentication=Active Directory Managed Identity;Encrypt=True" \
  Signer__BaseAddress="https://signer.example.com" \
  Signer__ApiKey="<application|secret>" \
  Admin__Bootstrap__Email="admin@example.com" \
  Admin__Bootstrap__Password="<a strong password>"
```

No PowerShell ou no cmd do Windows, coloque as configurações num arquivo JSON e passe
`--settings @params.json`: uma chave de API do Signer contém um `|`, que o shell interpretaria como
pipe.

| Configuração | Por quê |
|---|---|
| `WEBSITES_PORT=8080` | O container escuta na porta 8080. O App Service detecta 80 e 8080 automaticamente; definir a porta elimina a ambiguidade. |
| `WEBSITES_CONTAINER_START_TIME_LIMIT=1800` | **A configuração de migração de schema deste cenário.** O Servidor faz as migrações antes de começar a escutar, e o App Service encerra um container que não respondeu na porta dentro desse número de segundos. O padrão é 230, então uma atualização com uma criação de índice demorada é encerrada, reiniciada e encerrada de novo, num loop que não informa nada. 1800 é o máximo da plataforma e não custa nada quando as migrações são rápidas. |
| `ASPNETCORE_FORWARDEDHEADERS_ENABLED=true` | A plataforma termina o TLS nos front-ends dela. Sem isso, o Servidor vê HTTP simples vindo de um endereço do datacenter: os cookies perdem o `Secure`, as URLs absolutas saem com `http://`, e toda linha de auditoria registra o proxy em vez de quem fez a chamada. |
| `KeyRingProtector__Certificate` | **Obrigatória.** Veja abaixo como usar uma referência do Key Vault em vez de um app setting. |
| `ConnectionStrings__TrustBridge` | Identidade gerenciada, sem senha. |
| `Admin__Bootstrap__*` | Só na primeira inicialização. Remova depois de entrar. |

O `WEBSITES_ENABLE_APP_SERVICE_STORAGE` está ausente de propósito: esta implantação **não precisa de
armazenamento persistente**. Os dois chaveiros são linhas no Azure SQL, e o único diretório em que o
container grava é `/tmp`.

### Logs

**Ative o log do container**, porque uma linha de auditoria que o banco de dados não aceita é gravada
no Log do Servidor no nível `Critical`, e nada mais a guarda. A saída padrão (stdout) do container não
é retida por padrão no App Service Linux; `az webapp log tail` é um fluxo ao vivo, não um registro.

```bash
az webapp log config -g <rg> -n <app> --docker-container-logging filesystem
```

Isso é o mínimo: o sink no sistema de arquivos tem tamanho limitado e é rotativo. Uma implantação que
pretende manter a trilha envia os logs para o Application Insights ou para um workspace do Log
Analytics, com a retenção que escolheu. Quando um workspace é associado pelo portal, a variável
`APPLICATIONINSIGHTS_CONNECTION_STRING` é definida, e o Servidor envia para lá toda linha de log como
um trace com o id de correlação da requisição, sem nenhuma outra configuração.
`Logging__ApplicationInsights__ConnectionString` é o mesmo valor, na chave do próprio Servidor, para
um workspace associado de outra forma.

### Fixe a plataforma

```bash
az webapp update -g <rg> -n <app> --client-affinity-enabled true --https-only true
az monitor autoscale list -g <rg> -o table                       # then delete any that exist
az webapp config set -g <rg> -n <app> --always-on true \
  --generic-configurations '{"healthCheckPath": "/"}'
```

**`--https-only true`** é uma decisão que o Servidor deixa de propósito para a implantação: nada nele
redireciona HTTP para HTTPS, porque um Servidor on-premises costuma ser acessado por HTTP simples atrás
do proxy do próprio cliente. Aqui os dois esquemas chegam ao aplicativo, então, sem isso, um console
aberto em `http://` recebe um cookie de sessão sem o flag `Secure`.

**O Health check fica em `/`, e o probe de verdade vai para o seu monitoramento.** Com uma única
instância, o Health check não é um recurso de balanceamento de carga, e sim um **gatilho de reinício**:
sem para onde redirecionar o tráfego, o App Service substitui a instância depois de uma hora de pings
com falha. `/` responde 200 mesmo sem o banco de dados, então só falha quando o container está morto,
que é a única falha que um reinício resolve. `/healthz` *pode* falhar por conta própria (Azure SQL fora
do ar), e ligá-lo ao Health check resultaria num reinício a cada hora que não resolve nada e ainda
interrompe as sessões vinculadas ao cofre a cada vez. Aponte um **teste de disponibilidade** para
`/healthz`; ele é anônimo e não auditado, então consultá-lo a cada trinta segundos não custa nada.

`--health-check-path` não é um parâmetro de `az webapp config set`; o caminho é uma propriedade de
site-config, e é para isso que serve o `--generic-configurations`. No PowerShell ou no cmd, coloque o
JSON num arquivo e passe `"@params.json"`.

### O Protetor do Chaveiro

Os comandos para gerá-lo estão na [página de instalação](https://docs.lacunasoftware.com/articles/trustbridge/server-installation#o-protetor-do-chaveiro)
e são os mesmos aqui. Há dois lugares para colocá-lo, e o segundo é melhor:

- **Um app setting**, como mostrado acima.
- **Uma referência do Key Vault**: armazene o base64 como um secret, dê à identidade gerenciada do
  aplicativo a permissão `get` sobre ele e defina
  `KeyRingProtector__Certificate=@Microsoft.KeyVault(SecretUri=https://<vault>.vault.azure.net/secrets/<name>/)`.

**Guarde o próprio `.pfx` permanentemente num lugar que nem este aplicativo nem esta assinatura do
Azure consigam acessar.** Uma referência do Key Vault é uma casa conveniente para o Servidor em
execução, mas não é um backup: um Key Vault que o mesmo operador pode excluir não é uma cópia guardada
em outro lugar.

### Certificados do Cofre neste cenário

```powershell
az webapp config appsettings set -g $rg -n $app --settings `
  Vault__Enabled=true `
  Certificates__Validation__License=$([Convert]::ToBase64String([IO.File]::ReadAllBytes("LacunaPkiLicense.config")))
```

A licença é o mesmo tipo de artefato que o protetor, com data de validade e comprada por alguém, então
uma referência do Key Vault também serve para ela; renová-la passa a ser uma nova versão do secret e
um reinício.

Uma ressalva é específica deste cenário. **O App Service reinicia containers para a própria
manutenção**, e uma sessão vinculada a um Certificado do Cofre não sobrevive a um reinício: ela termina
como `Stranded`, e o cliente cria uma nova. Nada se perde permanentemente, e as sessões vinculadas a
estações de trabalho não são afetadas, mas uma cadência de reinícios que não é sua fica visível para a
assinatura com o cofre.

## 7. O schema

Não há nada a fazer se você concedeu o `db_ddladmin` no passo 5. O banco de dados em si precisa já
existir, e existe: o passo 2 o criou. Diferentemente do cenário com Docker, aqui o Servidor não
consegue criar o banco.

Para aplicar as migrações você mesmo, defina `Database__MigrateOnStart=false` e execute, numa máquina
com o .NET SDK e um checkout na versão implantada:

```bash
dotnet tool restore
dotnet ef database update --project src/TrustBridge.Server \
  --connection "Server=tcp:<sqlserver>.database.windows.net,1433;Database=TrustBridge;Authentication=Active Directory Default;Encrypt=True;Command Timeout=3600"
```

Repita antes de cada atualização. O `Command Timeout=3600` está ali de propósito.

## 8. Verifique

```bash
az webapp log tail -g <rg> -n <app>
```

Procure a linha `KeyRingPosture` com o thumbprint do protetor, a linha da postura do cofre e
`Application started`. Uma inicialização a frio leva cerca de um minuto até `/healthz` responder.
Depois:

```bash
curl -s https://<app>.azurewebsites.net/healthz     # Healthy
```

Em seguida, entre em `https://<app>.azurewebsites.net/admin`, defina uma senha de verdade, faça a
inscrição do segundo fator e remova as duas configurações `Admin__Bootstrap__*`.

## Operação

- **Domínio e certificado personalizados.** As estações de trabalho se conectam ao nome de host que
  você informar, e a cadeia precisa ser uma em que elas confiem. Um certificado gerenciado do App
  Service num domínio personalizado é a resposta simples; `*.azurewebsites.net` também funciona, mas
  amarra a configuração da frota a um nome de host do Azure.
- **Reinícios custam mais aqui.** Deixe `DOCKER_ENABLE_CI` desligado e faça deploy de forma
  deliberada. Mantenha o Health check num caminho que não possa falhar por conta própria.
- **Nunca faça scale-out.**
