Skip to main content

Deployment

This guide covers deploying Chive to production using Docker and Kubernetes.

Prerequisites

  • Docker 24+ and Docker Compose
  • Kubernetes 1.28+ cluster (for K8s deployment)
  • Helm 3.12+ (for K8s deployment)
  • Access to container registry

Docker deployment

Building images

# Build all images
docker compose -f docker-compose.prod.yml build

# Build specific service
docker compose -f docker-compose.prod.yml build api

Image structure

ImagePurpose
chive/apiAPI server (Hono)
chive/indexerFirehose consumer
chive/workerBackground job processor
chive/webNext.js frontend

Running with Docker Compose

# Start all services
docker compose -f docker-compose.prod.yml up -d

# View logs
docker compose -f docker-compose.prod.yml logs -f

# Stop services
docker compose -f docker-compose.prod.yml down

Production compose file

# docker-compose.prod.yml
version: '3.8'

services:
api:
image: chive/api:latest
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
deploy:
replicas: 3
resources:
limits:
cpus: '1'
memory: 1G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3

indexer:
image: chive/indexer:latest
environment:
- NODE_ENV=production
- FIREHOSE_URL=${FIREHOSE_URL}
deploy:
replicas: 1 # Single consumer for ordering
resources:
limits:
cpus: '0.5'
memory: 512M

worker:
image: chive/worker:latest
environment:
- NODE_ENV=production
- REDIS_URL=${REDIS_URL}
deploy:
replicas: 2
resources:
limits:
cpus: '0.5'
memory: 512M

web:
image: chive/web:latest
ports:
- "3001:3000"
environment:
- NODE_ENV=production
- API_URL=http://api:3000
deploy:
replicas: 2

Kubernetes deployment

Helm chart

Chive provides a Helm chart for Kubernetes deployment:

# Add Chive Helm repo
helm repo add chive https://charts.chive.pub
helm repo update

# Install
helm install chive chive/chive \
--namespace chive \
--create-namespace \
--values values.yaml

Values configuration

# values.yaml
global:
environment: production
domain: chive.pub

api:
replicas: 3
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPU: 70

indexer:
replicas: 1 # Must be 1 for event ordering
resources:
requests:
cpu: 250m
memory: 256Mi

worker:
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi

web:
replicas: 2
resources:
requests:
cpu: 250m
memory: 256Mi

postgresql:
enabled: true
primary:
persistence:
size: 100Gi

elasticsearch:
enabled: true
replicas: 3
volumeClaimTemplate:
resources:
requests:
storage: 100Gi

neo4j:
enabled: true
core:
standalone: true
persistentVolume:
size: 50Gi

redis:
enabled: true
architecture: standalone
persistence:
size: 10Gi

ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: chive.pub
paths:
- path: /
pathType: Prefix
tls:
- secretName: chive-tls
hosts:
- chive.pub

Secrets management

# Create secrets
kubectl create secret generic chive-secrets \
--namespace chive \
--from-literal=database-url='postgresql://...' \
--from-literal=redis-url='redis://...' \
--from-literal=jwt-secret='...'

# Or use external secrets operator
kubectl apply -f external-secrets.yaml

Database setup

PostgreSQL initialization

# Run migrations
kubectl exec -it deploy/chive-api -n chive -- \
pnpm db:migrate:up

# Verify schema
kubectl exec -it deploy/chive-api -n chive -- \
pnpm db:migrate:status

Elasticsearch setup

# Apply templates
kubectl exec -it deploy/chive-api -n chive -- \
tsx scripts/db/setup-elasticsearch.ts

Neo4j setup

# Initialize schema
kubectl exec -it deploy/chive-api -n chive -- \
tsx scripts/db/setup-neo4j.ts

Environment variables

Required

VariableDescription
DATABASE_URLPostgreSQL connection string
REDIS_URLRedis connection string
ELASTICSEARCH_URLElasticsearch cluster URL
NEO4J_URINeo4j Bolt URI
NEO4J_PASSWORDNeo4j password
JWT_SECRETSecret for signing JWTs
FIREHOSE_URLATProto relay URL

Optional

VariableDefaultDescription
NODE_ENVdevelopmentEnvironment
PORT3000API server port
LOG_LEVELinfoLogging level
OTEL_EXPORTER_OTLP_ENDPOINTNoneOpenTelemetry endpoint

Health checks

Endpoints

EndpointPurpose
/healthBasic liveness check
/health/readyReadiness (includes DB checks)
/health/liveLiveness (process running)

Kubernetes probes

livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 10
periodSeconds: 30

readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10

TLS and certificates

Using cert-manager

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@chive.pub
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx

Rolling updates

Deployment strategy

spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0

Triggering updates

# Update image tag
helm upgrade chive chive/chive \
--namespace chive \
--set api.image.tag=v1.2.3

# Or with kubectl
kubectl set image deployment/chive-api \
api=chive/api:v1.2.3 -n chive

Rollback

# Helm rollback
helm rollback chive 1 -n chive

# Kubernetes rollback
kubectl rollout undo deployment/chive-api -n chive

# Check rollout status
kubectl rollout status deployment/chive-api -n chive