đ [WIP] Deployment Guide
This guide covers deploying BunSqStat in various environments, from development to production.
Overview
Section titled âOverviewâBunSqStat supports multiple deployment strategies:
- Docker Compose (recommended for most users)
- Kubernetes (for large-scale deployments)
- Manual deployment (for custom setups)
- Cloud platforms (AWS, GCP, Azure)
Prerequisites
Section titled âPrerequisitesâ- Docker and Docker Compose (for containerized deployment)
- Bun runtime (for manual deployment)
- Redis Stack server
- Access to Squid log files
- SSL certificates (for HTTPS in production)
Docker Compose Deployment (Recommended)
Section titled âDocker Compose Deployment (Recommended)âQuick Start
Section titled âQuick Startâ# Clone repositorygit clone https://github.com/francyfox/BunSqStat.gitcd BunSqStat
# Copy production environmentcp .env.production .env
# Edit environment variablesnano .env
# Deploy all servicesdocker-compose -f docker-compose.prod.yml up -d
# Check service statusdocker-compose ps
# View logsdocker-compose logs -f bunsqstat-serverProduction Docker Compose
Section titled âProduction Docker Composeâversion: '3.8'
services: redis: image: redis/redis-stack-server:7.2-latest container_name: bunsqstat-redis restart: unless-stopped ports: - "127.0.0.1:6379:6379" volumes: - redis_data:/data - ./docker/redis.conf:/usr/local/etc/redis/redis.conf:ro command: redis-server /usr/local/etc/redis/redis.conf environment: - REDIS_PASSWORD=${REDIS_PASSWORD} healthcheck: test: ["CMD", "redis-cli", "--no-auth-warning", "-a", "${REDIS_PASSWORD:-}", "ping"] interval: 30s timeout: 10s retries: 5 start_period: 30s networks: - bunsqstat
server: build: context: . dockerfile: apps/server/Dockerfile args: - NODE_ENV=production container_name: bunsqstat-server restart: unless-stopped ports: - "127.0.0.1:3001:3001" depends_on: redis: condition: service_healthy environment: - NODE_ENV=production - PORT=3001 - REDIS_HOST=redis - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD} - SQUID_LOG_PATH=${SQUID_LOG_PATH} - LOG_LEVEL=${LOG_LEVEL:-warn} volumes: - "${SQUID_LOG_PATH}:${SQUID_LOG_PATH}:ro" - app_logs:/var/log/bunsqstat healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s networks: - bunsqstat
web: build: context: . dockerfile: apps/web/Dockerfile args: - NODE_ENV=production - API_BASE_URL=http://server:3001 container_name: bunsqstat-web restart: unless-stopped ports: - "127.0.0.1:3000:80" depends_on: server: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s networks: - bunsqstat
nginx: image: nginx:alpine container_name: bunsqstat-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./docker/nginx/sites/:/etc/nginx/conf.d/:ro - ./ssl:/etc/nginx/ssl:ro - nginx_logs:/var/log/nginx depends_on: - web - server networks: - bunsqstat
volumes: redis_data: driver: local app_logs: driver: local nginx_logs: driver: local
networks: bunsqstat: driver: bridgeNginx Configuration
Section titled âNginx Configurationâupstream backend { server bunsqstat-server:3001;}
upstream frontend { server bunsqstat-web:80;}
# HTTP redirect to HTTPSserver { listen 80; server_name your-domain.com www.your-domain.com;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / { return 301 https://$server_name$request_uri; }}
# HTTPS serverserver { listen 443 ssl http2; server_name your-domain.com www.your-domain.com;
# SSL configuration ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off;
# Security headers add_header Strict-Transport-Security "max-age=63072000" always; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "strict-origin-when-cross-origin";
# Gzip compression gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# API routes location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade;
# Timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; }
# Health checks location /health { proxy_pass http://backend/health; access_log off; }
# Frontend routes location / { proxy_pass http://frontend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade;
# Handle client-side routing try_files $uri $uri/ /index.html; }
# Static assets with long cache location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { proxy_pass http://frontend; expires 1y; add_header Cache-Control "public, immutable"; }}Kubernetes Deployment
Section titled âKubernetes DeploymentâNamespace and ConfigMap
Section titled âNamespace and ConfigMapâapiVersion: v1kind: Namespacemetadata: name: bunsqstat
---# k8s/configmap.ymlapiVersion: v1kind: ConfigMapmetadata: name: bunsqstat-config namespace: bunsqstatdata: NODE_ENV: "production" LOG_LEVEL: "warn" REDIS_HOST: "bunsqstat-redis" REDIS_PORT: "6379" SEARCH_INDEX_NAME: "log_idx" MAX_SEARCH_RESULTS: "1000"Redis Deployment
Section titled âRedis DeploymentâapiVersion: apps/v1kind: Deploymentmetadata: name: bunsqstat-redis namespace: bunsqstatspec: replicas: 1 selector: matchLabels: app: bunsqstat-redis template: metadata: labels: app: bunsqstat-redis spec: containers: - name: redis image: redis/redis-stack-server:7.2-latest ports: - containerPort: 6379 env: - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: bunsqstat-secrets key: redis-password volumeMounts: - name: redis-data mountPath: /data - name: redis-config mountPath: /usr/local/etc/redis/redis.conf subPath: redis.conf resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "2Gi" cpu: "1000m" volumes: - name: redis-data persistentVolumeClaim: claimName: redis-pvc - name: redis-config configMap: name: redis-config
---apiVersion: v1kind: Servicemetadata: name: bunsqstat-redis namespace: bunsqstatspec: selector: app: bunsqstat-redis ports: - port: 6379 targetPort: 6379
---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: redis-pvc namespace: bunsqstatspec: accessModes: - ReadWriteOnce resources: requests: storage: 10GiApplication Deployment
Section titled âApplication DeploymentâapiVersion: apps/v1kind: Deploymentmetadata: name: bunsqstat-server namespace: bunsqstatspec: replicas: 3 selector: matchLabels: app: bunsqstat-server template: metadata: labels: app: bunsqstat-server spec: containers: - name: server image: bunsqstat-server:latest ports: - containerPort: 3001 envFrom: - configMapRef: name: bunsqstat-config env: - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: bunsqstat-secrets key: redis-password volumeMounts: - name: squid-logs mountPath: /var/log/squid readOnly: true resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 3001 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 3001 initialDelaySeconds: 5 periodSeconds: 5 volumes: - name: squid-logs hostPath: path: /var/log/squid type: Directory
---apiVersion: v1kind: Servicemetadata: name: bunsqstat-server namespace: bunsqstatspec: selector: app: bunsqstat-server ports: - port: 3001 targetPort: 3001
---apiVersion: apps/v1kind: Deploymentmetadata: name: bunsqstat-web namespace: bunsqstatspec: replicas: 2 selector: matchLabels: app: bunsqstat-web template: metadata: labels: app: bunsqstat-web spec: containers: - name: web image: bunsqstat-web:latest ports: - containerPort: 80 resources: requests: memory: "64Mi" cpu: "50m" limits: memory: "128Mi" cpu: "200m"
---apiVersion: v1kind: Servicemetadata: name: bunsqstat-web namespace: bunsqstatspec: selector: app: bunsqstat-web ports: - port: 80 targetPort: 80Ingress Configuration
Section titled âIngress ConfigurationâapiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: bunsqstat-ingress namespace: bunsqstat annotations: kubernetes.io/ingress.class: "nginx" cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/rate-limit: "100" nginx.ingress.kubernetes.io/rate-limit-window: "1m"spec: tls: - hosts: - bunsqstat.your-domain.com secretName: bunsqstat-tls rules: - host: bunsqstat.your-domain.com http: paths: - path: /api pathType: Prefix backend: service: name: bunsqstat-server port: number: 3001 - path: / pathType: Prefix backend: service: name: bunsqstat-web port: number: 80Cloud Platform Deployment
Section titled âCloud Platform DeploymentâAWS ECS with Fargate
Section titled âAWS ECS with Fargateâ{ "family": "bunsqstat", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "1024", "memory": "2048", "executionRoleArn": "arn:aws:iam::ACCOUNT:role/ecsTaskExecutionRole", "taskRoleArn": "arn:aws:iam::ACCOUNT:role/ecsTaskRole", "containerDefinitions": [ { "name": "redis", "image": "redis/redis-stack-server:7.2-latest", "portMappings": [ { "containerPort": 6379, "protocol": "tcp" } ], "essential": true, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/bunsqstat", "awslogs-region": "us-west-2", "awslogs-stream-prefix": "redis" } } }, { "name": "server", "image": "ACCOUNT.dkr.ecr.us-west-2.amazonaws.com/bunsqstat-server:latest", "portMappings": [ { "containerPort": 3001, "protocol": "tcp" } ], "essential": true, "dependsOn": [ { "containerName": "redis", "condition": "START" } ], "environment": [ { "name": "NODE_ENV", "value": "production" }, { "name": "REDIS_HOST", "value": "localhost" } ], "secrets": [ { "name": "REDIS_PASSWORD", "valueFrom": "arn:aws:ssm:us-west-2:ACCOUNT:parameter/bunsqstat/redis-password" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/bunsqstat", "awslogs-region": "us-west-2", "awslogs-stream-prefix": "server" } } }, { "name": "web", "image": "ACCOUNT.dkr.ecr.us-west-2.amazonaws.com/bunsqstat-web:latest", "portMappings": [ { "containerPort": 80, "protocol": "tcp" } ], "essential": true, "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/bunsqstat", "awslogs-region": "us-west-2", "awslogs-stream-prefix": "web" } } } ]}Google Cloud Run
Section titled âGoogle Cloud RunâapiVersion: serving.knative.dev/v1kind: Servicemetadata: name: bunsqstat-server annotations: run.googleapis.com/ingress: all run.googleapis.com/execution-environment: gen2spec: template: metadata: annotations: autoscaling.knative.dev/maxScale: "10" autoscaling.knative.dev/minScale: "1" run.googleapis.com/cpu-throttling: "false" spec: containerConcurrency: 100 containers: - image: gcr.io/PROJECT_ID/bunsqstat-server:latest ports: - containerPort: 3001 env: - name: NODE_ENV value: "production" - name: REDIS_HOST value: "10.0.0.3" # Cloud Memorystore Redis IP - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: redis-password key: password resources: limits: cpu: "1000m" memory: "2Gi" startupProbe: httpGet: path: /health port: 3001 timeoutSeconds: 240 periodSeconds: 240 failureThreshold: 1 livenessProbe: httpGet: path: /health port: 3001Manual Deployment
Section titled âManual DeploymentâYou can use this latest artifacts from build pipeline
System Requirements
Section titled âSystem Requirementsâ# Install Buncurl -fsSL https://bun.sh/install | bash
# Install Node.js (fallback)curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -sudo apt-get install -y nodejs
# Install Redis Stackcurl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpgecho "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.listsudo apt-get updatesudo apt-get install redis-stack-server
# Install Nginx (optional)sudo apt-get install nginx
# Install PM2 (process manager)npm install -g pm2Application Setup
Section titled âApplication Setupâ# Clone and buildcurl -O https://github.com/francyfox/BunSqStat/releases/download/v0.1.6/bunsqstat.zipunzip bunsqstat.zip
# Set up environmentcp .env.production .envnano .env
# Create system usersudo useradd -r -s /bin/false bunsqstatsudo usermod -a -G adm bunsqstat # For log file access
# Set up directoriessudo mkdir -p /opt/bunsqstatsudo mkdir -p /var/log/bunsqstatsudo mkdir -p /etc/bunsqstat
# Copy filessudo cp -r . /opt/bunsqstat/sudo chown -R bunsqstat:bunsqstat /opt/bunsqstatsudo chown -R bunsqstat:bunsqstat /var/log/bunsqstat
# Run# Use pm2 for starting demon process for backend service# https://bun.com/guides/ecosystem/pm2# Serve frontend files with nginx/apache/caddySystemd Services
Section titled âSystemd Servicesâ[Unit]Description=BunSqStat ServerAfter=network.target redis.serviceWants=redis.service
[Service]Type=simpleUser=bunsqstatGroup=bunsqstatWorkingDirectory=/opt/bunsqstat/apps/serverExecStart=/home/bunsqstat/.bun/bin/bun run dist/server.jsRestart=alwaysRestartSec=10Environment=NODE_ENV=productionEnvironmentFile=/etc/bunsqstat/.env
# LoggingStandardOutput=journalStandardError=journalSyslogIdentifier=bunsqstat-server
# SecurityNoNewPrivileges=yesProtectSystem=strictProtectHome=yesReadWritePaths=/var/log/bunsqstat /tmp
[Install]WantedBy=multi-user.target[Unit]Description=BunSqStat Web ServerAfter=network.target
[Service]Type=simpleUser=www-dataGroup=www-dataWorkingDirectory=/opt/bunsqstat/apps/webExecStart=/usr/bin/serve -s dist -l 3000Restart=alwaysRestartSec=10
[Install]WantedBy=multi-user.targetPM2 Configuration
Section titled âPM2 Configurationâmodule.exports = { apps: [ { name: 'bunsqstat-server', script: 'bun', args: 'run dist/server.js', cwd: '/opt/bunsqstat/apps/server', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'production', PORT: 3001 }, log_file: '/var/log/bunsqstat/server.log', error_file: '/var/log/bunsqstat/server-error.log', out_file: '/var/log/bunsqstat/server-out.log', merge_logs: true, max_memory_restart: '1G', node_args: '--max-old-space-size=1024' }, { name: 'bunsqstat-web', script: 'serve', args: '-s dist -l 3000', cwd: '/opt/bunsqstat/apps/web', instances: 2, env: { NODE_ENV: 'production' } } ]}SSL/TLS Configuration
Section titled âSSL/TLS ConfigurationâLetâs Encrypt with Certbot
Section titled âLetâs Encrypt with Certbotâ# Install Certbotsudo apt install certbot python3-certbot-nginx
# Obtain certificatesudo certbot --nginx -d your-domain.com -d www.your-domain.com
# Auto-renewalsudo crontab -e# Add: 0 12 * * * /usr/bin/certbot renew --quietManual SSL Setup
Section titled âManual SSL Setupâ# Generate private keyopenssl genrsa -out /etc/ssl/private/bunsqstat.key 4096
# Generate certificate signing requestopenssl req -new -key /etc/ssl/private/bunsqstat.key -out bunsqstat.csr
# Install certificate filessudo cp your-certificate.crt /etc/ssl/certs/bunsqstat.crtsudo cp ca-bundle.crt /etc/ssl/certs/bunsqstat-ca-bundle.crt
# Set permissionssudo chmod 600 /etc/ssl/private/bunsqstat.keysudo chmod 644 /etc/ssl/certs/bunsqstat.crtMonitoring and Logging
Section titled âMonitoring and LoggingâLog Aggregation
Section titled âLog Aggregationâ<source> @type forward port 24224 bind 0.0.0.0</source>
<match bunsqstat.**> @type elasticsearch host elasticsearch port 9200 index_name bunsqstat type_name logs</match>Health Monitoring
Section titled âHealth Monitoringâglobal: scrape_interval: 15s
scrape_configs: - job_name: 'bunsqstat' static_configs: - targets: ['bunsqstat-server:3001'] metrics_path: '/metrics' scrape_interval: 30sBackup Strategy
Section titled âBackup Strategyâ#!/bin/bash# Redis data backupredis-cli --rdb /backups/redis/dump-$(date +%Y%m%d-%H%M%S).rdb
# Application logs backuptar -czf /backups/logs/app-logs-$(date +%Y%m%d).tar.gz /var/log/bunsqstat/
# Configuration backupcp /etc/bunsqstat/.env /backups/config/env-$(date +%Y%m%d)
# Retention (keep 30 days)find /backups -type f -mtime +30 -deletePerformance Optimization
Section titled âPerformance OptimizationâRedis Tuning
Section titled âRedis Tuningâ# /etc/redis/redis.conf (production)maxmemory 4gbmaxmemory-policy allkeys-lrusave 900 1save 300 10save 60 10000maxclients 10000tcp-keepalive 300timeout 300System Limits
Section titled âSystem Limitsâbunsqstat soft nofile 65536bunsqstat hard nofile 65536bunsqstat soft nproc 32768bunsqstat hard nproc 32768
# /etc/sysctl.confnet.core.somaxconn = 65536net.ipv4.tcp_max_syn_backlog = 65536vm.overcommit_memory = 1Troubleshooting
Section titled âTroubleshootingâCommon Issues
Section titled âCommon Issuesâ# Service not startingsudo systemctl status bunsqstat-serversudo journalctl -u bunsqstat-server -f
# Redis connection issuesredis-cli pingsudo systemctl status redis
# File permission issuessudo chown -R bunsqstat:bunsqstat /opt/bunsqstatsudo chmod +r /var/log/squid/access.log
# Memory issuesfree -hsudo dmesg | grep -i memoryPerformance Issues
Section titled âPerformance Issuesâ# Check resource usagehtopiotopdocker stats
# Monitor Redisredis-cli --latency-historyredis-cli info memory
# Check disk spacedf -hdu -sh /var/log/*For more deployment scenarios and troubleshooting, see Configuration Guide and API Reference.