Deployment & DevOps
Complete guide to deploying and operating the BugBounty KSP platform in production environments.
π― Overview
This guide covers:
- Infrastructure setup
- Deployment strategies
- CI/CD pipelines
- Monitoring and logging
- Backup and disaster recovery
- Security best practices
ποΈ Infrastructure Architecture
Production Architecture
βββββββββββββββββββ
β CloudFlare β
β (CDN + DDoS) β
ββββββββββ¬βββββββββ
β
ββββββββββΌβββββββββ
β Load Balancer β
β (nginx/ALB) β
ββββββββββ¬βββββββββ
β
βββββββββββββββββββββΌββββββββββββββββββββ
β β β
ββββββΌβββββ ββββββΌβββββ ββββββΌβββββ
β Web β β Web β β Web β
β Server β β Server β β Server β
β (x3) β β (x3) β β (x3) β
ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ
β β β
βββββββββββββββββββββΌββββββββββββββββββββ
β
ββββββββββββββββΌβββββββββββββββ
β β β
ββββββΌβββββ ββββββΌβββββ ββββββΌβββββ
β Primary β β Redis β β Redis β
β DB β β Primary β β Replica β
ββββββ¬βββββ βββββββββββ βββββββββββ
β
ββββββΌβββββ
βReplica β
β DB β
βββββββββββ
π³ Docker Deployment
Dockerfile
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production stage
FROM node:18-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Copy built application
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./
USER nodejs
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "dist/index.js"]
Docker Compose
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/bugbounty_ksp
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
replicas: 3
resources:
limits:
cpus: '1'
memory: 1G
db:
image: postgres:14-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=bugbounty_ksp
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- web
restart: unless-stopped
volumes:
postgres_data:
redis_data:
Nginx Configuration
upstream web_backend {
least_conn;
server web:3000 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name bugbounty-ksp.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name bugbounty-ksp.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://web_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;
}
location /static/ {
alias /app/public/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /health {
proxy_pass http://web_backend;
access_log off;
}
}
βΈοΈ Kubernetes Deployment
Deployment Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: bugbounty-ksp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: bugbounty-ksp
template:
metadata:
labels:
app: bugbounty-ksp
spec:
containers:
- name: web
image: bugbounty-ksp:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-secret
key: url
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-secret
key: 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: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: bugbounty-ksp-service
namespace: production
spec:
selector:
app: bugbounty-ksp
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: bugbounty-ksp-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: bugbounty-ksp
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
π CI/CD Pipeline
GitHub Actions Workflow
name: Deploy to Production
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: $
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linters
run: npm run lint
- name: Run tests
run: npm test
- name: Run security audit
run: npm audit --audit-level=moderate
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: $
username: $
password: $
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: $/$
tags: |
type=ref,event=branch
type=sha,prefix=-
type=semver,pattern=
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: $
labels: $
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v0.1.10
with:
host: $
username: $
key: $
script: |
cd /opt/bugbounty-ksp
docker-compose pull
docker-compose up -d
docker system prune -af
- name: Health check
run: |
sleep 30
curl -f https://bugbounty-ksp.com/health || exit 1
- name: Notify deployment
if: success()
uses: 8398a7/action-slack@v3
with:
status: $
text: 'Deployment successful! π'
webhook_url: $
π Monitoring & Logging
Prometheus Configuration
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'bugbounty-ksp'
static_configs:
- targets: ['web:3000']
metrics_path: '/metrics'
Application Metrics
import prometheus from 'prom-client';
// Create metrics
const httpRequestDuration = new prometheus.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status'],
});
const httpRequestTotal = new prometheus.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status'],
});
// Middleware to track metrics
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration.labels(req.method, req.route?.path || req.path, res.statusCode).observe(duration);
httpRequestTotal.labels(req.method, req.route?.path || req.path, res.statusCode).inc();
});
next();
});
// Metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', prometheus.register.contentType);
res.end(await prometheus.register.metrics());
});
Logging with Winston
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'bugbounty-ksp' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
}));
}
export default logger;
πΎ Backup & Disaster Recovery
Automated Backups
#!/bin/bash
# backup.sh
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"
DB_NAME="bugbounty_ksp"
# Database backup
pg_dump -h localhost -U user $DB_NAME | gzip > "$BACKUP_DIR/db_$TIMESTAMP.sql.gz"
# Files backup
tar -czf "$BACKUP_DIR/files_$TIMESTAMP.tar.gz" /app/uploads
# Upload to S3
aws s3 cp "$BACKUP_DIR/db_$TIMESTAMP.sql.gz" s3://backups/bugbounty-ksp/
aws s3 cp "$BACKUP_DIR/files_$TIMESTAMP.tar.gz" s3://backups/bugbounty-ksp/
# Cleanup old backups (keep last 30 days)
find $BACKUP_DIR -name "*.gz" -mtime +30 -delete
Backup Schedule (Cron)
# Crontab
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
Restore Procedure
#!/bin/bash
# restore.sh
BACKUP_FILE=$1
# Stop application
docker-compose down
# Restore database
gunzip < $BACKUP_FILE | psql -h localhost -U user bugbounty_ksp
# Restart application
docker-compose up -d
echo "Restore completed"
π Security Best Practices
Environment Variables
Never commit sensitive data. Use:
- Docker secrets
- Kubernetes secrets
- AWS Parameter Store
- HashiCorp Vault
SSL/TLS Configuration
# Generate SSL certificate with Let's Encrypt
certbot certonly --webroot -w /var/www/html -d bugbounty-ksp.com
# Auto-renewal
0 0 1 * * certbot renew --quiet
Firewall Rules
# UFW configuration
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw enable
π Maintenance
Zero-Downtime Deployment
# Rolling update
docker-compose up -d --no-deps --build web
# Kubernetes rolling update
kubectl rollout restart deployment/bugbounty-ksp -n production
kubectl rollout status deployment/bugbounty-ksp -n production
Database Migrations
# Run migrations
npm run db:migrate
# Rollback if needed
npm run db:migrate:undo
π¨ Incident Response
Health Check Script
#!/bin/bash
# health-check.sh
URL="https://bugbounty-ksp.com/health"
SLACK_WEBHOOK="your_webhook_url"
if ! curl -f -s $URL > /dev/null; then
curl -X POST $SLACK_WEBHOOK \
-H 'Content-Type: application/json' \
-d '{"text":"π¨ BugBounty KSP is DOWN!"}'
fi
π Resources
| β Discord Integration | Home | Next: Contributing β |