Skip to main content
Recommended for: Production deployments with 50+ users

Architecture overview

Important: Teable requires S3-compatible storage. You can use:
  • AWS S3 (cross-cloud setup)
  • MinIO (self-hosted on Azure VM/AKS)
  • Any other S3-compatible service
Azure Blob Storage is not directly supported because it uses a different API.

Prerequisites

  • Azure CLI installed and logged in
  • Azure subscription with permissions for App Service, PostgreSQL, Redis
  • Access to an S3-compatible storage (e.g., AWS S3 account, or MinIO deployment)

Step 1: Create Azure resources

1.1 Create resource group

az group create \
  --name teable-rg \
  --location eastus

1.2 Create PostgreSQL Flexible Server

az postgres flexible-server create \
  --resource-group teable-rg \
  --name teable-db \
  --location eastus \
  --admin-user teableadmin \
  --admin-password '<YourStrongPassword>' \
  --sku-name Standard_B2s \
  --tier Burstable \
  --storage-size 128 \
  --version 16 \
  --public-access 0.0.0.0-255.255.255.255
Wait until provisioning completes. Get the connection string:
az postgres flexible-server show \
  --resource-group teable-rg \
  --name teable-db \
  --query "fullyQualifiedDomainName" --output tsv
Result: teable-db.postgres.database.azure.com

1.3 Create Azure Cache for Redis

az redis create \
  --resource-group teable-rg \
  --name teable-cache \
  --location eastus \
  --sku Basic \
  --vm-size c0 \
  --minimum-tls-version 1.2
Get the access key:
az redis list-keys \
  --resource-group teable-rg \
  --name teable-cache \
  --query "primaryKey" --output tsv

Step 2: Set up S3-compatible storage

Since Azure Blob Storage is not S3-compatible, choose one of the following options:

Option A: Use AWS S3 (cross-cloud)

This is the simplest approach if you already have AWS access or don’t want to manage MinIO.
  1. Create S3 buckets on AWS (see AWS deployment guide steps 1.3-1.4)
    • Public bucket: teable-public-<unique-suffix>
    • Private bucket: teable-private-<unique-suffix>
  2. Configure public bucket (see Object Storage guide):
    • Enable public read access
    • Configure CORS to allow any origin
You’ll use these environment variables:
BACKEND_STORAGE_PROVIDER=s3
BACKEND_STORAGE_S3_REGION=us-west-2
BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com
BACKEND_STORAGE_S3_ACCESS_KEY=<aws-access-key>
BACKEND_STORAGE_S3_SECRET_KEY=<aws-secret-key>
BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<suffix>
BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<suffix>
STORAGE_PREFIX=https://teable-public-<suffix>.s3.us-west-2.amazonaws.com

Option B: Deploy MinIO on Azure

If you prefer to keep everything in Azure, deploy MinIO as an S3-compatible gateway. Recommended image: minio/minio:RELEASE.2025-04-22T22-12-26Z Quick setup (Azure VM):
  1. Create a VM and install MinIO:
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/

# Start MinIO
export MINIO_ROOT_USER=admin
export MINIO_ROOT_PASSWORD=<strong-password>
minio server /data --console-address ":9001"
  1. Create buckets via MinIO console (port 9001) or CLI:
mc alias set myminio http://your-vm-ip:9000 admin <password>
mc mb myminio/public
mc mb myminio/private
mc policy set download myminio/public
You’ll use these environment variables:
BACKEND_STORAGE_PROVIDER=minio
BACKEND_STORAGE_MINIO_ENDPOINT=<vm-ip-or-domain>
BACKEND_STORAGE_MINIO_PORT=443
BACKEND_STORAGE_MINIO_USE_SSL=true
BACKEND_STORAGE_MINIO_ACCESS_KEY=admin
BACKEND_STORAGE_MINIO_SECRET_KEY=<password>
BACKEND_STORAGE_PUBLIC_BUCKET=public
BACKEND_STORAGE_PRIVATE_BUCKET=private
STORAGE_PREFIX=https://<vm-ip-or-domain>

Step 3: Prepare environment variables

Create a file app-settings.txt with all required variables:
# Core
PUBLIC_ORIGIN=https://teable-app.azurewebsites.net
SECRET_KEY=<generate-32-char-random-string>

# Database
PRISMA_DATABASE_URL=postgresql://teableadmin:<password>@teable-db.postgres.database.azure.com:5432/postgres?sslmode=require

