Skip to main content

ยท 4 min read
Paul Greenberg

This tutorial walks you through deploying applications protected by caddy security app, hosted by Caddy web server via Azure Container Instances (ACI) service.

Table of Contentsโ€‹

Azure Configurationโ€‹

Define environment variables. Change app to something else, e.g. xyz.

export ACI_RG_NAME=app-auth-rg-001
export ACI_ST_ACCOUNT_NAME=appauthst001
export ACI_LOCATION=eastus
export ACI_ST_SHARE_NAME=appauthstsh001

Next, create resource group:

az group create --name "${ACI_RG_NAME}" --location eastus

Add Azure File Shareโ€‹


Register a provider using your subscription ID:

az provider register -n Microsoft.Storage --subscription 91cd6b60-64b7-46e2-bffb-952352196549

Create a storage account:

az storage account create --resource-group "${ACI_RG_NAME}" --name "${ACI_ST_ACCOUNT_NAME}" --location "${ACI_LOCATION}" --sku Standard_LRS

Review the newly created account:

az storage account list | jq -r '.[]

Create a file share:

az storage share create --name "${ACI_ST_SHARE_NAME}" --account-name "${ACI_ST_ACCOUNT_NAME}"

Copy Files to Azure File Shareโ€‹

Navigate to Resource Group, then browse to Storage Share associated with it.


Next, open the share, click "Browse" and add the following nested directories. The app-auth-ci-001 is the name of the Caddy instance (ACI container).

  • caddy,
    • app-auth-ci-001
      • config


Next, browse to caddy/app-auth-ci-001/config and upload Caddyfile with the following content.


http_port 80
https_port 443
admin off

order authenticate before respond
order authorize before basicauth

security {
local identity store localdb {
realm local
path {env.LOCAL_DATA_PATH}caddy/app-auth-ci-001/userdb/users.json
user webadmin {
name Webmaster
email webadmin@localhost.localdomain
# echo -n "App@2f4cb79053be" | bcrypt-cli -c 10
password "$2a$10$JqJEf2pra4hIkw4CSDePoOfIoXVUFwSn6pJie6m02MUP7YGQVPQAi" overwrite
roles "authp/admin" "authp/user"
user jsmith {
name John Smith
email jsmith@localhost.localdomain
# password {env.USER_JSMITH_SECRET}
password "App@2f4cb79053be"
roles "authp/admin" "authp/user"

authentication portal myportal {
crypto default token lifetime 7200
crypto key sign-verify {env.JWT_SHARED_KEY}
cookie domain
ui {
links {
"My Identity" "/auth/whoami" icon "las la-user"
"File Server" "/" icon "las la-cloud"
transform user {
match origin local
action add role authp/user
enable identity store localdb

authorization policy file_server_policy {
crypto key sign-verify {env.JWT_SHARED_KEY}
set auth url /auth/
allow roles "authp/admin" "authp/user"

:80 {
redir https://{host}{uri} 302

:443 {
tls internal {

route /version* {
respond "app 1.0"

route /debug* {
authorize with file_server_policy
header Content-Type text/html
respond <<EOF
<p>Host: <code>{}</code></p>
<p>Time: <code>{}</code></p>
<p>ID: <code>{}</code></p>
EOF 200

route /auth* {
authenticate with myportal

route /* {
authorize with file_server_policy
file_server {
root {env.LOCAL_DATA_PATH}

Container Deploymentโ€‹

Get Storage Account Key:

ACI_ST_ACCOUNT_KEY=$(az storage account keys list --resource-group "${ACI_RG_NAME}" --account-name "${ACI_ST_ACCOUNT_NAME}" --query "[0].value" --output tsv)

Next, deploy the container:

az container create --resource-group "${ACI_RG_NAME}" \
--name "app-auth-ci-001" \
--image "" \
--dns-name-label "app-auth-ci-001" \
--ports 80 443 \
--azure-file-volume-account-name "${ACI_ST_ACCOUNT_NAME}" \
--azure-file-volume-account-key "${ACI_ST_ACCOUNT_KEY}" \
--azure-file-volume-share-name "${ACI_ST_SHARE_NAME}" \
--environment-variables LOCAL_DATA_PATH="/app/data/" JWT_SHARED_KEY="d24ae7de-ca54-4334-94ba-301fc414d5de" XDG_DATA_HOME="/app/data/caddy/app-auth-ci-001/data" XDG_CONFIG_HOME="/app/data/caddy/app-auth-ci-001/config" \
--azure-file-volume-mount-path /app/data/ \
--command-line "caddy run --config /app/data/caddy/app-auth-ci-001/config/Caddyfile"

Accessing Containerโ€‹

Browse to container URL:

The first time your access your container, you get that the site is unreachable.


If you refresh after 10-15 seconds, you will get the connection is not private. It is expected. Trust the cert and proceed


After that, log in with username jsmith and password App@2f4cb79053be.


Click "File Server" to get redirected to Caddy file server browser:


You should be able to browse the contents of /app/data directory.



List containers:

az container list --resource-group "${ACI_RG_NAME}"

Check logs:

az container logs --resource-group "${ACI_RG_NAME}" --name "app-auth-ci-001"
az container show --resource-group "${ACI_RG_NAME}" --name "app-auth-ci-001"


There are some gaps in my explanation. Please engage, ask questions here or submit edits here.

ยท One min read
Paul Greenberg

Please share your stories about Caddy and auth plugins!

Simply add Markdown files (or folders) to the blog/ directory.

Additionally, add yourself to blog authors via authors.yml.

If your blog post has images, then create a folder for the post and co-locate images with the post itself.

  • 2021-10-31-mypost/
  • 2021-10-31-mypost/image1.jpeg

Include the image in the .md file with the following snippet:

![Image for my post](./image1.jpeg)

The blog post date can be extracted from filenames, such as:

  • 2021-10-31-welcome/