Vapora/docs/CUSTOM_DEPLOYMENT_SERVER.md
Jesús Pérez 7110ffeea2
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
chore: extend doc: adr, tutorials, operations, etc
2026-01-12 03:32:47 +00:00

13 KiB

Custom Documentation Deployment Server

Complete guide for setting up and configuring custom deployment servers for mdBook documentation.

Overview

VAPORA supports multiple custom deployment methods:

  • SSH/SFTP — Direct file synchronization to remote servers
  • HTTP — API-based deployment with REST endpoints
  • Docker — Container registry deployment
  • AWS S3 — Cloud object storage with CloudFront CDN
  • Google Cloud Storage — GCS with cache control

🔐 Prerequisites

Repository Secrets Setup

Add these secrets to GitHub repository (SettingsSecrets and variablesActions):

Core Secrets (all methods)

DOCS_DEPLOY_METHOD        # ssh, sftp, http, docker, s3, gcs

SSH/SFTP Method

DOCS_DEPLOY_HOST          # docs.your-domain.com
DOCS_DEPLOY_USER          # docs (remote user)
DOCS_DEPLOY_PATH          # /var/www/vapora-docs
DOCS_DEPLOY_KEY           # SSH private key (base64 encoded)

HTTP Method

DOCS_DEPLOY_ENDPOINT      # https://deploy.your-domain.com/api/deploy
DOCS_DEPLOY_TOKEN         # Authentication bearer token

AWS S3 Method

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_DOCS_BUCKET           # vapora-docs-prod
AWS_REGION                # us-east-1

Google Cloud Storage Method

GCS_CREDENTIALS_FILE      # Service account JSON (base64 encoded)
GCS_DOCS_BUCKET           # vapora-docs-prod

Docker Registry Method

DOCKER_REGISTRY           # registry.your-domain.com
DOCKER_USERNAME
DOCKER_PASSWORD

📝 Deployment Script

The deployment script is located at: .scripts/deploy-docs.sh

Script Features

  • Supports 6 deployment methods
  • Pre-flight validation (connectivity, required files)
  • Automatic backups (SSH/SFTP)
  • Post-deployment verification
  • Detailed logging
  • Rollback capability (SSH)

Configuration Files

.scripts/
├── deploy-docs.sh                (Main deployment script)
├── .deploy-config.production     (Production config)
└── .deploy-config.staging        (Staging config)

Running Locally

# Build locally first
cd docs && mdbook build

# Deploy to production
bash .scripts/deploy-docs.sh production

# Deploy to staging
bash .scripts/deploy-docs.sh staging

# View logs
tail -f /tmp/docs-deploy-*.log

🔧 SSH/SFTP Deployment Setup

1. Create Deployment User on Remote Server

# SSH into your server
ssh user@docs.your-domain.com

# Create docs user
sudo useradd -m -d /var/www/vapora-docs -s /bin/bash docs

# Set up directory
sudo mkdir -p /var/www/vapora-docs/backups
sudo chown -R docs:docs /var/www/vapora-docs
sudo chmod 755 /var/www/vapora-docs

2. Configure SSH Key

# On your deployment server
sudo -u docs mkdir -p /var/www/vapora-docs/.ssh
sudo -u docs chmod 700 /var/www/vapora-docs/.ssh

# Create authorized_keys
sudo -u docs touch /var/www/vapora-docs/.ssh/authorized_keys
sudo -u docs chmod 600 /var/www/vapora-docs/.ssh/authorized_keys

3. Add Public Key to Server

# Locally, generate key (if needed)
ssh-keygen -t ed25519 -f ~/.ssh/vapora-docs -N ""

# Add to server's authorized_keys
cat ~/.ssh/vapora-docs.pub | ssh user@docs.your-domain.com \
  "sudo -u docs tee -a /var/www/vapora-docs/.ssh/authorized_keys"

# Test connection
ssh -i ~/.ssh/vapora-docs docs@docs.your-domain.com "ls -la"

4. Add to GitHub Secrets

# Encode private key (base64)
cat ~/.ssh/vapora-docs | base64 -w0 | pbcopy

# Paste into GitHub Secrets:
# Settings → Secrets → New repository secret
# Name: DOCS_DEPLOY_KEY
# Value: [paste base64-encoded key]

5. Add SSH Configuration Secrets

DOCS_DEPLOY_METHOD    = ssh
DOCS_DEPLOY_HOST      = docs.your-domain.com
DOCS_DEPLOY_USER      = docs
DOCS_DEPLOY_PATH      = /var/www/vapora-docs
DOCS_DEPLOY_KEY       = [base64-encoded private key]