# Redis (required for queues)
BACKEND_CACHE_PROVIDER=redis
BACKEND_CACHE_REDIS_URI=rediss://:<redis-key>@teable-cache.redis.cache.windows.net:6380/0

# Performance cache (optional; can point to the same Redis)
BACKEND_PERFORMANCE_CACHE=rediss://:<redis-key>@teable-cache.redis.cache.windows.net:6380/0

# Storage (S3-compatible)
# For Option A (AWS S3):
BACKEND_STORAGE_PROVIDER=s3
BACKEND_STORAGE_S3_REGION=us-west-2
BACKEND_STORAGE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com
BACKEND_STORAGE_S3_ACCESS_KEY=<aws-access-key>
BACKEND_STORAGE_S3_SECRET_KEY=<aws-secret-key>
BACKEND_STORAGE_PUBLIC_BUCKET=teable-public-<suffix>
BACKEND_STORAGE_PRIVATE_BUCKET=teable-private-<suffix>
STORAGE_PREFIX=https://teable-public-<suffix>.s3.us-west-2.amazonaws.com

# For Option B (MinIO):
# BACKEND_STORAGE_PROVIDER=minio
# BACKEND_STORAGE_MINIO_ENDPOINT=<vm-ip>
# BACKEND_STORAGE_MINIO_PORT=443
# BACKEND_STORAGE_MINIO_USE_SSL=true
# BACKEND_STORAGE_MINIO_ACCESS_KEY=admin
# BACKEND_STORAGE_MINIO_SECRET_KEY=<password>
# BACKEND_STORAGE_PUBLIC_BUCKET=public
# BACKEND_STORAGE_PRIVATE_BUCKET=private
# STORAGE_PREFIX=https://<vm-ip>
Generate a strong SECRET_KEY:
openssl rand -base64 32

Step 4: Deploy to Azure App Service

4.1 Create App Service Plan

az appservice plan create \
  --resource-group teable-rg \
  --name teable-plan \
  --location eastus \
  --is-linux \
  --sku P1v3

4.2 Create Web App (container-based)

az webapp create \
  --resource-group teable-rg \
  --plan teable-plan \
  --name teable-app \
  --deployment-container-image-name ghcr.io/teableio/teable:latest

4.3 Configure container settings

az webapp config container set \
  --resource-group teable-rg \
  --name teable-app \
  --docker-custom-image-name ghcr.io/teableio/teable:latest \
  --docker-registry-server-url https://ghcr.io

4.4 Set environment variables

# Convert app-settings.txt to JSON format and apply
az webapp config appsettings set \
  --resource-group teable-rg \
  --name teable-app \
  --settings \
    WEBSITES_PORT=3000 \
    PUBLIC_ORIGIN="https://teable-app.azurewebsites.net" \
    SECRET_KEY="<your-secret>" \
    PRISMA_DATABASE_URL="postgresql://..." \
    BACKEND_CACHE_PROVIDER="redis" \
    BACKEND_CACHE_REDIS_URI="rediss://..." \
    BACKEND_PERFORMANCE_CACHE="rediss://..." \
    BACKEND_STORAGE_PROVIDER="s3" \
    BACKEND_STORAGE_S3_REGION="us-west-2" \
    BACKEND_STORAGE_S3_ENDPOINT="https://s3.us-west-2.amazonaws.com" \
    BACKEND_STORAGE_S3_ACCESS_KEY="***" \
    BACKEND_STORAGE_S3_SECRET_KEY="***" \
    BACKEND_STORAGE_PUBLIC_BUCKET="teable-public-xxx" \
    BACKEND_STORAGE_PRIVATE_BUCKET="teable-private-xxx" \
    STORAGE_PREFIX="https://teable-public-xxx.s3.us-west-2.amazonaws.com"

4.5 Configure health check

az webapp config set \
  --resource-group teable-rg \
  --name teable-app \
  --generic-configurations '{"healthCheckPath": "/health"}'

4.6 Restart the app

az webapp restart \
  --resource-group teable-rg \
  --name teable-app

Step 5: Verify deployment

  1. Check app status:
az webapp show \
  --resource-group teable-rg \
  --name teable-app \
  --query "state" --output tsv
Expected: Running
  1. View logs:
az webapp log tail \
  --resource-group teable-rg \
  --name teable-app
  1. Test health check:
curl https://teable-app.azurewebsites.net/health
Expected response:
{"status":"ok"}
  1. Open Teable in browser: https://teable-app.azurewebsites.net

