Architecture Overview
Component Architecture
Draftable API Self-Hosted consists of several microservices that work together:| Component | Purpose | Kubernetes Suitability |
|---|---|---|
| Web | Django web application, API endpoints | ✅ Excellent (stateless) |
| Celery Worker | Background task processing | ✅ Excellent (stateless) |
| Celery Beat | Task scheduler | ✅ Excellent (stateless) |
| Compare | Document comparison engine (.NET) | ✅ Excellent (stateless) |
| Converter | Office-to-PDF conversion (LibreOffice) | ✅ Excellent (stateless) |
| PostgreSQL | Database | ⚠️ Consider managed service |
| Redis | Caching and sessions | ⚠️ Consider managed service |
| RabbitMQ | Message broker | ⚠️ Consider managed service |
Stateful vs Stateless Recommendation
| Component | Kubernetes (Dev/Test Only) | Production Requirement |
|---|---|---|
| PostgreSQL | 10-postgresql.yaml | Amazon RDS for PostgreSQL |
| Redis | 12-redis.yaml | Amazon ElastiCache for Redis |
| RabbitMQ | 11-rabbitmq.yaml | Amazon MQ for RabbitMQ |
- Kubernetes frequently tears down and recreates containers—excellent for stateless services but dangerous for stateful workloads
- Pod restarts, node failures, or cluster updates can result in permanent data loss
- Managed services provide automated backups, high availability, and data persistence guarantees
S3 Authentication Architecture
| Component | Service Account | S3 Authentication Method |
|---|---|---|
| Web | draftable-s3-sa | IRSA (IAM Roles for Service Accounts) |
| Celery Worker | draftable-s3-sa | IRSA |
| Celery Beat | draftable-s3-sa | IRSA |
| Compare | default | Explicit AWS credentials (required!) |
AWSSDK.SecurityToken assembly required for AssumeRoleWithWebIdentity. Attempting to use IRSA will result in:
Prerequisites
Required Information
Before starting, gather the following:- AWS Account ID
- Desired AWS Region (e.g.,
ap-southeast-2) - Domain name for the application
- Draftable product license key
Required Tools
Install and configure the following tools on your workstation:
Verify installations:
Hardware Requirements
Your EKS cluster nodes should meet these minimum requirements:- Instance Type:
t3.largeor larger (2 vCPU, 8 GB RAM per node) - Node Count: Minimum 3 nodes for production
- Storage: 50 GB per node
Estimated Deployment Time
| Phase | Duration |
|---|---|
| EKS cluster creation | 15–25 minutes |
| S3 and IAM setup | 10–15 minutes |
| Application deployment | 10–15 minutes |
| Total | 45–60 minutes |
AWS Account Setup
IAM Access Configuration
Option A: Use Existing Administrator Role (Recommended)
After cluster creation, add your administrator role via EKS Access Entries:- Go to AWS Console → EKS → Your Cluster → Access tab
- Click Create access entry
- Select your SSO Administrator role (or equivalent)
- Click Next
- Add access policy: AmazonEKSClusterAdminPolicy
- Click Create
Option B: Create Dedicated IAM User
If you prefer a dedicated user, it needs these permissions:AdministratorAccess(recommended for initial setup)
AmazonEKSClusterPolicyAmazonEKSServicePolicyAmazonEC2FullAccessAmazonVPCFullAccessAWSCloudFormationFullAccessIAMFullAccess
Configure AWS CLI
Create EKS Cluster
Cluster Configuration
Download or create the cluster configuration file:eksctl-cluster-config.yaml
EKS cluster configuration for Draftable
Kubernetes Version: Always use the latest stable version. Check available versions with:
Create the Cluster
Configure kubectl
After cluster creation, configurekubectl to connect:
Grant Console Access (Optional but Recommended)
To view cluster resources in the AWS Console:- Go to AWS Console → EKS → draftable-eks → Access tab
- Click Create access entry
- Select your SSO Administrator role
- Add policy: AmazonEKSClusterAdminPolicy
- Click Create
Verify Cluster
Ready state.
Create S3 Bucket
Create the Bucket
Configure CORS
Download or create the CORS configuration:s3-bucket-cors.json
S3 CORS configuration
AllowedOrigins to match your domain:
Configure IAM for S3 Access
Draftable requires two types of AWS credentials:- IRSA (IAM Role for Service Accounts) – Used by Web, Celery Worker, and Celery Beat
- IAM User with Access Keys – Required by the Compare service (does not support IRSA)
Get OIDC Provider ID
Create S3 Access Policy
Download or create the policy:s3-access-policy.json
IAM policy for S3 access
Create IAM Role for IRSA (Web/Celery)
Download or create the trust policy:irsa-trust-policy.json
IRSA trust policy template
YOUR_ACCOUNT_ID– Your AWS account IDYOUR_REGION– Your AWS regionYOUR_OIDC_ID– The OIDC ID from the previous step
Create IAM User for Compare Service
Save the
AccessKeyId and SecretAccessKey from the output – you will need these when creating Kubernetes secrets.Install AWS Load Balancer Controller
The AWS Load Balancer Controller is required to create Application Load Balancers (ALB) for the Ingress.Create IAM Policy
Create Service Account
Install Controller via Helm
Deploy Draftable
Kubernetes Manifests
All Kubernetes manifests are available in our GitHub repository:Kubernetes Manifests for EKS + S3
Complete set of deployment manifests
| File | Description |
|---|---|
00-namespace.yaml | Creates the draftable namespace |
01-secrets.yaml | Application secrets (template) |
02-configmap.yaml | Application configuration |
03-service-account.yaml | IRSA service account for S3 |
04-aws-credentials-secret.yaml | AWS credentials for Compare service |
10-postgresql.yaml | PostgreSQL database |
11-rabbitmq.yaml | RabbitMQ message broker |
12-redis.yaml | Redis cache |
20-web-init-job.yaml | Database migration job |
21-web.yaml | Web application |
22-celery-worker.yaml | Celery background worker |
23-celery-beat.yaml | Celery scheduler |
24-compare.yaml | Compare service |
25-converter.yaml | Document converter (must use TCP probes) |
30-ingress.yaml | ALB Ingress |
Update Configuration Files
Before deploying, update these files with your values:02-configmap.yaml
03-service-account.yaml
30-ingress.yaml
Create Secrets
Deploy Infrastructure
Run Database Migrations
Deploy Application
Converter startup time: The converter pod may take 90-120 seconds to become ready. During startup, you may see “Office process died with exit code 81” in the logs—this is normal while LibreOffice initializes.
Deploy Ingress
Configure DNS and HTTPS
Get ALB DNS Name
ADDRESS column shows the ALB DNS name.
Create DNS Record
In your DNS provider, create a CNAME record:| Field | Value |
|---|---|
| Name | Your subdomain (e.g., draftable) |
| Type | CNAME |
| Value | ALB DNS name from above |
Request ACM Certificate
If you haven’t already created an ACM certificate:- Go to AWS Certificate Manager in the AWS Console
- Click Request a certificate
- Select Request a public certificate
- Enter your domain name
- Choose DNS validation
- Complete the DNS validation process
- Update
30-ingress.yamlwith the certificate ARN
Verification
Check Pod Status
Running status with 1/1 or 2/2 ready.
Expected output:
Check S3 Connectivity
After creating a comparison, verify files are stored in S3:Access the Application
Openhttps://your-domain.example.com in your browser. You should see the Draftable login page.
Troubleshooting
Pods Stuck in Pending
Cause: PersistentVolumeClaims not binding.
Solution: Check StorageClass exists:
gp2 (default on EKS). If using gp3, you may need to create the StorageClass first.
CreateContainerConfigError
Cause: ConfigMap or Secret key not found.
Solution: Verify all ConfigMap and Secret keys exist:
Compare Fails with AWSSDK.SecurityToken could not be found
Cause: Compare service is attempting to use IRSA (not supported).
Solution:
- Ensure Compare uses
serviceAccountName: default(notdraftable-s3-sa) - Verify
aws-s3-credentialssecret exists withAWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY
Viewer Doesn’t Render Documents (CORS Error)
Cause: S3 bucket CORS not configured. Solution: Apply CORS configuration:DisallowedHost Error in Web Logs
Cause: ALB health checks use the pod’s internal IP address, not the domain name.
Solution: Ensure ALLOWED_HOSTS: "*" is set in the ConfigMap (already configured in the provided manifests).
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 is named redis-svc in our manifests to avoid this conflict. Ensure you’re using the provided manifests.
Converter in CrashLoopBackOff (400+ Restarts)
Cause: HTTP health probes fail because JODConverter returns 404 on all health endpoints, and LibreOffice takes 60-90 seconds to initialize.
Symptoms:
- Pod restarts continuously (often 400+ times)
- Events show:
Readiness probe failed: HTTP probe failed with statuscode: 404 - Logs show:
Office process died with exit code 81; restarting it
25-converter.yaml uses:
Useful Debugging Commands
Production Recommendations
Using Managed AWS Services
For production deployments, replace in-cluster stateful services with managed AWS services:Amazon RDS for PostgreSQL
- Create RDS PostgreSQL instance (Engine: PostgreSQL 16, Multi-AZ enabled)
- Update ConfigMap:
- Skip applying
10-postgresql.yaml
Amazon ElastiCache for Redis
- Create ElastiCache Redis cluster (Engine: Redis 7.x)
- Update ConfigMap:
- Skip applying
12-redis.yaml
Amazon MQ for RabbitMQ
- Create Amazon MQ RabbitMQ broker
- Update ConfigMap:
- Skip applying
11-rabbitmq.yaml
Configuration Reference
S3 Environment Variables
| Variable | Purpose | Required |
|---|---|---|
FILE_STORAGE_TYPE | Must be s3 to enable S3 storage | ✅ |
S3_STORAGE_BUCKET | S3 bucket name | ✅ |
AWS_S3_REGION_NAME | AWS region (note: not AWS_REGION) | ✅ |
Application Environment Variables
For a complete list of environment variables, see the Docker Compose Guide.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 (sanitized)
- Error messages from the browser console