6. Set Up Web Server

# On remote server, configure nginx
sudo tee /etc/nginx/sites-available/vapora-docs > /dev/null << 'EOF'
server {
    listen 80;
    server_name docs.your-domain.com;
    root /var/www/vapora-docs/docs;

    location / {
        index index.html;
        try_files $uri $uri/ /index.html;
    }

    location ~ \.(js|css|fonts|images)$ {
        expires 1h;
        add_header Cache-Control "public, immutable";
    }
}
EOF

# Enable site
sudo ln -s /etc/nginx/sites-available/vapora-docs \
  /etc/nginx/sites-enabled/vapora-docs

# Test and reload
sudo nginx -t && sudo systemctl reload nginx

🌐 HTTP API Deployment Setup

1. Create Deployment Endpoint

Implement an HTTP endpoint that accepts deployments:

# Example: Flask deployment API
from flask import Flask, request, jsonify
import tarfile
import os
from pathlib import Path

app = Flask(__name__)

DOCS_PATH = "/var/www/vapora-docs"
BACKUP_PATH = f"{DOCS_PATH}/backups"

@app.route('/api/deploy', methods=['POST'])
def deploy():
    # Verify token
    token = request.headers.get('Authorization', '').replace('Bearer ', '')
    if not verify_token(token):
        return {'error': 'Unauthorized'}, 401

    # Check for archive
    if 'archive' not in request.files:
        return {'error': 'No archive provided'}, 400

    archive = request.files['archive']

    # Create backup
    os.makedirs(BACKUP_PATH, exist_ok=True)
    backup_name = f"backup_{int(time.time())}"
    os.rename(f"{DOCS_PATH}/current",
              f"{BACKUP_PATH}/{backup_name}")

    # Extract archive
    os.makedirs(f"{DOCS_PATH}/current", exist_ok=True)
    with tarfile.open(fileobj=archive) as tar:
        tar.extractall(f"{DOCS_PATH}/current")

    # Update symlink
    os.symlink(f"{DOCS_PATH}/current", f"{DOCS_PATH}/docs")

    return {'status': 'deployed', 'backup': backup_name}, 200

@app.route('/health', methods=['GET'])
def health():
    return {'status': 'healthy'}, 200

def verify_token(token):
    # Implement your token verification
    return token == os.getenv('DEPLOY_TOKEN')

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000)

2. Configure Nginx Reverse Proxy

upstream deploy_api {
    server 127.0.0.1:5000;
}

server {
    listen 443 ssl http2;
    server_name deploy.your-domain.com;

    ssl_certificate /etc/letsencrypt/live/deploy.your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/deploy.your-domain.com/privkey.pem;

    # API endpoint
    location /api/deploy {
        proxy_pass http://deploy_api;
        client_max_body_size 100M;
    }

    # Health check
    location /health {
        proxy_pass http://deploy_api;
    }
}

3. Add GitHub Secrets

DOCS_DEPLOY_METHOD      = http
DOCS_DEPLOY_ENDPOINT    = https://deploy.your-domain.com/api/deploy
DOCS_DEPLOY_TOKEN       = your-secure-token

☁️ AWS S3 Deployment Setup

1. Create S3 Bucket and IAM User

# Create bucket
aws s3 mb s3://vapora-docs-prod --region us-east-1

# Create IAM user
aws iam create-user --user-name vapora-docs-deployer

# Create access key
aws iam create-access-key --user-name vapora-docs-deployer

# Create policy
cat > /tmp/s3-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::vapora-docs-prod",
        "arn:aws:s3:::vapora-docs-prod/*"
      ]
    }
  ]
}
EOF

# Attach policy
aws iam put-user-policy \
  --user-name vapora-docs-deployer \
  --policy-name S3Access \
  --policy-document file:///tmp/s3-policy.json

2. Configure CloudFront (Optional)

# Create distribution
aws cloudfront create-distribution \
  --origin-domain-name vapora-docs-prod.s3.amazonaws.com \
  --default-root-object index.html

3. Add GitHub Secrets

DOCS_DEPLOY_METHOD      = s3
AWS_ACCESS_KEY_ID       = AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY   = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_DOCS_BUCKET         = vapora-docs-prod
AWS_REGION              = us-east-1

🐳 Docker Registry Deployment Setup

1. Create Docker Registry

# Using Docker Registry (self-hosted)
docker run -d \
  -p 5000:5000 \
  --restart always \
  --name registry \
  -e REGISTRY_STORAGE_DELETE_ENABLED=true \
  registry:2

# Or use managed: AWS ECR, Docker Hub, etc.