Troubleshooting

Database connection errors

  • Verify PostgreSQL firewall allows Azure services
  • Check connection string includes ?sslmode=require
  • Ensure database name is correct (default is postgres, not teable)

Redis connection errors

  • Use rediss:// (with double ‘s’) for TLS connections
  • Use port 6380 (not 6379) for Azure Cache for Redis
  • Verify access key is correct

S3 access errors (Option A: AWS S3)

  • Verify AWS credentials are correct
  • Check bucket names match exactly
  • Ensure public bucket has public read policy and CORS configured
  • Test S3 access from Azure: curl https://s3.us-west-2.amazonaws.com should work

Container fails to start

  • Check logs: az webapp log tail ...
  • Verify all required environment variables are set
  • Ensure WEBSITES_PORT=3000 is set

Production best practices

For detailed production recommendations including resource sizing, high availability, and scaling strategies, see Self-Hosted Overview.
Azure-specific tips:
  • Use Azure Key Vault for sensitive values
  • Enable HTTPS only: az webapp update --https-only true
  • Use VNet Integration to restrict database/redis access
  • Enable Application Insights for monitoring
Custom domain setup:
# Add custom domain
az webapp config hostname add \
  --resource-group teable-rg \
  --webapp-name teable-app \
  --hostname teable.yourcompany.com

# Create managed SSL certificate
az webapp config ssl create \
  --resource-group teable-rg \
  --name teable-app \
  --hostname teable.yourcompany.com

Alternative: Deploy on AKS (Azure Kubernetes Service)

For larger deployments or teams already using Kubernetes, AKS provides better scalability and control.

AKS Architecture

Step 1: Create AKS Cluster

# Create AKS cluster
az aks create \
  --resource-group teable-rg \
  --name teable-aks \
  --location eastus \
  --node-count 2 \
  --node-vm-size Standard_D4s_v3 \
  --enable-managed-identity \
  --generate-ssh-keys

# Get credentials
az aks get-credentials \
  --resource-group teable-rg \
  --name teable-aks

Step 2: Create Azure Managed Services

Create PostgreSQL and Redis using the same commands from Step 1.2 and Step 1.3.
For AKS, you may want to enable VNet integration to secure the connection:
# Get AKS VNet ID
AKS_VNET=$(az aks show \
  --resource-group teable-rg \
  --name teable-aks \
  --query "networkProfile.networkPlugin" -o tsv)

# Configure PostgreSQL to accept connections from AKS VNet
az postgres flexible-server vnet-rule create \
  --resource-group teable-rg \
  --server-name teable-db \
  --name aks-access \
  --vnet-name <aks-vnet-name> \
  --subnet <aks-subnet-name>

Step 3: Deploy MinIO to AKS (Optional)

If you prefer to keep storage within Azure, deploy MinIO to AKS:
# minio-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: minio
spec:
  replicas: 1
  selector:
    matchLabels:
      app: minio
  template:
    metadata:
      labels:
        app: minio
    spec:
      containers:
        - name: minio
          image: minio/minio:latest
          args:
            - server
            - /data
            - --console-address
            - ":9001"
          env:
            - name: MINIO_ROOT_USER
              value: "minioadmin"
            - name: MINIO_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: minio-secrets
                  key: password
          ports:
            - containerPort: 9000
            - containerPort: 9001
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: minio-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: minio
spec:
  ports:
    - name: api
      port: 9000
      targetPort: 9000
    - name: console
      port: 9001
      targetPort: 9001
  selector:
    app: minio
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: minio-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
  storageClassName: managed-premium
For production MinIO deployments, consider using the MinIO Operator for distributed mode with data redundancy.

Step 4: Create Kubernetes Configurations

Create the ConfigMap with Azure-specific settings:
# teable-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: teable-config
data:
  PUBLIC_ORIGIN: "https://teable.yourcompany.com"
  
  # Storage (MinIO in AKS)
  BACKEND_STORAGE_PROVIDER: "minio"
  BACKEND_STORAGE_MINIO_ENDPOINT: "minio.yourcompany.com"
  STORAGE_PREFIX: "https://minio.yourcompany.com"
  BACKEND_STORAGE_MINIO_INTERNAL_ENDPOINT: "minio.default.svc.cluster.local"
  BACKEND_STORAGE_MINIO_PORT: "443"
  BACKEND_STORAGE_MINIO_INTERNAL_PORT: "9000"
  BACKEND_STORAGE_MINIO_USE_SSL: "true"
  
  # Cache
  BACKEND_CACHE_PROVIDER: "redis"
  
  # Other
  NEXT_ENV_IMAGES_ALL_REMOTE: "true"
  PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING: "1"
