Skip to main content
Draftable does not recommend this deployment. We recommend you do not use it.This is a “how to make it work” guide, not a “this works well” guide.Draftable API Self-Hosted v3 can be deployed on Azure Kubernetes Service, and we have verified that it works end to end. But it can only do so on network file share storage, which is not a supported configuration on any platform. We do not test against it, we do not optimise for it, and we cannot support problems arising from it.Please choose a different platform if you possibly can. See Choosing a different platform — on Azure, a Linux virtual machine running Docker Compose avoids every limitation described here, and on Kubernetes, AWS EKS with S3 is fully supported.This guide exists so that customers with a genuine constraint — a Microsoft-only mandate combined with a Kubernetes mandate — are not left guessing. It is not an endorsement, and following it successfully does not make this a supported configuration.Improving Azure storage support is under active consideration. Until something changes, the constraints described here are inherent to this platform combination and cannot be configured away.

Why we do not recommend this platform

Two limitations sit behind that warning. Neither is a bug, and neither can be worked around with configuration.

Draftable has no Azure Blob Storage backend

API Self-Hosted supports exactly two file storage backends: local filesystem and Amazon S3. Azure Blob Storage is not supported, and there is no way to point Draftable’s S3 client at a non-AWS, S3-compatible endpoint. The practical consequence is that on AKS you must use FILE_STORAGE_TYPE=local backed by a shared network file share, because the web, Celery worker, Celery beat and compare services all read and write the same files at the same time, from potentially different nodes. Object storage is what this product is designed around on Kubernetes. A network file share works, but it is emulating a local disk over a network, and that carries a measurable cost described in Storage performance.

Azure has no managed RabbitMQ

Draftable requires AMQP 0-9-1. Azure Service Bus implements AMQP 1.0 and is therefore not compatible, and Azure offers no managed RabbitMQ service of any kind. On AWS you can use Amazon MQ for RabbitMQ and keep your message broker outside the cluster. On Azure you must run RabbitMQ yourself, either inside the cluster or on a dedicated virtual machine. That is a genuine reduction in resilience compared with our AWS reference architecture, and it means you own the broker’s availability and backups.
PostgreSQL and Redis have good managed equivalents on Azure (Azure Database for PostgreSQL Flexible Server and Azure Cache for Redis) and you should use them. RabbitMQ is the exception.

Choosing a different platform

In rough order of how well we support them:

Docker Compose on a Linux VM

Best supported option on Azure. Run an Azure Linux virtual machine with Docker Compose. Local storage is genuinely local, so none of the file share overhead applies.

AWS EKS with S3

The fully supported Kubernetes option. If your organisation can use AWS, this is the architecture the product is designed for, and the only Kubernetes storage configuration we support.

Generic Kubernetes

Platform-agnostic manifests and configuration reference. This article builds on it — start there for the base manifests.

Azure AKS

Not recommended. This article. Functional and verified, but on unsupported storage. Choose it only if the three options above are genuinely unavailable to you.
If you are on AKS purely for compute standardisation and are not bound by data residency requirements, there is one further option worth knowing about: AKS with an Amazon S3 bucket. This uses the fully supported S3 code path, and nothing prevents pods in Azure from reaching S3. It means your documents are stored in AWS, which is unacceptable for many organisations, but if it is acceptable for yours it avoids every limitation in this guide.

What we tested, and what we did not

We would rather you knew the limits of our testing than assumed more than we can support. Verified working:
  • AKS 1.35, two Standard_D2s_v5 nodes, australiaeast
  • Azure Files Premium with the NFS v4.1 protocol as the shared ReadWriteMany volume
  • API Self-Hosted v3 using the latest image tags as of 3 August 2026 (apish-web 3.0.8 was current at that date), with in-cluster PostgreSQL 16, RabbitMQ 3.13.3 and Redis 8
  • Comparisons of Word and PDF documents completing end to end via the API
  • The comparison results viewer rendering correctly in a browser
  • Public HTTPS via ingress-nginx and a Let’s Encrypt certificate
Not tested, and therefore not characterised:
  • Sustained load over hours or days. Our testing covered a single session.
  • Azure NetApp Files, which may perform better than Azure Files and which some customers prefer for exactly this kind of workload.
  • Azure Files Premium with SMB for full comparison workloads. We measured its raw file operations, which were worse than NFS, and did not proceed further.
  • Multi-region, private-endpoint-only, or restricted-egress network topologies.
  • Horizontal scaling beyond a single replica of each service.
Network file share storage is not a supported configuration. Draftable does not test or optimise against NFS or SMB, and we cannot provide support for issues arising from network file share storage — including performance, file locking, and mount behaviour. This is a standing position, not specific to Azure.Draftable Support can help you with Draftable’s own configuration on AKS: environment variables, licensing, container settings, and the Azure-specific issues documented in this article. We cannot troubleshoot Azure Files performance or your cluster’s networking and storage configuration.If a problem is traced to file share performance or behaviour, the resolutions available to you are to change storage tier or to change platform. We will tell you that plainly rather than iterate on it.If you need a fully supported Kubernetes deployment, that is AWS EKS with S3 storage.

