Deployment
Spooled Cloud can be deployed as a managed SaaS service or self-hosted on your own infrastructure. This guide covers all deployment options and production best practices.
Self-Hosted
Deploy on your infrastructure with full control over data and scaling.
Deployment Architecture
A production Spooled deployment consists of stateless API servers behind a load balancer, with PostgreSQL for persistence and Redis for real-time pub/sub.
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ecfdf5', 'primaryTextColor': '#065f46', 'primaryBorderColor': '#10b981', 'lineColor': '#6b7280'}}}%%
flowchart TB
subgraph lb["Load Balancer"]
LB["Nginx / Cloud LB"]
end
subgraph api["API Tier (Stateless)"]
A1["Spooled API #1"]
A2["Spooled API #2"]
A3["Spooled API #N"]
end
subgraph data["Data Tier"]
PG[(PostgreSQL)]
RD[(Redis)]
end
subgraph workers["Worker Pool"]
W1["Worker 1"]
W2["Worker 2"]
WN["Worker N"]
end
LB --> A1
LB --> A2
LB --> A3
A1 --> PG
A2 --> PG
A3 --> PG
A1 --> RD
A2 --> RD
A3 --> RD
W1 --> LB
W2 --> LB
WN --> LB Managed (spooled.cloud)
The easiest way to get started. Sign up at spooled.cloud/signup and start queuing jobs immediately.
- ✓ No infrastructure to manage
- ✓ Automatic scaling and updates
- ✓ Built-in monitoring and alerting
- ✓ 99.9% SLA (Pro and Enterprise plans)
- ✓ Security-focused architecture and operational monitoring
Self-Hosted: Docker
The quickest way to self-host Spooled for development or small deployments.
Workers are separate
Spooled runs the queue + APIs, but it does not execute your business logic. You run one or more worker processes (your app code) that claim jobs and complete/fail them. Docker Compose starts Spooled + PostgreSQL + Redis; you add your own worker container(s) separately.
Quick Start
# Clone the repository
git clone https://github.com/spooled-cloud/spooled-backend.git
cd spooled-backend
# Copy example environment
cp .env.example .env
# Start with Docker Compose
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f spooled-api Docker Run (Single Container)
docker run -d \
--name spooled-backend \
-p 8080:8080 \ # REST API
-p 9090:9090 \ # Prometheus metrics
-p 50051:50051 \ # gRPC API (self-hosted) (HTTP/2 + Protobuf)
-e DATABASE_URL="postgres://user:pass@host:5432/spooled" \
-e REDIS_URL="redis://redis:6379" \
-e JWT_SECRET="your-production-secret" \
-e RUST_ENV="production" \
spooled-backend:latest Ports
| Port | Protocol | Description |
|---|---|---|
| 8080 | HTTP/1.1 | REST API, WebSocket, SSE |
| 9090 | HTTP/1.1 | Prometheus metrics |
| 50051 | HTTP/2 | gRPC API (self-hosted, TLS optional) |
| 443 | HTTP/2 | gRPC API (Spooled Cloud: grpc.spooled.cloud, TLS required) |
Docker Compose Configuration
The docker-compose.yml file configures the following services:
- spooled-api — REST API on port 8080, gRPC API on port 50051 (self-hosted)
- postgres — PostgreSQL 16 database for queue storage
- pgbouncer — Connection pooler for production workloads
- redis — Redis 7 for pub/sub and rate limiting
- prometheus — Metrics collection
- grafana — Dashboards on port 3000
See the GitHub repository for the full docker-compose.yml.
Self-Hosted: Kubernetes
For production deployments at scale, use Kubernetes with our Kustomize manifests.
Prerequisites
- Kubernetes 1.24+
- kubectl configured
- Helm 3.10+ (optional, for dependencies)
- PostgreSQL 14+ (managed like RDS/Cloud SQL or self-hosted)
- Redis 7+ (managed like ElastiCache/Memorystore or self-hosted)
Installation
# Create namespace
kubectl create namespace spooled-cloud
# Create secrets
kubectl create secret generic spooled-secrets \
--namespace spooled-cloud \
--from-literal=database-url='postgres://user:pass@postgres:5432/spooled' \
--from-literal=redis-url='redis://redis:6379' \
--from-literal=jwt-secret='your-production-secret'
# Deploy with Kustomize
kubectl apply -k k8s/overlays/production
# Verify deployment
kubectl get pods -n spooled-cloud Helm Chart
# Helm chart status
# The official Helm chart is not published yet.
# Track progress / updates in GitHub Discussions:
# https://github.com/orgs/Spooled-Cloud/discussions
#
# For Kubernetes today, use Docker Compose as a baseline and translate to:
# - Deployment for spooled-api (stateless)
# - Service + Ingress (WebSocket/SSE compatible)
# - Managed PostgreSQL + Redis (recommended)
#
# If you need a chart urgently, open a discussion and we'll prioritize it. Configuration
Create a custom values.yaml to configure:
- replicaCount — Number of API replicas
- ingress — Ingress settings for external access
- resources — CPU and memory limits
- autoscaling — HPA configuration
- secrets — External secrets reference
Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 Self-Hosted: Bare Metal
For maximum control, run Spooled directly on servers.
Build from Source
# Requirements: Rust 1.75+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Clone and build
git clone https://github.com/spooled-cloud/spooled-backend.git
cd spooled-backend
cargo build --release
# Binary at target/release/spooled-backend System Service
Configure systemd to manage the Spooled service with automatic restart on failure. See the repository for example systemd unit files.
Reverse Proxy
Configure Nginx or Caddy as a reverse proxy with TLS termination. Ensure:
- WebSocket upgrade support for
/api/v1/ws - Buffering disabled for SSE at
/api/v1/events - Proper proxy headers (Host, X-Real-IP, X-Forwarded-For)
- gRPC proxying: expose your internal gRPC service on 443 (HTTP/2 required) in production
Cloudflare Tunnel for gRPC
When using Cloudflare Tunnel to expose your gRPC server, HTTPS is required for HTTP/2 connections. Cloudflare Tunnel does not support HTTP/2 over plaintext HTTP.
Required Configuration:
- Service Type:
HTTPS - URL:
backend:50051 - HTTP2 Connection:
ON - No TLS Verify:
ON(required for self-signed certs)
The backend includes self-signed certificates (10-year validity) in ./certs/ that work with Cloudflare Tunnel's "No TLS Verify" option.
Environment Variables
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | PostgreSQL connection string |
REDIS_URL | Yes* | Redis connection (* optional for single-node) |
JWT_SECRET | Yes | 256-bit secret for JWT signing |
ADMIN_API_KEY | Yes | Initial admin API key |
HOST | No | Bind address (default: 0.0.0.0) |
PORT | No | REST API port (default: 8080) |
GRPC_PORT | No | gRPC API port (default: 50051) |
METRICS_PORT | No | Prometheus port (default: 9090) |
LOG_LEVEL | No | trace, debug, info, warn, error |
LOG_FORMAT | No | json or pretty |
RUST_ENV | No | development or production |
Monitoring & Alerting
Prometheus Metrics
Metrics are exposed at /metrics on port 9090. Key metrics to monitor:
| Metric | Description |
|---|---|
spooled_jobs_pending | Jobs waiting to be processed |
spooled_jobs_processing | Jobs currently being processed |
spooled_job_max_age_seconds | Oldest pending job age (critical!) |
spooled_jobs_enqueued_total | Total jobs enqueued |
spooled_jobs_completed_total | Total jobs completed |
spooled_jobs_failed_total | Total jobs failed |
spooled_workers_active | Active workers |
spooled_api_request_duration | Request latency histogram |
Recommended Alerts
| Alert | Condition | Severity |
|---|---|---|
| JobQueueBacklog | Oldest job > 5 min | Warning |
| JobQueueBacklogCritical | Oldest job > 15 min | Critical |
| HighJobFailureRate | Failure rate > 10% | Warning |
| NoHealthyWorkers | No healthy workers | Critical |
| HighAPILatency | P99 > 1 second | Warning |
| JobsDeadlettered | >5 DLQ jobs in 15 min | Warning |
Scaling Guidelines
Resource Guidelines
| Load | Memory | CPU | Replicas |
|---|---|---|---|
| < 100 req/s | 256-512 Mi | 250-500m | 1-2 |
| 100-1000 req/s | 512Mi-1Gi | 500-1000m | 2-5 |
| > 1000 req/s | 1-2 Gi | 1000-2000m | 5+ |
Database Scaling
| Load | Recommendation |
|---|---|
| < 1K req/s | Single PostgreSQL instance |
| 1K-10K req/s | PostgreSQL with PgBouncer |
| > 10K req/s | Read replicas + connection pooling |
Production Checklist
- ☑ TLS/HTTPS — All traffic encrypted
- ☑ Change JWT_SECRET — Use a unique 256-bit secret
- ☑ Database backups — Automated daily backups with point-in-time recovery
- ☑ Connection pooling — PgBouncer for production workloads
- ☑ Monitoring — Prometheus metrics scraped, Grafana dashboards
- ☑ Alerting — Alerts for high error rates, queue depth, DLQ growth
- ☑ Log aggregation — Centralized logging (Loki, CloudWatch, Datadog)
- ☑ Secret management — Secrets in Vault, AWS Secrets Manager, or K8s secrets
- ☑ Health checks — Load balancer configured with /health endpoint
- ☑ Horizontal scaling — Multiple API instances behind load balancer
- ☑ Set RUST_ENV=production — Enables production optimizations
Database Requirements
PostgreSQL
- Version: 14+ (16 recommended)
- Extensions: pgcrypto (auto-enabled)
- Connections: At least 20 per API instance
- Storage: SSD recommended for queue performance
Redis
- Version: 7+ (for function support)
- Memory: At least 256MB for small deployments
- Persistence: RDB or AOF for durability
- HA: Redis Sentinel or Cluster for production
Troubleshooting
Database Connection Errors
- Verify
DATABASE_URLis correct - Check network connectivity to database
- Ensure database is accepting connections
- Check connection pool settings
Redis Connection Errors
- Verify
REDIS_URLis correct - Application will run without Redis (degraded mode)
- Real-time features require Redis
Slow Job Processing
- Check
spooled_job_max_age_secondsmetric - Add more workers if queue is backing up
- Review job timeout settings
- Check for database bottlenecks