Did a write up here: https://dev.to/kedzior_io/net-core-api-in-azure-container-instances-secured-with-https-using-caddy2-32jm
Here is the copy/paste:
- Create your Web API project let's call it
MyApp.Image.Api
and let's say it depends on another project MyApp.Core
- Add
Dockerfile
to your MyApp.Image.Api
project
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
ARG ENVIRONMENT
ENV ASPNETCORE_URLS http://*:5000
ENV ENVIRONMENT_NAME "${ENVIRONMENT}"
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
# copy project dependencies
COPY ["src/MyApp.Core/MyApp.Core.csproj", "src/MyApp.Core/"]
COPY ["src/MyApp.Image.Api/MyApp.Image.Api.csproj", "src/MyApp.Image.Api/"]
RUN dotnet restore "src/MyApp.Image.Api/MyApp.Image.Api.csproj"
COPY . .
RUN dotnet build "src/MyApp.Image.Api/MyApp.Image.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "src/MyApp.Image.Api/MyApp.Image.Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.Image.Api.dll"]
Next within the same project create directory called Proxy
in it you will need 4 files:
Caddyfile.development
- this will create proxy when running locally with self-signed certificate
{
email [email protected]
}
https://localhost {
reverse_proxy localhost:5000
}
Caddyfile.staging
{
email [email protected]
}
https://myapp-image-api-staging.eastus.azurecontainer.io {
reverse_proxy localhost:5000
}
Caddyfile.production
{
email [email protected]
}
https://myapp-image-api.eastus.azurecontainer.io {
reverse_proxy localhost:5000
}
Dockerfile
- we will set environment prior to executing this
FROM caddy:latest
ARG ENVIRONMENT
COPY "src/MyApp.Image.Api/Proxy/Caddyfile.${ENVIRONMENT}" /etc/caddy/Caddyfile
*if you don't have multiple environments you know what to skip :-)
- Now you need to add
docker-compose.yml
to the project
version: '3.4'
services:
proxy:
image: myapp-image-api-proxy:${CONTAINER_VERSION}-${ENVIRONMENT}
build:
context: ../../
args:
ENVIRONMENT: ${ENVIRONMENT}
dockerfile: src/MyApp.Image.Api/Proxy/Dockerfile
api:
image: myapp-image-api:${CONTAINER_VERSION}-${ENVIRONMENT}
depends_on:
- proxy
build:
context: ../../
args:
ENVIRONMENT: ${ENVIRONMENT}
dockerfile: src/MyApp.Image.Api/Dockerfile
You should end up with this structure:
- Now you are ready to go! I use that powershell script to build my images. If you are building to locally just run this:
# set azure environment
$env:ENVIRONMENT = 'development'
$env:CONTAINER_VERSION = 'latest'
# builds images
docker-compose build
If you want to build and push images to Azure Container Registry go for commands below but before make sure you have created registry:
az provider register --namespace Microsoft.ContainerInstance`
az acr create --resource-group EastUS--name myapp --sku Basic // *enable admin user in azure portal once Azure Container Registry is created
# logs to azure
az login
# set azure environment
$env:ENVIRONMENT = 'staging'
$env:CONTAINER_VERSION = 'latest'
# builds images
docker-compose build
# logs to Azure Container Registry
az acr login --name myapp
# tag images
docker tag myapp-image-api-proxy:latest-staging myapp.azurecr.io/myapp-image-api-proxy:latest-staging
docker tag myapp-image-api:latest-staging myapp.azurecr.io/myapp-image-api:latest-staging
# push images
docker push myapp.azurecr.io/myapp-image-api-proxy:latest-staging
docker push myapp.azurecr.io/myapp-image-api:latest-staging
# clean up
docker rmi myapp-image-api-proxy:latest-staging
docker rmi myapp-image-api:latest-staging
docker rmi myapp.azurecr.io/myapp-image-api-proxy:latest-staging
docker rmi myapp.azurecr.io/myapp-image-api:latest-staging
Cool! You are ready to fire it up!##
- You will need two packages:
Microsoft.Azure.Management.ContainerInstance.Fluent
Microsoft.Azure.Management.Fluent
- Let's start from connecting to Azure using C# Fluent API
private IAzure GetAzureContext()
{
Log.Information("[Container] Getting Service Principal");
var creds = new AzureCredentialsFactory().FromServicePrincipal(
_config["AppSettings:ImageApi:ServicePrincipalClientId"],
_config["AppSettings:ImageApi:ServicePrincipalSecretId"],
_config["AppSettings:ImageApi:ServicePrincipalTenat"],
AzureEnvironment.AzureGlobalCloud);
Log.Information("[Container] Getting subscribtion");
var azure = Microsoft.Azure.Management.Fluent.Azure.Authenticate(creds).WithSubscription(_config["AppSettings:ImageApi:SubscribtionId"]);
return azure;
}
Where do you get these service principal ids?
In Azure CLI do:
az ad sp create-for-rbac --name myapp-containers --sdk-auth > my.azureauth
That file will have all service principal data needed.
- Now the main part:
private string CreateContainer()
{
Log.Information("[Container] Authenticating with Azure");
var azureContext = GetAzureContext();`
// Get azure container registry for my resource group named 'EastUS' and registry 'myapp'
var azureRegistry = azureContext.ContainerRegistries.GetByResourceGroup(
_config["AppSettings:ImageApi:ResourceGroupName"],
_config["AppSettings:ImageApi:ContainerRegistryName"]);
var acrCredentials = azureRegistry.GetCredentials();
// Get container group for my resource group named 'EastUS' and container group i.e 'myapp-image-api-staging'
var containerGroup = azureContext.ContainerGroups.GetByResourceGroup(
_config["AppSettings:ImageApi:ResourceGroupName"],
_config["AppSettings:ImageApi:ContainerGroupName"]
);
if (containerGroup is null)
{
Log.Information("[Container] Creating with fluent API");
// ContainerGroupName = 'myapp-image-api-staging'
// ResourceGroupName = 'EastUS'
// VolumeName = 'image-api-volume'
// FileShare = 'containers'
// # yes you need to have storage account with file share, we need it so that proxy (caddy2) can store Let's Encrypt certs in there
// az storage share create --name myapp-staging-containers-share --account-name myappstaging
//
// StorageAccountName = 'myappstaging'
// StorageAccountKey = 'well-that-key-here'
// ProxyContainerName = 'image-api-proxy'
// ProxyImageName = 'myapp.azurecr.io/myapp-image-api-proxy:latest-staging'
// VolumeMountPath = '/data/'
// ApiContainerName 'image-api'
// ApiImageName 'myapp.azurecr.io/myapp-image-api:latest-staging'
containerGroup = azureContext.ContainerGroups.Define(_config["AppSettings:ImageApi:ContainerGroupName"])
.WithRegion(Region.USEast)
.WithExistingResourceGroup(_config["AppSettings:ImageApi:ResourceGroupName"])
.WithLinux()
.WithPrivateImageRegistry(azureRegistry.LoginServerUrl, acrCredentials.Username, acrCredentials.AccessKeys[AccessKeyType.Primary])
.DefineVolume(_config["AppSettings:ImageApi:VolumeName"])
.WithExistingReadWriteAzureFileShare(_config["AppSettings:ImageApi:FileShare"])
.WithStorageAccountName(_config["AppSettings:ImageApi:StorageAccountName"])
.WithStorageAccountKey(_config["AppSettings:ImageApi:StorageAccountKey"])
.Attach()
.DefineContainerInstance(_config["AppSettings:ImageApi:ProxyContainerName"])
.WithImage(_config["AppSettings:ImageApi:ProxyImageName"])
.WithExternalTcpPort(443)
.WithExternalTcpPort(80)
.WithCpuCoreCount(1.0)
.WithMemorySizeInGB(0.5)
.WithVolumeMountSetting(
_config["AppSettings:ImageApi:VolumeName"],
_config["AppSettings:ImageApi:VolumeMountPath"])
.Attach()
.DefineContainerInstance(_config["AppSettings:ImageApi:ApiContainerName"])
.WithImage(_config["AppSettings:ImageApi:ApiImageName"])
.WithExternalTcpPort(5000)
.WithCpuCoreCount(1.0)
.WithMemorySizeInGB(3.5)
.Attach()
.WithDnsPrefix(_config["AppSettings:ImageApi:ContainerGroupName"])
.WithRestartPolicy(ContainerGroupRestartPolicy.Always)
.Create();
}
Log.Information("[Container] created {fqdn}", containerGroup.Fqdn);
return containerGroup.Fqdn;
}
Containers are running!
It takes around 2:50 min to have them fully available.
Hitting fqdn
given in the code above gives me my swagger index page: