> ## Documentation Index
> Fetch the complete documentation index at: https://help.draftable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Self-Hosted v3 – AWS EKS Deployment Guide (S3 Storage)

> Complete guide for deploying Draftable API Self-Hosted v3 on AWS Elastic Kubernetes Service (EKS) with S3 storage backend.

This guide provides step-by-step instructions for deploying Draftable API Self-Hosted v3 on AWS EKS with **S3 storage**.

<Warning>
  **AWS Only**: Kubernetes deployment of Draftable API Self-Hosted v3 is currently supported on **AWS EKS only**. Azure AKS is not officially supported — while it is technically possible to use an NFS shared volume (e.g. Azure NetApp Files) with `FILE_STORAGE_TYPE=local`, Draftable does not test or optimise against NFS and cannot provide support for NFS-related issues. See [`FILE_STORAGE_TYPE`](/hc/en-us/articles/51141426113049-Draftable-API-Self-Hosted-v3-Docker-Compose-Guide#file_storage_type) for details.
</Warning>

<Warning>
  **S3 Storage Only**: This guide covers S3-based deployments. EFS storage configurations are not covered in this documentation and are **not recommended** for Draftable deployments.
</Warning>

<CardGroup>
  <Card title="Prerequisites" icon="clipboard-check" href="#prerequisites" iconType="solid" horizontal />

  <Card title="Architecture" icon="sitemap" href="#architecture-overview" iconType="solid" horizontal />

  <Card title="AWS Setup" icon="aws" href="#aws-account-setup" iconType="solid" horizontal />

  <Card title="EKS Cluster" icon="dharmachakra" href="#create-eks-cluster" iconType="solid" horizontal />

  <Card title="S3 & IAM" icon="bucket" href="#create-s3-bucket" iconType="solid" horizontal />

  <Card title="Deploy Draftable" icon="rocket" href="#deploy-draftable" iconType="solid" horizontal />

  <Card title="DNS & HTTPS" icon="lock" href="#configure-dns-and-https" iconType="solid" horizontal />

  <Card title="Troubleshooting" icon="wrench" href="#troubleshooting" iconType="solid" horizontal />
</CardGroup>

***

## 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

<Warning>
  **Critical Data Loss Risk**: The stateful components (PostgreSQL, Redis, RabbitMQ) included in this guide use Kubernetes PersistentVolumeClaims for storage. However, if pods are restarted, rescheduled, or the cluster is rebuilt, **you may lose all your data** including the database, cached sessions, and queued messages.

  For **production deployments**, you **must** use managed AWS services for stateful components.
</Warning>

| 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       |

**Why managed services?**

* 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

<Warning>
  **Redis persistence is especially critical.** Redis stores the activated license and custom fonts. If Redis data is lost, all users will be locked out until the license is reactivated by re-running the `web-init` job. Always use persistent storage for Redis — or preferably, a managed Redis service with persistence enabled.
</Warning>

### S3 Authentication Architecture

<Warning>
  **Critical**: The Compare service does **NOT** support IRSA (IAM Roles for Service Accounts). It requires explicit AWS credentials.
</Warning>

| 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!)** |

The Compare service (.NET application) is missing the `AWSSDK.SecurityToken` assembly required for `AssumeRoleWithWebIdentity`. Attempting to use IRSA will result in:

```
Assembly AWSSDK.SecurityToken could not be found or loaded.
This assembly must be available at runtime to use Amazon.Runtime.AssumeRoleAWSCredentials.
```

***

## 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:

| Tool        | Purpose                       | Installation                                              |
| ----------- | ----------------------------- | --------------------------------------------------------- |
| **AWS CLI** | AWS resource management       | [Download](https://aws.amazon.com/cli/)                   |
| **kubectl** | Kubernetes cluster management | [Download](https://kubernetes.io/docs/tasks/tools/)       |
| **eksctl**  | EKS cluster creation          | [Download](https://github.com/weaveworks/eksctl/releases) |
| **Helm**    | Kubernetes package manager    | [Download](https://github.com/helm/helm/releases)         |

Verify installations:

```bash theme={null}
aws --version
kubectl version --client
eksctl version
helm version
```

### Hardware Requirements

Your EKS cluster nodes should meet these minimum requirements:

* **Instance Type**: `t3.large` or 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

<Tip>
  **Recommendation**: Instead of creating a dedicated IAM user for EKS access, grant access to your existing administrator role (e.g., SSO Administrator). This allows you to use your regular admin credentials with `kubectl` and view resources in the AWS Console.
</Tip>

#### Option A: Use Existing Administrator Role (Recommended)

After cluster creation, add your administrator role via EKS Access Entries:

1. Go to **AWS Console** → **EKS** → Your Cluster → **Access** tab
2. Click **Create access entry**
3. Select your **SSO Administrator role** (or equivalent)
4. Click **Next**
5. Add access policy: **AmazonEKSClusterAdminPolicy**
6. Click **Create**

#### Option B: Create Dedicated IAM User

If you prefer a dedicated user, it needs these permissions:

* `AdministratorAccess` (recommended for initial setup)

Or these specific policies:

* `AmazonEKSClusterPolicy`
* `AmazonEKSServicePolicy`
* `AmazonEC2FullAccess`
* `AmazonVPCFullAccess`
* `AWSCloudFormationFullAccess`
* `IAMFullAccess`

### Configure AWS CLI

```bash theme={null}
aws configure
# Enter: Access Key ID, Secret Access Key, Region, Output format (json)
```

***

## Create EKS Cluster

### Cluster Configuration

Download or create the cluster configuration file:

<Card title="eksctl-cluster-config.yaml" icon="github" href="https://github.com/draftable/Draftable-Docs/blob/main/attachments/apish-v3-kubernetes-eks-s3/eksctl-cluster-config.yaml" horizontal>
  EKS cluster configuration for Draftable
</Card>

Key configuration points:

```yaml theme={null}
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: draftable-eks
  region: ap-southeast-2  # UPDATE: Your region
  version: "1.31"  # UPDATE: Use latest stable version

vpc:
  nat:
    gateway: Single  # Avoids Elastic IP limits

iam:
  withOIDC: true  # REQUIRED for IRSA (S3 access)

managedNodeGroups:
  - name: draftable-workers
    instanceType: t3.large
    desiredCapacity: 3
    minSize: 2
    maxSize: 5
    volumeSize: 50
    volumeType: gp3
```

<Note>
  **Kubernetes Version**: Always use the latest stable version. Check available versions with:

  ```bash theme={null}
  aws eks describe-addon-versions --query 'addons[0].addonVersions[*].compatibilities[*].clusterVersion' --output text | sort -u
  ```
</Note>

### Create the Cluster

```bash theme={null}
eksctl create cluster -f eksctl-cluster-config.yaml
```

<Warning>
  This command takes **15–25 minutes** to complete. Do not interrupt the process.
</Warning>

### Configure kubectl

After cluster creation, configure `kubectl` to connect:

```bash theme={null}
aws eks update-kubeconfig --region ap-southeast-2 --name draftable-eks
```

### Grant Console Access (Optional but Recommended)

To view cluster resources in the AWS Console:

1. Go to **AWS Console** → **EKS** → **draftable-eks** → **Access** tab
2. Click **Create access entry**
3. Select your **SSO Administrator role**
4. Add policy: **AmazonEKSClusterAdminPolicy**
5. Click **Create**

### Verify Cluster

```bash theme={null}
kubectl get nodes
```

You should see 3 nodes in `Ready` state.

***

## Create S3 Bucket

### Create the Bucket

```bash theme={null}
# Set variables
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
BUCKET_NAME="draftable-storage-$ACCOUNT_ID"
REGION="ap-southeast-2"

# Create bucket
aws s3 mb s3://$BUCKET_NAME --region $REGION
```

### Configure CORS

<Warning>
  **CORS is required** for the document viewer to load files from S3. Without CORS, comparisons will fail to render in the browser.
</Warning>

Download or create the CORS configuration:

<Card title="s3-bucket-cors.json" icon="github" href="https://github.com/draftable/Draftable-Docs/blob/main/attachments/apish-v3-kubernetes-eks-s3/s3-bucket-cors.json" horizontal>
  S3 CORS configuration
</Card>

Update the `AllowedOrigins` to match your domain:

```json theme={null}
[
    {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET", "HEAD"],
        "AllowedOrigins": ["https://your-domain.example.com"],
        "ExposeHeaders": ["ETag", "Content-Length", "Content-Type"]
    }
]
```

Apply the CORS configuration:

```bash theme={null}
aws s3api put-bucket-cors --bucket $BUCKET_NAME --cors-configuration file://s3-bucket-cors.json
```

***

## Configure IAM for S3 Access

Draftable requires two types of AWS credentials:

1. **IRSA (IAM Role for Service Accounts)** – Used by Web, Celery Worker, and Celery Beat
2. **IAM User with Access Keys** – Required by the Compare service (does not support IRSA)

### Get OIDC Provider ID

```bash theme={null}
OIDC_ID=$(aws eks describe-cluster --name draftable-eks --region ap-southeast-2 \
  --query "cluster.identity.oidc.issuer" --output text | rev | cut -d'/' -f1 | rev)
echo $OIDC_ID
```

### Create S3 Access Policy

Download or create the policy:

<Card title="s3-access-policy.json" icon="github" href="https://github.com/draftable/Draftable-Docs/blob/main/attachments/apish-v3-kubernetes-eks-s3/iam-policies/s3-access-policy.json" horizontal>
  IAM policy for S3 access
</Card>

Update the bucket name in the policy, then create it:

```bash theme={null}
aws iam create-policy \
  --policy-name DraftableS3AccessPolicy \
  --policy-document file://s3-access-policy.json
```

### Create IAM Role for IRSA (Web/Celery)

Download or create the trust policy:

<Card title="irsa-trust-policy.json" icon="github" href="https://github.com/draftable/Draftable-Docs/blob/main/attachments/apish-v3-kubernetes-eks-s3/iam-policies/irsa-trust-policy.json" horizontal>
  IRSA trust policy template
</Card>

Update the following placeholders:

* `YOUR_ACCOUNT_ID` – Your AWS account ID
* `YOUR_REGION` – Your AWS region
* `YOUR_OIDC_ID` – The OIDC ID from the previous step

Create the role:

```bash theme={null}
aws iam create-role \
  --role-name DraftableS3AccessRole \
  --assume-role-policy-document file://irsa-trust-policy.json

aws iam attach-role-policy \
  --role-name DraftableS3AccessRole \
  --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/DraftableS3AccessPolicy
```

### Create IAM User for Compare Service

<Warning>
  **Required**: The Compare service cannot use IRSA and must have explicit AWS credentials.
</Warning>

```bash theme={null}
# Create user
aws iam create-user --user-name draftable-s3-user

# Attach policy
aws iam put-user-policy \
  --user-name draftable-s3-user \
  --policy-name S3Access \
  --policy-document file://s3-access-policy.json

# Create access key
aws iam create-access-key --user-name draftable-s3-user
```

<Note>
  **Save the `AccessKeyId` and `SecretAccessKey`** from the output – you will need these when creating Kubernetes secrets.
</Note>

***

## Install AWS Load Balancer Controller

The AWS Load Balancer Controller is required to create Application Load Balancers (ALB) for the Ingress.

### Create IAM Policy

```bash theme={null}
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json

aws iam create-policy \
  --policy-name AWSLoadBalancerControllerIAMPolicy \
  --policy-document file://iam_policy.json
```

### Create Service Account

```bash theme={null}
eksctl create iamserviceaccount \
  --cluster=draftable-eks \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy \
  --approve \
  --region=ap-southeast-2
```

### Install Controller via Helm

```bash theme={null}
helm repo add eks https://aws.github.io/eks-charts
helm repo update

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=draftable-eks \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller
```

Verify the controller is running:

```bash theme={null}
kubectl get deployment -n kube-system aws-load-balancer-controller
```

***

## Deploy Draftable

### Kubernetes Manifests

All Kubernetes manifests are available in our GitHub repository:

<Card title="Kubernetes Manifests for EKS + S3" icon="github" href="https://github.com/draftable/Draftable-Docs/tree/main/attachments/apish-v3-kubernetes-eks-s3" horizontal>
  Complete set of deployment manifests
</Card>

<Warning>
  **Templated Files**: All YAML manifests are deliberately templated and incomplete. You **must** replace placeholder values (marked with `YOUR_*` or `REPLACE_*`) with your own configuration before applying them to your cluster.
</Warning>

| 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`

```yaml theme={null}
data:
  APP_BASE_URL: "https://your-domain.example.com"
  SERVER_DNS: "your-domain.example.com"
  CSRF_TRUSTED_ORIGINS: "https://your-domain.example.com"
  S3_STORAGE_BUCKET: "your-bucket-name"
  AWS_S3_REGION_NAME: "ap-southeast-2"
```

<Warning>
  **Critical S3 Configuration**: The `FILE_STORAGE_TYPE` must be set to `s3`. Without this, the application uses local filesystem storage regardless of other S3 settings.
</Warning>

#### `03-service-account.yaml`

```yaml theme={null}
annotations:
  eks.amazonaws.com/role-arn: arn:aws:iam::YOUR_ACCOUNT_ID:role/DraftableS3AccessRole
```

#### `30-ingress.yaml`

```yaml theme={null}
annotations:
  alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-southeast-2:YOUR_ACCOUNT_ID:certificate/YOUR_CERT_ID
spec:
  rules:
    - host: your-domain.example.com
```

### Create Secrets

<Steps>
  <Step title="Create namespace">
    ```bash theme={null}
    kubectl apply -f 00-namespace.yaml
    ```
  </Step>

  <Step title="Create application secrets">
    ```bash theme={null}
    kubectl create secret generic draftable-secrets -n draftable \
      --from-literal=db-password='YourDbPassword123!' \
      --from-literal=amqp-password='YourAmqpPassword123!' \
      --from-literal=redis-password='YourRedisPassword123!' \
      --from-literal=django-secret-key='your-64-character-random-string-here-make-it-long-and-random!' \
      --from-literal=draftable-product-key='YOUR-DRAFTABLE-LICENSE-KEY'
    ```
  </Step>

  <Step title="Create AWS credentials for Compare service">
    ```bash theme={null}
    kubectl create secret generic aws-s3-credentials -n draftable \
      --from-literal=AWS_ACCESS_KEY_ID='YOUR_ACCESS_KEY' \
      --from-literal=AWS_SECRET_ACCESS_KEY='YOUR_SECRET_KEY'
    ```
  </Step>
</Steps>

### Deploy Infrastructure

<Steps>
  <Step title="Apply configuration">
    ```bash theme={null}
    kubectl apply -f 02-configmap.yaml
    kubectl apply -f 03-service-account.yaml
    ```
  </Step>

  <Step title="Deploy infrastructure services">
    ```bash theme={null}
    kubectl apply -f 10-postgresql.yaml
    kubectl apply -f 11-rabbitmq.yaml
    kubectl apply -f 12-redis.yaml
    ```
  </Step>

  <Step title="Wait for infrastructure pods">
    ```bash theme={null}
    kubectl wait --for=condition=ready pod -l app=postgresql -n draftable --timeout=300s
    kubectl wait --for=condition=ready pod -l app=rabbitmq -n draftable --timeout=300s
    kubectl wait --for=condition=ready pod -l app=redis -n draftable --timeout=300s
    ```
  </Step>
</Steps>

### Run Database Migrations

```bash theme={null}
kubectl apply -f 20-web-init-job.yaml

# Wait for job to complete
kubectl wait --for=condition=complete job/web-init -n draftable --timeout=300s
```

### Deploy Application

<Warning>
  **Converter Health Probes**: The `25-converter.yaml` manifest **must** use TCP socket probes (not HTTP). The JODConverter API returns 404 on all health endpoints, and LibreOffice takes 60-90 seconds to start. Using HTTP probes will cause the pod to enter CrashLoopBackOff with hundreds of restarts. See [Troubleshooting](#converter-in-crashloopbackoff-400-restarts) for details.
</Warning>

```bash theme={null}
kubectl apply -f 21-web.yaml
kubectl apply -f 22-celery-worker.yaml
kubectl apply -f 23-celery-beat.yaml
kubectl apply -f 24-compare.yaml
kubectl apply -f 25-converter.yaml

# Wait for web pods
kubectl wait --for=condition=ready pod -l app=web -n draftable --timeout=300s
```

<Note>
  **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.
</Note>

### Deploy Ingress

```bash theme={null}
kubectl apply -f 30-ingress.yaml
```

***

## Configure DNS and HTTPS

### Get ALB DNS Name

```bash theme={null}
kubectl get ingress draftable-ingress -n draftable
```

The `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:

1. Go to **AWS Certificate Manager** in the AWS Console
2. Click **Request a certificate**
3. Select **Request a public certificate**
4. Enter your domain name
5. Choose **DNS validation**
6. Complete the DNS validation process
7. Update `30-ingress.yaml` with the certificate ARN

***

## Verification

### Check Pod Status

```bash theme={null}
kubectl get pods -n draftable
```

All pods should show `Running` status with `1/1` or `2/2` ready.

Expected output:

```
NAME                             READY   STATUS    RESTARTS   AGE
celery-beat-xxx                  1/1     Running   0          5m
celery-worker-xxx                1/1     Running   0          5m
compare-xxx                      1/1     Running   0          5m
converter-xxx                    1/1     Running   0          5m
postgresql-xxx                   1/1     Running   0          10m
rabbitmq-xxx                     1/1     Running   0          10m
redis-xxx                        1/1     Running   0          10m
web-xxx                          1/1     Running   0          5m
web-xxx                          1/1     Running   0          5m
```

### Check S3 Connectivity

After creating a comparison, verify files are stored in S3:

```bash theme={null}
aws s3 ls s3://$BUCKET_NAME/ --recursive
```

### Access the Application

Open `https://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:

```bash theme={null}
kubectl get storageclass
```

Ensure you're using `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:

```bash theme={null}
kubectl get configmap draftable-config -n draftable -o yaml
kubectl get secret draftable-secrets -n draftable -o yaml
```

***

### Compare Fails with `AWSSDK.SecurityToken could not be found`

**Cause**: Compare service is attempting to use IRSA (not supported).

**Solution**:

1. Ensure Compare uses `serviceAccountName: default` (not `draftable-s3-sa`)
2. Verify `aws-s3-credentials` secret exists with `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`

```bash theme={null}
kubectl get deployment compare -n draftable -o jsonpath='{.spec.template.spec.serviceAccountName}'
# Should output: default

kubectl get secret aws-s3-credentials -n draftable
# Should exist
```

***

### Viewer Doesn't Render Documents (CORS Error)

**Cause**: S3 bucket CORS not configured.

**Solution**: Apply CORS configuration:

```bash theme={null}
aws s3api put-bucket-cors --bucket $BUCKET_NAME --cors-configuration file://s3-bucket-cors.json
```

***

### `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`

**Solution**: The converter **must** use TCP socket probes, not HTTP probes. Ensure your `25-converter.yaml` uses:

```yaml theme={null}
livenessProbe:
  tcpSocket:
    port: 8080
  initialDelaySeconds: 120
  periodSeconds: 30
  timeoutSeconds: 10
  failureThreshold: 3
readinessProbe:
  tcpSocket:
    port: 8080
  initialDelaySeconds: 90
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 6
```

<Warning>
  **Do NOT** use `httpGet` probes for the converter. The JODConverter REST API returns 404 on `/`, `/health`, and `/actuator/health` endpoints, causing probe failures and endless restart loops.
</Warning>

**Why TCP probes work**: TCP probes simply verify port 8080 is listening, which it is once Tomcat starts—even while LibreOffice is still initializing.

**Note**: During normal startup, you will see "Office process died with exit code 81" messages in the logs. This is expected behavior while LibreOffice initializes.

***

### Useful Debugging Commands

```bash theme={null}
# View all pods
kubectl get pods -n draftable

# View pod logs
kubectl logs -n draftable deployment/web --tail=100

# Describe pod for events
kubectl describe pod -n draftable -l app=web

# Check environment variables in pod
kubectl exec -n draftable deployment/web -- env | grep S3

# Restart a deployment
kubectl rollout restart deployment/web -n draftable
```

***

## Production Recommendations

### Using Managed AWS Services

For production deployments, replace in-cluster stateful services with managed AWS services:

#### Amazon RDS for PostgreSQL

1. Create RDS PostgreSQL instance (Engine: PostgreSQL 16, Multi-AZ enabled)
2. Update ConfigMap:
   ```yaml theme={null}
   DB_HOST: "your-rds-endpoint.region.rds.amazonaws.com"
   DB_PORT: "5432"
   ```
3. Skip applying `10-postgresql.yaml`

#### Amazon ElastiCache for Redis

1. Create ElastiCache Redis cluster (Engine: Redis 7.x)
2. Update ConfigMap:
   ```yaml theme={null}
   REDIS_HOST: "your-elasticache-endpoint.region.cache.amazonaws.com"
   REDIS_PORT: "6379"
   ```
3. Skip applying `12-redis.yaml`

#### Amazon MQ for RabbitMQ

<Warning>
  Amazon MQ must use the **RabbitMQ** engine. **Amazon MQ for ActiveMQ is not compatible** with Draftable (it uses AMQP 1.0; Draftable requires AMQP 0-9-1), and **AWS SQS is not supported**. See [supported message brokers](/hc/en-us/articles/51141426113049-Draftable-API-Self-Hosted-v3-Docker-Compose-Guide#amqp-rabbitmq). If your RabbitMQ or Redis uses a private or internal CA, see [Using a private or internal CA](/hc/en-us/articles/API-Self-Hosted-v3-Using-a-Private-or-Internal-CA).
</Warning>

1. Create Amazon MQ RabbitMQ broker
2. Update ConfigMap:
   ```yaml theme={null}
   AMQP_HOST: "your-amazonmq-endpoint.mq.region.amazonaws.com"
   AMQP_PORT: "5671"  # Note: Amazon MQ uses TLS
   ```
3. 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](/hc/en-us/articles/51141426113049-Draftable-API-Self-Hosted-v3-Docker-Compose-Guide).

***

## Support

If you encounter issues during deployment, please contact us at [support@draftable.com](mailto: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
