Production Deployment
This guide covers deploying Flowstate to production using Docker images hosted on a container registry. The platform uses a multi-stage build process with optimized layer caching for fast, reliable deployments.
Architecture Overview
Section titled “Architecture Overview”Flowstate production architecture consists of:
| Service | Runtime | Description | Public Access |
|---|---|---|---|
| Backend | Node.js 22 | Express REST API (internal only) | No |
| Gateway | Node.js 22 | Connect RPC service (public) | Yes |
| Web | Node.js 22 SSR | Astro standalone server (main app) | Yes |
| Admin | Node.js 22 SSR | Astro standalone server (admin dashboard) | Yes |
| MongoDB | MongoDB 7 | Replica set database | No |
| Redis | Redis 7 Alpine | Cache and pub/sub | No |
| Storage | S3-compatible | Object storage (AWS S3, DO Spaces, R2) | Yes (public URLs) |
Network topology:
Internet ├──> Web (app.your-domain.com) ──┐ ├──> Admin (admin.your-domain.com) ──┼──> Backend (internal) ──┬──> MongoDB ├──> Gateway (rpc.your-domain.com) ──┘ └──> Redis └──> Storage (storage.your-domain.com)Docker Images
Section titled “Docker Images”Production images are built via GitHub Actions and pushed to your container registry:
| Image | Description |
|---|---|
| Backend | REST API server |
| Web | Astro web frontend |
| Admin | Admin dashboard |
| Docs | Documentation site |
Note: The Gateway does not have its own image. It reuses the backend image with
command: ['node', 'dist/gateway.js']in the production compose file.
Tags:
latest— Latest build fromproductionbranchstaging— Latest build fromstagingbranchmain— Latest build frommainbranchdev— Latest build fromdevbranchv1.2.3— Specific version tag
Multi-Stage Builds
Section titled “Multi-Stage Builds”All images use multi-stage builds for optimization:
# Stage 1: DependenciesFROM node:22-alpine AS depsWORKDIR /appCOPY package.json pnpm-lock.yaml ./RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Stage 2: BuildFROM node:22-alpine AS builderWORKDIR /appCOPY --from=deps /app/node_modules ./node_modulesCOPY . .RUN pnpm build
# Stage 3: ProductionFROM node:22-alpine AS runnerWORKDIR /appENV NODE_ENV=productionCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesUSER nodeCMD ["node", "dist/index.js"]Benefits:
- Smaller final image (no build tools)
- Faster deployments (fewer layers to pull)
- Better security (minimal attack surface)
Build Optimization
Section titled “Build Optimization”The build process uses dual-layer caching for maximum speed:
1. Docker Registry Cache
Section titled “1. Docker Registry Cache”GitHub Actions pushes cache layers to the container registry:
- name: Build and push uses: docker/build-push-action@v5 with: cache-from: type=registry,ref=your-registry/flowstate/backend:buildcache cache-to: type=registry,ref=your-registry/flowstate/backend:buildcache,mode=max2. GitHub Actions Cache
Section titled “2. GitHub Actions Cache”pnpm store is cached between workflow runs:
- name: Cache pnpm store uses: actions/cache@v3 with: path: ~/.pnpm-store key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}Layer Ordering
Section titled “Layer Ordering”Layers are ordered from least to most frequently changed:
# 1. Base image (changes rarely)FROM node:22-alpine
# 2. System dependencies (changes rarely)RUN apk add --no-cache curl
# 3. Package files (changes occasionally)COPY package.json pnpm-lock.yaml ./
# 4. Dependencies (changes occasionally)RUN pnpm install --frozen-lockfile
# 5. Source code (changes frequently)COPY . .
# 6. Build (changes frequently)RUN pnpm buildBranching Strategy
Section titled “Branching Strategy”Flowstate uses a four-tier branching model:
dev → main → staging → production| Branch | Purpose | Auto-Deploy | Image Tag |
|---|---|---|---|
dev | Active development | No | dev |
main | Stable, tested code | No | main |
staging | Pre-production validation | Yes (staging env) | staging |
production | Live release | Yes (production env) | latest, v1.2.3 |
Promotion flow:
- Feature branches merge to
devvia PR devmerges tomainwhen stable (weekly)mainmerges tostagingfor pre-production testingstagingmerges toproductionafter validation
Never skip tiers — always promote through each environment.
CI/CD Pipeline
Section titled “CI/CD Pipeline”GitHub Actions builds and pushes images on every push to dev, main, staging, or production:
name: Build and Push Docker Images
on: push: branches: [dev, main, staging, production] tags: ['v*']
jobs: build: runs-on: ubuntu-latest strategy: matrix: service: [backend, web, admin, docs] steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: your-registry username: ${{ github.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - uses: docker/build-push-action@v5 with: context: . file: ./apps/${{ matrix.service }}/Dockerfile push: true tags: your-registry/flowstate/${{ matrix.service }}:${{ github.ref_name }} cache-from: type=registry,ref=your-registry/flowstate/${{ matrix.service }}:buildcache cache-to: type=registry,ref=your-registry/flowstate/${{ matrix.service }}:buildcache,mode=maxBuild time:
- Cold build (no cache): ~8 minutes
- Warm build (with cache): ~2 minutes
Deployment Methods
Section titled “Deployment Methods”Option 1: Docker Compose (Simple)
Section titled “Option 1: Docker Compose (Simple)”For small deployments on a single server:
version: '3.8'
services: backend: image: your-registry/flowstate/backend:latest environment: - DATABASE_URL=${DATABASE_URL} - REDIS_URL=${REDIS_URL} - JWT_SECRET=${JWT_SECRET} depends_on: - mongodb - redis restart: unless-stopped
web: image: your-registry/flowstate/web:latest environment: - SERVER_API_URL=http://backend:3000 ports: - '80:4321' depends_on: - backend restart: unless-stopped
mongodb: image: mongo:7 command: --replSet rs0 volumes: - mongodb_data:/data/db restart: unless-stopped
redis: image: redis:7-alpine volumes: - redis_data:/data restart: unless-stopped
volumes: mongodb_data: redis_data:Deploy:
# Pull latest imagesdocker compose pull
# Start servicesdocker compose up -d
# View logsdocker compose logs -fOption 2: Docker Swarm (Scalable)
Section titled “Option 2: Docker Swarm (Scalable)”For multi-node deployments with load balancing:
version: '3.8'
services: backend: image: your-registry/flowstate/backend:latest deploy: replicas: 3 update_config: parallelism: 1 delay: 10s restart_policy: condition: on-failure environment: - DATABASE_URL=${DATABASE_URL} - REDIS_URL=${REDIS_URL} networks: - flowstate
web: image: your-registry/flowstate/web:latest deploy: replicas: 2 ports: - '80:4321' networks: - flowstate
networks: flowstate: driver: overlayDeploy:
# Initialize swarmdocker swarm init
# Deploy stackdocker stack deploy -c docker-compose.prod.yml flowstate
# Scale servicesdocker service scale flowstate_backend=5
# Update servicedocker service update --image your-registry/flowstate/backend:v1.2.3 flowstate_backendOption 3: Kubernetes (Enterprise)
Section titled “Option 3: Kubernetes (Enterprise)”For large-scale deployments with auto-scaling:
apiVersion: apps/v1kind: Deploymentmetadata: name: backendspec: replicas: 3 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: your-registry/flowstate/backend:latest ports: - containerPort: 3000 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: flowstate-secrets key: database-url resources: requests: memory: '512Mi' cpu: '500m' limits: memory: '1Gi' cpu: '1000m' livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 10 periodSeconds: 5Deploy:
# Apply manifestskubectl apply -f k8s/
# Check statuskubectl get podskubectl get services
# View logskubectl logs -f deployment/backend
# Scalekubectl scale deployment backend --replicas=5Health Checks
Section titled “Health Checks”All services expose a health endpoint for monitoring:
| Service | Endpoint | Description |
|---|---|---|
| Backend | /health | Checks MongoDB + Redis connectivity |
| Web | /health | Proxies to backend health check |
| Admin | /health | Proxies to backend health check |
| Gateway | /health | Lightweight liveness probe |
| Docs | /healthz | Nginx liveness probe (static site) |
Backend health response:
{ "status": "ok", "timestamp": "2026-02-28T10:30:00.000Z", "services": { "database": { "status": "healthy", "latency": 12 }, "redis": { "status": "healthy", "latency": 3 }, "storage": { "status": "healthy", "latency": 45 } }, "uptime": 86400}Security
Section titled “Security”Non-Root User
Section titled “Non-Root User”All images run as the node user (UID 1000):
USER nodeNever run containers as root in production.
Read-Only Filesystem
Section titled “Read-Only Filesystem”Mount the root filesystem as read-only:
services: backend: read_only: true tmpfs: - /tmp - /app/.cacheSecrets Management
Section titled “Secrets Management”Never commit secrets to the repository. Use environment variables or secret management tools:
- Docker Compose:
.envfile (gitignored) - Docker Swarm:
docker secret create - Kubernetes:
kubectl create secret - Cloud providers: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault
Network Isolation
Section titled “Network Isolation”Only expose necessary ports:
services: backend: # No ports exposed (internal only)
web: ports: - '80:4321' # Only web port exposedMonitoring
Section titled “Monitoring”Centralize logs using a logging driver:
services: backend: logging: driver: 'json-file' options: max-size: '10m' max-file: '3'Production logging solutions:
- Loki + Grafana — Self-hosted
- Datadog — SaaS
- CloudWatch Logs — AWS
- Stackdriver — GCP
Metrics
Section titled “Metrics”Expose Prometheus metrics from the backend /metrics endpoint and scrape with Prometheus:
scrape_configs: - job_name: 'flowstate-backend' static_configs: - targets: ['backend:3000']Alerts
Section titled “Alerts”Set up alerts for critical issues:
- Service down (health check fails)
- High error rate (>5% of requests)
- High latency (p95 >1s)
- Database connection failures
- Redis connection failures
- Disk space low (<10% free)
Backup and Recovery
Section titled “Backup and Recovery”Database Backups
Section titled “Database Backups”Automated daily backups:
#!/bin/bashDATE=$(date +%Y%m%d_%H%M%S)BACKUP_DIR="/backups/mongodb"
docker compose exec -T mongodb mongodump --archive > "$BACKUP_DIR/backup_$DATE.archive"
# Keep only last 7 daysfind "$BACKUP_DIR" -name "backup_*.archive" -mtime +7 -deleteRestore:
docker compose exec -T mongodb mongorestore --archive < backup_20260228_103000.archiveRedis Backups
Section titled “Redis Backups”Redis uses AOF (Append-Only File) for persistence:
services: redis: command: redis-server --appendonly yes volumes: - redis_data:/dataBackup:
docker compose exec redis redis-cli BGSAVEdocker compose cp redis:/data/dump.rdb ./backup/Storage Backups
Section titled “Storage Backups”S3-compatible storage handles replication automatically. For additional safety:
- Enable versioning on buckets
- Configure lifecycle policies
- Set up cross-region replication
Desktop App Builds
Section titled “Desktop App Builds”Desktop builds are triggered by pushing a version tag:
git tag v1.2.3git push origin v1.2.3GitHub Actions builds cross-platform Electron apps:
| Platform | Artifact | Size |
|---|---|---|
| macOS (Intel) | Flowstate-1.2.3-x64.dmg | ~150MB |
| macOS (Apple Silicon) | Flowstate-1.2.3-arm64.dmg | ~150MB |
| Windows | Flowstate-Setup-1.2.3.exe | ~120MB |
| Linux (AppImage) | Flowstate-1.2.3.AppImage | ~140MB |
Auto-update configuration:
import { autoUpdater } from 'electron-updater';
autoUpdater.setFeedURL({ provider: 's3', bucket: process.env.DESKTOP_S3_BUCKET, region: '<region>',});
autoUpdater.checkForUpdatesAndNotify();Rollback
Section titled “Rollback”If a deployment fails, rollback to the previous version:
Docker Compose:
# Update docker-compose.yml to use the previous tag, then restartdocker compose up -dDocker Swarm:
docker service update --image your-registry/flowstate/backend:v1.2.2 flowstate_backendKubernetes:
kubectl rollout undo deployment/backendPerformance Tuning
Section titled “Performance Tuning”Node.js Optimization
Section titled “Node.js Optimization”ENV NODE_ENV=productionENV NODE_OPTIONS="--max-old-space-size=2048"MongoDB Optimization
Section titled “MongoDB Optimization”services: mongodb: command: --wiredTigerCacheSizeGB 2 --replSet rs0Redis Optimization
Section titled “Redis Optimization”services: redis: command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lruResource Limits
Section titled “Resource Limits”services: backend: deploy: resources: limits: cpus: '1.0' memory: 1G reservations: cpus: '0.5' memory: 512MChecklist
Section titled “Checklist”Before deploying to production:
- All environment variables configured
- Secrets stored securely (not in code)
- Health checks passing
- Database backups configured
- Monitoring and alerts set up
- SSL/TLS certificates installed
- CORS origins restricted (not
*) - Rate limiting enabled
- Logs centralized
- Rollback plan documented
- Load testing completed
- Security audit passed
- Documentation updated
Troubleshooting
Section titled “Troubleshooting”Image pull failures
Section titled “Image pull failures”# Authenticate with your container registryecho $REGISTRY_TOKEN | docker login your-registry -u USERNAME --password-stdin
# Pull manuallydocker pull your-registry/flowstate/backend:latestService won’t start
Section titled “Service won’t start”# Check logsdocker compose logs backend
# Check healthdocker compose ps
# Restart servicedocker compose restart backendDatabase connection errors
Section titled “Database connection errors”# Verify MongoDB is runningdocker compose ps mongodb
# Check replica set statusdocker compose exec mongodb mongosh --eval "rs.status()"
# Verify connection string (check for typos or missing replicaSet param)echo $DATABASE_URLHigh memory usage
Section titled “High memory usage”# Check container statsdocker stats
# Increase memory limits in your compose or deployment manifestSupport
Section titled “Support”For deployment issues:
- Check logs:
docker compose logs -f - Verify health endpoints are returning 200
- Review documentation:
/docs/deployment - Contact DevOps team: [email protected]