Create secrets with Azure service credentials:
# teable-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: teable-secrets
type: Opaque
stringData:
  # PostgreSQL (Azure Flexible Server)
  PRISMA_DATABASE_URL: "postgresql://teableadmin:<password>@teable-db.postgres.database.azure.com:5432/postgres?sslmode=require"
  
  # Application secrets
  SECRET_KEY: "<your-secret-key>"
  
  # MinIO credentials
  BACKEND_STORAGE_PUBLIC_BUCKET: "public"
  BACKEND_STORAGE_PRIVATE_BUCKET: "private"
  BACKEND_STORAGE_MINIO_ACCESS_KEY: "minioadmin"
  BACKEND_STORAGE_MINIO_SECRET_KEY: "<minio-password>"
  
  # Redis (Azure Cache for Redis)
  BACKEND_CACHE_REDIS_URI: "rediss://:<redis-key>@teable-cache.redis.cache.windows.net:6380/0"

Step 5: Deploy Teable

# teable-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: teable
spec:
  replicas: 2  # Scale as needed
  selector:
    matchLabels:
      app: teable
  template:
    metadata:
      labels:
        app: teable
    spec:
      initContainers:
        - name: db-migrate
          image: ghcr.io/teableio/teable:latest
          args:
            - migrate-only
          envFrom:
            - configMapRef:
                name: teable-config
            - secretRef:
                name: teable-secrets
          resources:
            limits:
              cpu: 1000m
              memory: 1024Mi
      containers:
        - name: teable
          image: ghcr.io/teableio/teable:latest
          args:
            - skip-migrate
          ports:
            - containerPort: 3000
          envFrom:
            - configMapRef:
                name: teable-config
            - secretRef:
                name: teable-secrets
          resources:
            requests:
              cpu: 500m
              memory: 1Gi
            limits:
              cpu: 2000m
              memory: 4Gi
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 15
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: teable
spec:
  ports:
    - port: 3000
      targetPort: 3000
  selector:
    app: teable

Step 6: Configure Ingress

Option A: Using NGINX Ingress Controller
# Install NGINX Ingress
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz
# teable-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: teable
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - teable.yourcompany.com
      secretName: teable-tls
  rules:
    - host: teable.yourcompany.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: teable
                port:
                  number: 3000
Option B: Using Azure Application Gateway Ingress Controller (AGIC)
# Enable AGIC addon
az aks enable-addons \
  --resource-group teable-rg \
  --name teable-aks \
  --addons ingress-appgw \
  --appgw-name teable-appgw \
  --appgw-subnet-cidr "10.225.0.0/16"
# teable-ingress-agic.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: teable
  annotations:
    kubernetes.io/ingress.class: azure/application-gateway
    appgw.ingress.kubernetes.io/backend-path-prefix: "/"
    appgw.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - teable.yourcompany.com
      secretName: teable-tls
  rules:
    - host: teable.yourcompany.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: teable
                port:
                  number: 3000

Step 7: Apply All Configurations

# Apply configurations
kubectl apply -f teable-config.yaml
kubectl apply -f teable-secrets.yaml
kubectl apply -f minio-deployment.yaml  # If using MinIO in AKS
kubectl apply -f teable-deployment.yaml
kubectl apply -f teable-ingress.yaml

# Verify deployment
kubectl get pods -l app=teable
kubectl logs -l app=teable

AKS Production Recommendations

  1. Autoscaling:
# Enable Horizontal Pod Autoscaler
kubectl autoscale deployment teable --cpu-percent=70 --min=2 --max=10

# Enable Cluster Autoscaler
az aks update \
  --resource-group teable-rg \
  --name teable-aks \
  --enable-cluster-autoscaler \
  --min-count 2 \
  --max-count 5
  1. Use Azure Key Vault for secrets instead of Kubernetes secrets:
# Enable Key Vault secrets provider
az aks enable-addons \
  --resource-group teable-rg \
  --name teable-aks \
  --addons azure-keyvault-secrets-provider
  1. Enable Azure Monitor:
az aks enable-addons \
  --resource-group teable-rg \
  --name teable-aks \
  --addons monitoring
  1. Use Azure Private Link for PostgreSQL and Redis to keep traffic within Azure network.

Last modified on January 23, 2026