AWS EKS with S3 storage: If you are deploying on AWS EKS and wish to use S3 for file storage, refer to the dedicated AWS EKS Deployment Guide instead, which covers IRSA, S3 configuration, and ALB Ingress.
Prerequisites
Architecture
Configuration
Deploy
Verify
Troubleshooting
Prerequisites
Required tools
Required information
- A running Kubernetes cluster with
kubectlconfigured - Draftable container images available (either pulled from the Draftable registry or pre-loaded into your cluster)
- Draftable product license key
- Domain name for the application (if exposing externally)
Hardware requirements
Your cluster nodes should meet these minimum requirements:- CPU: 4 vCPU total available for Draftable workloads
- Memory: 8 GB RAM total available
- Storage: PersistentVolume provisioner available in your cluster
Architecture overview
Component architecture
Draftable API Self-Hosted consists of several microservices:Stateful vs stateless components
The application components (Web, Celery, Compare, Converter) are stateless and well-suited for Kubernetes. The infrastructure components (PostgreSQL, Redis, RabbitMQ) are stateful — the manifests below run them in-cluster for simplicity, but managed services are recommended for production.Shared storage requirement
When usingFILE_STORAGE_TYPE=local, the Web, Celery Worker, Celery Beat, and Compare containers must all have access to the same shared filesystem at /srv/draftable. In Kubernetes, this requires a ReadWriteMany (RWX) PersistentVolume — for example, NFS, CephFS, or a cloud-native file share.
Configuration
Namespace
Create a dedicated namespace for Draftable resources:Secrets
Create a Kubernetes Secret containing sensitive configuration. Replace the placeholder values with your own:ConfigMap
Create a ConfigMap with application configuration. Update the placeholder values for your environment:For a complete reference of all available environment variables, see the Docker Compose Guide.
Deploy Draftable
Shared storage
Create a PersistentVolumeClaim for the shared application data volume. This must use aReadWriteMany access mode so it can be mounted by multiple pods simultaneously:
Uncomment and set
storageClassName to match your cluster’s RWX-capable StorageClass (e.g. nfs, efs-sc, cephfs). If your cluster has a default RWX StorageClass, you can omit this field.Infrastructure services
PostgreSQL
PostgreSQL
RabbitMQ
RabbitMQ
Redis
Redis
Database migrations
Run the database migration job before deploying application services:Application services
Web
Web
Celery Worker
Celery Worker
Celery Beat
Celery Beat
Compare
Compare
The Compare service is a .NET application with different environment variable requirements than the Python-based web containers. It connects directly to RabbitMQ and Redis but does not connect to PostgreSQL.
Converter
Converter
During normal startup, you may see “Office process died with exit code 81” messages in the converter logs. This is expected behaviour while LibreOffice initialises and typically resolves within 90–120 seconds.
Ingress
Configure an Ingress to expose the web service externally. The example below uses a generic Ingress resource — adapt the annotations and TLS configuration for your Ingress controller (e.g. nginx-ingress, Traefik, HAProxy):To use TLS, create a Kubernetes Secret with your certificate:
Deployment order
Apply the manifests in the following order:1
Create namespace and configuration
2
Deploy infrastructure services
3
Run database migrations
4
Deploy application services
5
Deploy Ingress
Verification
Check pod status
Running status with ready containers:
Check services
Access the application
Once the Ingress is configured and DNS is pointing to your cluster, openhttps://draftable.yourcompany.com in your browser.
For testing without Ingress, use port-forwarding:
http://localhost:8000.
Troubleshooting
Pods stuck in Pending
Cause: PersistentVolumeClaims not binding.
Solution: Verify your cluster has a StorageClass that supports the required access modes:
draftable-data PVC, ensure your StorageClass supports ReadWriteMany.
CreateContainerConfigError
Cause: ConfigMap or Secret key not found.
Solution: Verify all ConfigMap and Secret keys exist:
Users locked out after Redis restart
Cause: Redis stores the activated license. If Redis data is lost (pod restart without persistent storage, PVC deletion, or cache flush), the license is no longer available and all users are locked out. Solution: Re-run theweb-init job to reactivate the license from the DRAFTABLE_PRODUCT_KEY environment variable:
REDIS_PORT parse error (invalid literal for int())
Cause: Kubernetes auto-creates REDIS_PORT=tcp://ip:port when a service is named redis.
Solution: The Redis service in these manifests is named redis-svc to avoid this conflict. Ensure you are using redis-svc as the service name.
Converter in CrashLoopBackOff
Cause: HTTP health probes fail because JODConverter returns 404 on all health endpoints, and LibreOffice takes 60–90 seconds to initialise.
Solution: Ensure the converter uses TCP socket probes, not HTTP probes. See the Converter manifest above.
DisallowedHost error in web logs
Cause: Requests arriving with a hostname not in ALLOWED_HOSTS.
Solution: Set ALLOWED_HOSTS: "*" in the ConfigMap, or add all hostnames that will be used to access the application (including any health check hostnames used by your Ingress controller or load balancer).
Useful debugging commands
Using managed services
For production deployments, replace in-cluster stateful services with managed services provided by your platform:
When using managed services:
- Skip applying the corresponding in-cluster manifest (e.g. don’t apply
10-postgresql.yaml) - Update the ConfigMap with the managed service connection details:
- If TLS is required, set the corresponding TLS variable (e.g.
AMQP_TLS: "true",REDIS_TLS: "true")
Configuration reference
For a complete list of all environment variables including security, CORS, HSTS, logging, and S3 storage options, see the Docker Compose Guide – Environment Variables.Support
If you encounter issues during deployment, please contact us at support@draftable.com with:- Pod logs (
kubectl logs -n draftable deployment/<pod-name>) - Pod events (
kubectl describe pod -n draftable -l app=<app-name>) - ConfigMap values (sanitised)
- Error messages from the browser console