2. Configure Registry Authentication

# Create credentials
echo "username:$(openssl passwd -crypt password)" > /auth/htpasswd

# Docker login
docker login registry.your-domain.com \
  -u username -p password

3. Add GitHub Secrets

DOCS_DEPLOY_METHOD      = docker
DOCKER_REGISTRY         = registry.your-domain.com
DOCKER_USERNAME         = username
DOCKER_PASSWORD         = password

🔔 Webhooks & Notifications

Slack Notification

Add webhook URL to secrets:

NOTIFICATION_WEBHOOK = https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX

Workflow sends JSON payload:

{
  "status": "success",
  "environment": "production",
  "commit": "abc123...",
  "branch": "main",
  "timestamp": "2026-01-12T14:30:00Z",
  "run_url": "https://github.com/vapora-platform/vapora/actions/runs/123"
}

Custom Webhook Handler

@app.route('/webhook/deployment', methods=['POST'])
def deployment_webhook():
    data = request.json

    if data['status'] == 'success':
        send_slack_message(f"✅ Docs deployed: {data['commit']}")
    else:
        send_slack_message(f"❌ Deployment failed: {data['commit']}")

    return {'ok': True}

🔄 Deployment Workflow

Automatic Deployment Flow

Push to main (docs/ changes)
    ↓
mdBook Build & Deploy Workflow
    ├─ Build (2-3s)
    ├─ Quality Check
    └─ Upload Artifact
    ↓
mdBook Publish Workflow (triggered)
    ├─ Download Artifact
    ├─ Deploy to Custom Server
    │  ├─ Pre-flight Checks
    │  ├─ Deployment Method
    │  │  ├─ SSH: rsync files + backup
    │  │  ├─ HTTP: upload tarball
    │  │  ├─ S3: sync to bucket
    │  │  └─ Docker: push image
    │  └─ Post-deployment Verify
    ├─ Create Deployment Record
    └─ Send Notifications
    ↓
Documentation Live

Manual Deployment

# Local build
cd docs && mdbook build

# Deploy using script
bash .scripts/deploy-docs.sh production

# Or specific environment
bash .scripts/deploy-docs.sh staging

🆘 Troubleshooting

SSH Deployment Fails

Error: Permission denied (publickey)

Fix:

# Verify key is in authorized_keys
cat ~/.ssh/vapora-docs.pub | ssh user@server \
  "sudo -u docs cat >> /var/www/vapora-docs/.ssh/authorized_keys"

# Test connection
ssh -i ~/.ssh/vapora-docs -v docs@server.com

HTTP Deployment Fails

Error: HTTP 401 Unauthorized

Fix:

  • Verify token in GitHub Secrets matches server
  • Check HTTPS certificate validity
  • Verify endpoint is reachable
curl -H "Authorization: Bearer $TOKEN" https://deploy.server.com/health

S3 Deployment Fails

Error: NoSuchBucket

Fix:

  • Verify bucket name in secrets
  • Check IAM policy allows the action
  • Verify AWS credentials
aws s3 ls s3://vapora-docs-prod/

Docker Deployment Fails

Error: unauthorized: authentication required

Fix:

  • Verify credentials in secrets
  • Test Docker login locally
docker login registry.your-domain.com

📊 Deployment Configuration Reference

Production Template

# .deploy-config.production

DEPLOY_METHOD="ssh"
DEPLOY_HOST="docs.vapora.io"
DEPLOY_USER="docs"
DEPLOY_PATH="/var/www/vapora-docs"
BACKUP_RETENTION_DAYS=30
NOTIFY_ON_SUCCESS="true"
NOTIFY_ON_FAILURE="true"

Staging Template

# .deploy-config.staging

DEPLOY_METHOD="ssh"
DEPLOY_HOST="staging-docs.vapora.io"
DEPLOY_USER="docs-staging"
DEPLOY_PATH="/var/www/vapora-docs-staging"
BACKUP_RETENTION_DAYS=7
NOTIFY_ON_SUCCESS="false"
NOTIFY_ON_FAILURE="true"

Verification Checklist

  • SSH/SFTP user created and configured
  • SSH keys generated and added to server
  • Web server (nginx/apache) configured
  • GitHub secrets added for deployment method
  • Test push to main with docs/ changes
  • Monitor Actions tab for workflow
  • Verify deployment completed
  • Check documentation site
  • Test rollback procedure (if applicable)
  • Set up monitoring/alerts

📚 Additional Resources


Last Updated: 2026-01-12 Status: Production Ready

For deployment script details, see: .scripts/deploy-docs.sh