Storage performance

This is the part we most want you to understand before committing. We measured raw file operations on a live AKS cluster, comparing the node’s local disk against both Azure Files Premium protocols. Bulk throughput is not the problem. Sequential write bandwidth on Azure Files Premium measured around 226 MiB/s, comfortably faster than the node’s own OS disk. If you benchmark this deployment with large files, you will conclude that storage is fast, and you will be measuring the wrong thing. Per-file overhead is the problem. Creating a single file costs roughly 10 ms on NFS and 17 ms on SMB, against microseconds on a local disk. Draftable’s workload is full of small file operations: a .docx is a ZIP archive of many small parts, and comparing two documents means unpacking all of those parts, writing page renderings, scroll maps and change data, then cleaning up.

What that means in practice

The cost scales with the number of files a comparison touches, not with document size. That is why the deployment is viable at all, rather than a reason to choose it: Both part counts are measured, not estimated. The 5.8 second figure held to within 0.2 seconds across every run of our final clean test pass.
Comparison times above were measured on a deliberately small two-node test cluster (4 vCPU total) and are not a performance prediction for your environment. They are included only to show that ordinary documents remain workable. Your times will differ.
So:
  • Ordinary business documents — tens of internal parts — remain workable. Every comparison still pays overhead that a supported deployment would not, but it is unlikely to be the thing you notice.
  • Documents with very large numbers of embedded objects — hundreds or thousands of images or attachments — will feel it, and the cost grows linearly with part count.
  • High comparison concurrency multiplies file operations against a single shared file share, which becomes the bottleneck before CPU does.
None of this overhead exists in a supported deployment. On a Linux VM with Docker Compose, storage is genuinely local. On AWS EKS with S3, the product uses object storage as designed and hands large files to browsers directly rather than pulling them through a file share.Every millisecond described in this section is a cost you are choosing to take on, and it buys you nothing except the ability to run on AKS. If that constraint is real, proceed. If it is a preference, we would strongly encourage you to reconsider.

Always choose NFS, never SMB

SMB is the default protocol for Azure Files. Do not use it. Our measurements show SMB is around 1.6 times slower than NFS at creating files and roughly twice as slow at deleting them, and Draftable is a Linux application that expects POSIX filesystem behaviour, which NFS provides more faithfully. NFS requires the Premium tier. Standard Azure Files does not offer NFS.

Prerequisites

Start from the generic Kubernetes Deployment Guide for base manifests, environment variables and the overall component architecture. This article covers only what differs on Azure. Tools: Azure CLI, kubectl, and Helm. Azure prerequisites:
  • A subscription with quota for at least 8 vCPU in your chosen region for a production-shaped cluster. Check both your quota and your subscription’s permitted VM SKU list, which are separate restrictions — a size can be blocked even when quota is available.
  • Permission to create role assignments, not just resources. Contributor alone is insufficient; you need Owner, or Contributor plus User Access Administrator.
  • A Draftable product key, and outbound HTTPS access to draftable.com for licence activation.
Avoid B-series burstable VM sizes. Their CPU credit model causes comparison times to degrade unpredictably once credits are exhausted, which is very difficult to diagnose after the fact.

Azure-specific configuration

Six things differ from a generic Kubernetes deployment. The first will stop your deployment working entirely and is very hard to diagnose, so start there.

1. Load balancer health probes

This will silently break your deployment and the symptom points you in the wrong direction.
The ingress-nginx Helm chart sets appProtocol: http on the controller’s Service ports. The Azure cloud provider reads that and creates an HTTP health probe against / rather than a TCP probe. Other ingress controllers may behave the same way, so check yours if you are not using ingress-nginx. Your ingress controller answers / with a 404 when no ingress rule matches the probe’s request, because the probe sends no Host header. Azure load balancer health probes treat only HTTP 200 as healthy. Every node is therefore marked unhealthy and the load balancer silently discards all inbound traffic. The symptom is a connection timeout from the internet while the cluster is completely healthy internally. If you are using Let’s Encrypt, certificate issuance fails with Timeout during connect (likely firewall problem), which naturally sends you to inspect network security groups. The network security group is not the problem. Fix it with an annotation on the ingress controller Service:
/healthz is served on the controller’s data ports and returns 200. To confirm what your probe is actually configured to check:

2. The shared NFS StorageClass

nconnect=4 opens multiple TCP connections to the share and is the single most useful tuning option for this workload. actimeo=30 caches attribute lookups and reduces round trips considerably; if you see unexpected file consistency behaviour between services, reduce it. The minimum provisioned size for an Azure Files Premium share is 100 GiB, which is a billing floor rather than a capacity recommendation.
Mount this at /srv/draftable in the web, Celery worker, Celery beat and compare pods, and set:

