Azure Container Apps¶
This guide shows how to deploy xlwings Lite to Azure Container Apps.
Deploying the Container¶
+ Create > + Container App
Select the desired Resource group (you may need to click on Create new resource group first)
Container app name: e.g.
xlwings-liteDeployment source: Container image
Select the desired Region
Select the desired Container Apps environment (you may need to click on Create new environment first)
Click on Next: Container
Image source: Docker Hub or other registries
Image type: Public
Registry login server:
docker.ioImage and tag:
xlwings/xlwings-lite:1.0.0.0-77CPU and memory:
0.5 CPU cores, 1 Gi memoryEnvironment variables:
XLWINGS_LICENSE_KEY:your-license-key(if you don’t have one, get a xlwings trial license key)
Click on Next: Ingress
Activate the checkbox for Ingress
Under Ingress traffic, select Accepting traffic from anywhere
Set Target port to
8000
Click on Next: Tags
Click on Next: Review + create
Click on Create
You can optionally restrict access under Networking > IP Restrictions.
You’ll find the Application Url under the Overview menu item of your container app. Under Application > Containers activate the Environment variables tab, then add the Application Url without the leading
https://, it should look something like this:XLWINGS_HOSTNAME:<name>.<random>.<location>.azurecontainerapps.ioUnder Application > Scale (or Scale and replicas), set both Min replicas and Max replicas to
1to prevent cold starts. If desired, you could set a Scale rule to set Min replicas to0outside business hours.Go to your Application Url. You should see the version of xlwings Lite.
Hosting Pyodide separately (for production)¶
The xlwings Lite container ships with only the default version of the Pyodide distribution. When you upgrade the container, that version may change — and any workbook pinned to a previous Pyodide version will need to upgrade to the new Pyodide version.
In order to prevent this, you have to host the Pyodide distribution on Azure Blob Storage and point xlwings Lite at it. That way you can keep multiple Pyodide versions available indefinitely, and container upgrades become safe.
1. Create the storage account¶
A ready-made Bicep template creates a Storage account and a blob container (Azure’s equivalent of a storage bucket) with public read access and CORS configured for Pyodide.
Download pyodide-hosting.bicep.
Open Azure Cloud Shell (or the shell icon at the top of the portal), choosing the Bash environment, and upload the file (Manage files > Upload).
Set the variables used by the commands below. Use the resource group of your container app, and pick a globally-unique storage account name (3–24 lowercase letters and numbers):
export RESOURCE_GROUP_NAME="xlwings-lite-rg" export STORAGE_ACCOUNT="<storage-account-name>" export BLOB_CONTAINER="pyodide"
Note
If you don’t know your resource group name, list them with
az group list --query "[].name" -o tsv.Deploy the template:
az deployment group create \ --resource-group $RESOURCE_GROUP_NAME \ --template-file pyodide-hosting.bicep \ --parameters storageAccountName=$STORAGE_ACCOUNT
Note
To limit read access to your corporate network, pass a list of allowed IP ranges. Only do this if every user’s browser reaches Azure from a known IP range (corporate VPN / office NAT):
az deployment group create \ --resource-group $RESOURCE_GROUP_NAME \ --template-file pyodide-hosting.bicep \ --parameters storageAccountName=$STORAGE_ACCOUNT \ allowedIpRanges="['203.0.113.0/24','198.51.100.0/22']"
Next, run the following command to print the
pyodideBaseUrl(we will use it below under Step 3):az deployment group show \ --resource-group $RESOURCE_GROUP_NAME \ --name pyodide-hosting \ --query properties.outputs.pyodideBaseUrl.value -o tsv
2. Upload a Pyodide release¶
In the same Cloud Shell (using the variables set above), paste the following block. This downloads a Pyodide release and uploads it to a versioned path in the blob container:
VERSION=0.27.5
curl -fL https://github.com/pyodide/pyodide/releases/download/${VERSION}/pyodide-${VERSION}.tar.bz2 | tar -xj
az storage blob upload-batch \
--account-name $STORAGE_ACCOUNT \
--destination $BLOB_CONTAINER \
--destination-path v${VERSION}/full \
--source pyodide \
--auth-mode login \
--overwrite
rm -rf pyodide/
This might take a few minutes.
To keep additional Pyodide versions available, repeat this step with VERSION set to the desired version. Currently supported versions are:
0.27.5
3. Point xlwings Lite at the storage URL¶
In your container app, add an environment variable (Application > Containers > Environment variables):
Key:
XLWINGS_PYODIDE_BASE_URLValue: the
pyodideBaseUrlfrom step 1 (e.g.https://<account>.blob.core.windows.net/pyodide/)
Important
The value must end with a / (needed for the Content-Security-Policy). Without it, the browser blocks pyodide.mjs with a CSP error.
After the container app redeploys, xlwings Lite will load Pyodide from Blob Storage instead of the bundled copy.
Connecting to Azure Artifacts (for production)¶
By default, xlwings Lite installs packages from the public PyPI. If your organization keeps its Python packages in an Azure Artifacts feed, you can point xlwings Lite at it instead by setting XLWINGS_PYPI_INDEX_URL. The container reverse-proxies the feed on the same origin (under /pypi/), so the browser never sees the feed’s credentials and there are no CORS or CSP exceptions to manage.
There are two ways to authenticate the container to the feed:
Managed identity (recommended): no secret is stored anywhere. The container fetches a short-lived Microsoft Entra token from the Azure platform at runtime. This is the cleaner option and the one documented in full below.
Personal Access Token (PAT): a token embedded in the index URL. Simpler to set up, but the token expires and must be rotated. See Alternative: Personal Access Token (PAT).
Note
This section assumes you already have a working deployment from the steps above. The feed URL is the same one you would pass to pip install --index-url, e.g. https://pkgs.dev.azure.com/<org>/<project>/_packaging/<feed>/pypi/simple/ (organization-scoped feeds omit the /<project> segment).
Managed identity¶
1. Enable a system-assigned managed identity¶
Go to your container app, then Settings > Security > Identity.
Note
In older portal layouts the Identity blade lives directly under Settings rather than under Security. If you can’t find it, use the search box at the top of the left-hand menu and type
Identity.On the System assigned tab, switch Status to On and click Save.
Note the Object (principal) ID that appears — you’ll need it in the next step.
2. Grant the identity access to the feed¶
The managed identity is a Microsoft Entra service principal. Before you can grant it feed permissions, it usually has to be added to the Azure DevOps organization first.
In Azure DevOps, go to Organization settings > Users > Add users.
Set the user type to Service Principal, search by the Object (principal) ID (or the identity’s Application/Client ID), and assign an access level (e.g. Basic or Stakeholder). Click Add.
Note
This step is easy to miss. A brand-new managed identity often does not show up by name in the feed’s permissions dialog until it has been added to the organization here.
Now open your feed > Feed settings (gear icon) > Permissions > Add users/groups, find the identity (search by name or by the Object ID), and assign the role Collaborator (shown as Feed and Upstream Reader (Collaborator)).
Warning
Use Collaborator, not Reader. Reader can only serve packages that are already cached in the feed. Installing a package that isn’t cached yet triggers an ingestion from the feed’s upstream source (e.g. public PyPI), which requires the Collaborator role or higher. With only Reader, uncached packages fail with a misleading
Cannot find the package ... in feed(PackageNotFound) error.
3. Configure the environment variables¶
Go to Application > Containers > Environment variables (Edit and deploy) and add:
XLWINGS_PYPI_INDEX_URL: your feed URL, e.g.https://pkgs.dev.azure.com/<org>/_packaging/<feed>/pypi/simple/(no token)XLWINGS_PYPI_AUTH_MODE:azure-managed-identity
Saving creates a new revision and restarts the container.
Note
Enable the managed identity (step 1) before setting these variables. The platform only injects the identity token endpoint into the container once an identity is assigned, and xlwings Lite reads it at startup. If you set the variables first, restart the revision after enabling the identity.
4. Verify¶
Open https://<your-app-url>/pypi/simple/<some-package>/ in a browser (or with curl). A 200 response listing the package’s files (as an HTML page of links, or as JSON if you request it with Accept: application/vnd.pypi.simple.v1+json) means the feed connection and authentication are working. You can then add the package to your requirements.txt in the add-in.
If you get a 401/403, check the identity’s feed permissions (step 2). If a never-installed package returns a 404 with PackageNotFound, the identity likely has Reader instead of Collaborator. The container logs (Monitoring > Log stream, or az containerapp logs show) report token-fetch failures explicitly.
Alternative: Personal Access Token (PAT)¶
If you can’t use a managed identity, embed a PAT in the feed URL instead. Create a PAT in Azure DevOps (User settings > Personal access tokens) with the Packaging: Read scope, then set a single environment variable:
XLWINGS_PYPI_INDEX_URL:https://<PAT>@pkgs.dev.azure.com/<org>/_packaging/<feed>/pypi/simple/
No XLWINGS_PYPI_AUTH_MODE is needed (it defaults to PAT/basic authentication). The proxy forwards the token to the feed and strips it from everything the browser sees.
Note
As with managed identity, installing an uncached package triggers upstream ingestion, so the PAT (or the account it belongs to) needs permission to save packages from upstream sources. Prefer a token from a dedicated service account over a personal one, and rotate it before it expires.
Register the add-in with Microsoft 365 admin center¶
In your browser, go to
https://<your-hostname>/manifest, which will downloadxlwings-lite-manifest.xml.Go to Microsoft 365 admin center
Click on
Show all>Settings>Integrated Apps.If you have xlwings Lite installed, uninstall it first.
Click on
Upload custom appsand selectOffice Add-in(App type).Select
Upload manifest file (.xml) from device. ClickChoose File, then select thexlwings-lite-manifest.xmlfrom the previous step.Click
Next, then assign the desired users.Click
Nextand accept permission requests.Click
NextandFinish deployment.
The users will get the add-in to show up automatically although it may take a few hours.
Note
If you want to remove the add-in again and run into issues (“Remove apps failed. No apps were successfully removed. Please try to remove them later.”), use this legacy URL: https://admin.microsoft.com/#/Settings/AddIns
Updating¶
To update xlwings Lite, point your container app at the new image tag.
Go to your container app, then Application > Containers.
Click Edit and deploy, select the container, and update the Image and tag to:
xlwings/xlwings-lite:1.0.0.0-77
Click Save to create a new revision and deploy it.
Note
Normally you don’t need to update the manifest after deploying a new version of the container. This would only be required, e.g., if the URL of your container changed. In that case, the Microsoft 365 admin center offers a link to Update Add-in.