Deploy Piper to DigitalOcean Kubernetes with managed PostgreSQL and Redis.
Prerequisites
DigitalOcean account with billing enabled
doctl CLI installed and authenticated
kubectl installed
helm 3.14+ installed
A domain you control (for SSL)
Setup
1. Install and Authenticate doctl
# Install (macOS)
brew install doctl
# Authenticate with your API token
# Create token at: https://cloud.digitalocean.com/account/api/tokens
doctl auth init
2. Create Kubernetes Cluster
doctl kubernetes cluster create piper-cluster \
--region nyc1 \
--node-pool "name=default;size=s-2vcpu-4gb;count=3" \
--version latest
# Save kubeconfig
doctl kubernetes cluster kubeconfig save piper-cluster
# Verify connection
kubectl get nodes
3. Create Managed PostgreSQL
doctl databases create piper-postgres \
--engine pg \
--version 16 \
--size db-s-2vcpu-4gb \
--region nyc1 \
--num-nodes 1
Wait for the database to be online (~5 minutes):
doctl databases list
# Wait until Status shows "online"
Then enable the pgvector extension:
Get the database ID and connection details:
# Get the database ID
doctl databases list
# Get connection details (use the ID from above)
doctl databases connection < database-i d > --format Host,Port,User,Password
Connect via psql (replace values from above):
PGPASSWORD = your-password psql -h your-host.db.ondigitalocean.com -p 25060 -U doadmin -d defaultdb
Create the piper database:
CREATE DATABASE piper ;
\q
Reconnect to the piper database and enable pgvector:
PGPASSWORD = your-password psql -h your-host.db.ondigitalocean.com -p 25060 -U doadmin -d piper
CREATE EXTENSION IF NOT EXISTS vector ;
\q
4. Create Managed Valkey (Redis-compatible)
doctl databases create piper-valkey \
--engine valkey \
--version 8 \
--size db-s-1vcpu-2gb \
--region nyc1 \
--num-nodes 1
5. Install Nginx Ingress Controller
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.annotations."service\.beta\.kubernetes\.io/do-loadbalancer-name"="piper-lb"
Get the load balancer IP (may take a few minutes):
kubectl get svc -n ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
6. Install cert-manager (for Let’s Encrypt SSL)
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled= true
Create a ClusterIssuer for Let’s Encrypt:
kubectl apply -f - << EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected] # CHANGE THIS
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF
Point your domain to the load balancer IP:
Get the load balancer IP from step 5
In your DNS provider, create an A record:
Host : piper (or your subdomain)
Value : <LOAD_BALANCER_IP>
TTL : 300
Deploy Piper
1. Create Namespace and Secrets
kubectl create namespace piper
Generate application secrets (save the admin secret - you’ll need it later):
export JWT_SECRET = $( openssl rand -hex 32 )
export ADMIN_API_SECRET = $( openssl rand -hex 32 )
echo "Admin API Secret: $ADMIN_API_SECRET " # Save this!
Get your database passwords from the DigitalOcean Console:
PostgreSQL : Databases → piper-postgres → Connection Details → Password
Valkey : Databases → piper-valkey → Connection Details → Password
# Application secrets
kubectl create secret generic piper-secrets \
--namespace piper \
--from-literal=jwt-secret= " $JWT_SECRET " \
--from-literal=admin-api-secret= " $ADMIN_API_SECRET "
# Database credentials
kubectl create secret generic piper-db-credentials \
--namespace piper \
--from-literal=password= 'YOUR_POSTGRES_PASSWORD'
kubectl create secret generic piper-valkey-credentials \
--namespace piper \
--from-literal=password= 'YOUR_VALKEY_PASSWORD'
Save the ADMIN_API_SECRET - you’ll need it to create your first organization and user via the Admin API.
2. Create Image Pull Secret
kubectl create secret docker-registry ghcr-pull-secret \
--namespace piper \
--docker-server=ghcr.io \
--docker-username=YOUR_GITHUB_USERNAME \
--docker-password=YOUR_GITHUB_PAT
Copy the DigitalOcean values template:
cp helm/piper/values-digitalocean.yaml helm/piper/values-my-deployment.yaml
Edit values-my-deployment.yaml and fill in:
externalDatabase :
host : "your-db-host.db.ondigitalocean.com" # From DO Console
existingSecret : piper-db-credentials
externalRedis :
host : "your-redis-host.db.ondigitalocean.com" # From DO Console
existingSecret : piper-valkey-credentials
ingress :
host : "piper.yourcompany.com"
tls :
- secretName : piper-tls
hosts :
- "piper.yourcompany.com"
4. Deploy with Helm
# Fetch chart dependencies (first time only)
helm dependency build ./helm/piper
# Deploy (secrets are referenced from the piper-secrets created earlier)
helm upgrade --install piper ./helm/piper \
--namespace piper \
-f helm/piper/values-my-deployment.yaml \
--set config.existingSecret=piper-secrets
5. Verify Deployment
# Check pods are running
kubectl get pods -n piper
# Check services
kubectl get svc -n piper
# Check ingress and certificate
kubectl get ingress -n piper
kubectl get certificate -n piper
# View API logs
kubectl logs -n piper -l app.kubernetes.io/component=api -f
6. Test the Deployment
# Health check
curl https://piper.yourcompany.com/api/health
# Should return: {"status": "healthy"}
Updating
To deploy a new version:
# Update the image tag in your values file, then:
helm upgrade piper ./helm/piper \
--namespace piper \
-f helm/piper/values-my-deployment.yaml
Or update inline:
helm upgrade piper ./helm/piper \
--namespace piper \
-f helm/piper/values-my-deployment.yaml \
--set backend.image.tag="0.2.0" \
--set frontend.image.tag="0.2.0"
Scaling
Manual Scaling
kubectl scale deployment piper-api -n piper --replicas=4
kubectl scale deployment piper-worker -n piper --replicas=4
Enable Autoscaling
Update your values file:
api :
autoscaling :
enabled : true
minReplicas : 2
maxReplicas : 10
targetCPUUtilizationPercentage : 80
worker :
autoscaling :
enabled : true
minReplicas : 2
maxReplicas : 20
targetCPUUtilizationPercentage : 70
Then run helm upgrade.
Troubleshooting
Pods not starting
# Check pod status
kubectl describe pod -n piper < pod-nam e >
# Check events
kubectl get events -n piper --sort-by=.metadata.creationTimestamp
Database connection issues
# Test PostgreSQL connectivity
kubectl run -it --rm debug --image=postgres:16 --restart=Never -n piper -- \
psql "postgresql://doadmin:PASSWORD@HOST:25060/piper?sslmode=require" -c "SELECT 1"
Certificate not issuing
# Check certificate status
kubectl describe certificate piper-tls -n piper
# Check cert-manager logs
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager -f
View all logs
# API
kubectl logs -n piper -l app.kubernetes.io/component=api -f
# Worker
kubectl logs -n piper -l app.kubernetes.io/component=worker -f
# Frontend
kubectl logs -n piper -l app.kubernetes.io/component=frontend -f
Next Steps
Once your deployment is running, create your first organization and admin user:
Bootstrap Your Installation Create the first organization and admin user using the Admin API
Cleanup
To remove the deployment:
# Remove Piper
helm uninstall piper -n piper
kubectl delete namespace piper
# Remove infrastructure (optional)
doctl kubernetes cluster delete piper-cluster
doctl databases delete piper-postgres
doctl databases delete piper-valkey