3. Create the storage subdirectories before the compare service starts

The compare service watches /srv/draftable/config for licence changes and fails to start if that directory does not exist. A freshly provisioned file share is empty, and nothing creates it for you. On Docker Compose the directory is present inside the container image, so this problem is unique to a new network share. Add an init container to the compare deployment:

4. Compare service memory

Allow at least 4 GiB for the compare service. We observed the container being terminated by the kernel (OOMKilled) with a 2 GiB limit, and once at 3 GiB, while comparing a 120-page PDF. Note that 4 GiB is a headroom recommendation based on those observations — we did not validate it under load.
When the compare service is terminated, every comparison it was processing is lost. Callers see either a comparison that never completes or an HTTP 500 on their next status request. This looks exactly like a storage or network fault, so check for it first:

5. Database connection limits

Draftable holds persistent database connections by default: DB_CONN_MAX_AGE defaults to -1, which means connections are kept open indefinitely, and each worker process maintains its own. Under sustained API load, connection count grows and a database configured with too few connections will start refusing them. The API returns HTTP 500 and the logs show:
On Azure Database for PostgreSQL Flexible Server, max_connections is determined by the SKU you choose. Per Microsoft’s documentation a B1ms allows roughly 50 connections, which Draftable can exhaust. Size your database SKU by connection count, not only by CPU and memory.We encountered this on an in-cluster PostgreSQL instance rather than on Flexible Server, which we did not test. Verify the connection limit for whichever SKU you select.
If a larger SKU is not an option, set DB_CONN_MAX_AGE to a bounded value such as 300 to recycle connections instead of holding them open indefinitely. To check current usage:

6. Converter health probes

This applies to all Kubernetes platforms, but it is worth repeating because it produces a dramatic failure. The document converter must use TCP probes, not HTTP. Its API returns 404 on every health endpoint, and LibreOffice takes 60 to 90 seconds to initialise, so HTTP probes fail permanently and the pod restarts hundreds of times.
Office process died with exit code 81 in the converter log during startup is expected and not an error.

DNS and TLS

For a publicly reachable hostname without configuring your own DNS, Azure can assign a label to the load balancer’s public IP address, giving you <label>.<region>.cloudapp.azure.com:
That hostname resolves publicly, which is sufficient for Let’s Encrypt HTTP-01 validation via cert-manager. For production, point a CNAME for your own domain at the load balancer address and either use cert-manager or supply your own certificate as a TLS secret. Set APP_BASE_URL and CSRF_TRUSTED_ORIGINS to the final HTTPS URL, and keep ALLOWED_HOSTS broad enough to accept the load balancer’s IP-based health checks.

Verification

Confirm the licence actually activated. The initialisation job completes successfully even when activation fails, so a completed job is not proof:
Then confirm all pods are running without restarts, and submit a comparison through the API. Files should appear beneath /srv/draftable/data on the shared volume.
Draftable stores the activated licence in Redis. A copy is written to /srv/draftable/config/license.json only when that directory already exists, so on a newly provisioned file share you should not expect to find it — do not treat its absence as a failed activation.Redis persistence is therefore mandatory, not optional. If Redis loses its data, users are locked out until the initialisation job is re-run to reactivate the licence.

Troubleshooting

Almost always the load balancer health probe issue described in Load balancer health probes. Confirm by testing the ingress controller from inside the cluster — if that works, the fault is between the internet and the load balancer, not in your cluster.
Check these in order before suspecting storage:
  1. Compare service terminated for memory — see Compare service memory.
  2. Database connections exhausted — see Database connection limits.
  3. Only then consider file share throughput.
Memory exhaustion, connection exhaustion and file share contention all produce the same symptom: intermittent, load-dependent failures that will not reproduce on demand. They are easy to mistake for one another.
Confirm your share uses NFS, not SMB, and that it is on the Premium tier. Check nconnect=4 is present in your mount options. If your documents contain very large numbers of embedded objects, the per-file overhead described in Storage performance may be inherent to this platform rather than a misconfiguration.
Confirm the PersistentVolumeClaim is Bound and reports RWX access mode. Premium file shares must be at least 100 GiB, and NFS is unavailable on the Standard tier — requesting NFS with skuName: Standard_LRS will fail to provision.
Check that /srv/draftable/config exists on the shared volume. See Create the storage subdirectories.
Your subscription’s permitted SKU list is separate from your quota, and a size can be rejected even when quota shows capacity. The error message lists every permitted size for your subscription and region.

Getting help

Contact support@draftable.com with pod logs, pod events, your sanitised configuration, and your storage tier and protocol. Please tell us you are on AKS with a network file share, as it changes how we approach diagnosis. And please read the support boundaries in What we tested first, so our answer is not a surprise.