DigitalOcean Beispiele

DigitalOcean Cloud-Plattform-Beispiele einschließlich Droplets, Kubernetes, Spaces und App Platform

💻 Droplet-Management-API python

🟢 simple ⭐⭐

DigitalOcean Droplets mit der API erstellen, verwalten und überwachen

⏱️ 15 min 🏷️ digitalocean, api, droplets, infrastructure
Prerequisites: DigitalOcean account, API token, Python knowledge
# DigitalOcean Droplet Management
# Python - droplet_manager.py + requirements.txt

import os
import time
import requests
from datetime import datetime
import json

class DigitalOceanManager:
    def __init__(self, api_token):
        self.api_token = api_token
        self.base_url = "https://api.digitalocean.com/v2"
        self.headers = {
            "Authorization": f"Bearer {api_token}",
            "Content-Type": "application/json"
        }

    def create_droplet(self, name, region, size, image, ssh_keys=None, tags=None):
        """Create a new DigitalOcean Droplet"""
        url = f"{self.base_url}/droplets"

        data = {
            "name": name,
            "region": region,
            "size": size,
            "image": image,
            "ssh_keys": ssh_keys or [],
            "backups": False,
            "ipv6": True,
            "user_data": None,
            "private_networking": False,
            "tags": tags or []
        }

        try:
            response = requests.post(url, headers=self.headers, json=data)
            response.raise_for_status()

            droplet_info = response.json()['droplet']

            print(f"✅ Droplet '{name}' created successfully!")
            print(f"   ID: {droplet_info['id']}")
            print(f"   IP: {droplet_info['networks']['v4'][0]['ip_address']}")

            return droplet_info

        except requests.exceptions.RequestException as e:
            print(f"❌ Error creating droplet: {e}")
            if response.text:
                error_detail = json.loads(response.text)
                print(f"   Details: {error_detail.get('message', 'Unknown error')}")
            return None

    def list_droplets(self, tag_name=None):
        """List all droplets or droplets with specific tag"""
        url = f"{self.base_url}/droplets"
        if tag_name:
            url += f"?tag_name={tag_name}"

        try:
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()

            droplets = response.json()['droplets']

            print(f"📋 Found {len(droplets)} droplet(s):")
            print("-" * 60)

            for droplet in droplets:
                status_emoji = "🟢" if droplet['status'] == 'active' else "🟡"
                ip = droplet['networks']['v4'][0]['ip_address'] if droplet['networks']['v4'] else "N/A"

                print(f"{status_emoji} {droplet['name']}")
                print(f"   ID: {droplet['id']}")
                print(f"   IP: {ip}")
                print(f"   Status: {droplet['status']}")
                print(f"   Region: {droplet['region']['slug']}")
                print(f"   Size: {droplet['size_slug']}")
                if droplet.get('tags'):
                    print(f"   Tags: {', '.join(droplet['tags'])}")
                print()

            return droplets

        except requests.exceptions.RequestException as e:
            print(f"❌ Error listing droplets: {e}")
            return []

    def get_droplet_info(self, droplet_id):
        """Get detailed information about a specific droplet"""
        url = f"{self.base_url}/droplets/{droplet_id}"

        try:
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()

            droplet = response.json()['droplet']

            print(f"ℹ️  Droplet Information for '{droplet['name']}'")
            print("-" * 50)
            print(f"ID: {droplet['id']}")
            print(f"Status: {droplet['status']}")
            print(f"Region: {droplet['region']['name']} ({droplet['region']['slug']})")
            print(f"Size: {droplet['size_slug']}")
            print(f"CPUs: {droplet['vcpus']}")
            print(f"Memory: {droplet['memory']} MB")
            print(f"Disk: {droplet['disk']} GB")
            print(f"Image: {droplet['image']['distribution']} {droplet['image']['name']}")

            # Network information
            if droplet['networks']['v4']:
                for network in droplet['networks']['v4']:
                    print(f"IP ({network['type']}): {network['ip_address']}")

            print(f"Created: {droplet['created_at']}")

            if droplet.get('tags'):
                print(f"Tags: {', '.join(droplet['tags'])}")

            return droplet

        except requests.exceptions.RequestException as e:
            print(f"❌ Error getting droplet info: {e}")
            return None

    def power_off_droplet(self, droplet_id):
        """Power off a droplet"""
        url = f"{self.base_url}/droplets/{droplet_id}/actions"
        data = {"type": "power_off"}

        try:
            response = requests.post(url, headers=self.headers, json=data)
            response.raise_for_status()

            action = response.json()['action']
            print(f"🔌 Power off initiated for Droplet {droplet_id}")
            print(f"   Action ID: {action['id']}")
            print(f"   Status: {action['status']}")

            return action

        except requests.exceptions.RequestException as e:
            print(f"❌ Error powering off droplet: {e}")
            return None

    def power_on_droplet(self, droplet_id):
        """Power on a droplet"""
        url = f"{self.base_url}/droplets/{droplet_id}/actions"
        data = {"type": "power_on"}

        try:
            response = requests.post(url, headers=self.headers, json=data)
            response.raise_for_status()

            action = response.json()['action']
            print(f"🔌 Power on initiated for Droplet {droplet_id}")
            print(f"   Action ID: {action['id']}")
            print(f"   Status: {action['status']}")

            return action

        except requests.exceptions.RequestException as e:
            print(f"❌ Error powering on droplet: {e}")
            return None

    def delete_droplet(self, droplet_id):
        """Delete a droplet (PERMANENT!)"""
        url = f"{self.base_url}/droplets/{droplet_id}"

        # Confirm deletion
        confirm = input(f"⚠️  Are you sure you want to delete Droplet {droplet_id}? This cannot be undone! (yes/no): ")
        if confirm.lower() != 'yes':
            print("❌ Deletion cancelled")
            return False

        try:
            response = requests.delete(url, headers=self.headers)
            response.raise_for_status()

            print(f"✅ Droplet {droplet_id} deleted successfully")
            return True

        except requests.exceptions.RequestException as e:
            print(f"❌ Error deleting droplet: {e}")
            return False

    def create_snapshot(self, droplet_id, snapshot_name):
        """Create a snapshot of a droplet"""
        url = f"{self.base_url}/droplets/{droplet_id}/actions"
        data = {
            "type": "snapshot",
            "name": snapshot_name
        }

        try:
            response = requests.post(url, headers=self.headers, json=data)
            response.raise_for_status()

            action = response.json()['action']
            print(f"📸 Snapshot '{snapshot_name}' creation initiated")
            print(f"   Action ID: {action['id']}")
            print(f"   Status: {action['status']}")

            return action

        except requests.exceptions.RequestException as e:
            print(f"❌ Error creating snapshot: {e}")
            return None

    def get_action_status(self, action_id):
        """Check the status of an action"""
        url = f"{self.base_url}/actions/{action_id}"

        try:
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()

            action = response.json()['action']
            return action

        except requests.exceptions.RequestException as e:
            print(f"❌ Error getting action status: {e}")
            return None

# Example usage
if __name__ == "__main__":
    # Get API token from environment variable
    api_token = os.environ.get('DIGITALOCEAN_API_TOKEN')

    if not api_token:
        print("❌ Please set DIGITALOCEAN_API_TOKEN environment variable")
        exit(1)

    # Initialize manager
    manager = DigitalOceanManager(api_token)

    # Example: Create a droplet
    print("🚀 Creating a new droplet...")
    droplet = manager.create_droplet(
        name="my-app-server",
        region="nyc3",  # New York 3
        size="s-1vcpu-1gb",  # $5/month
        image="ubuntu-22-04-x64",  # Ubuntu 22.04
        tags=["web", "production"]
    )

    if droplet:
        # Wait for droplet to be active
        print("⏳ Waiting for droplet to be active...")
        time.sleep(30)

        # List all droplets
        manager.list_droplets()

    # Example: Get droplet info (replace with actual droplet ID)
    # manager.get_droplet_info("your-droplet-id")

    # Example: Create snapshot (replace with actual droplet ID)
    # manager.create_snapshot("your-droplet-id", "backup-before-update")

# requirements.txt
"""
requests>=2.31.0
python-dotenv>=1.0.0
"""

💻 Kubernetes-Cluster-Bereitstellung yaml

🟡 intermediate ⭐⭐⭐

Anwendungen auf DigitalOcean Kubernetes Service (DOKS) bereitstellen und verwalten

⏱️ 30 min 🏷️ digitalocean, kubernetes, doks, deployment, containers
Prerequisites: DigitalOcean account, Kubernetes knowledge, kubectl installed, doctl CLI
# DigitalOcean Kubernetes Deployment
# deployment.yaml - Complete application deployment on DOKS

apiVersion: v1
kind: Namespace
metadata:
  name: myapp
  labels:
    name: myapp
    environment: production

---
# ConfigMap for application configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
  namespace: myapp
data:
  NODE_ENV: "production"
  PORT: "3000"
  REDIS_HOST: "redis-service"
  REDIS_PORT: "6379"
  DB_HOST: "postgresql-service"
  DB_PORT: "5432"

---
# Secret for sensitive data
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
  namespace: myapp
type: Opaque
data:
  # Base64 encoded values
  # echo -n 'your-database-password' | base64
  DB_PASSWORD: eW91ci1kYXRhYmFzZS1wYXNzd29yZA==
  # echo -n 'your-jwt-secret' | base64
  JWT_SECRET: eW91ci1qd3Qtc2VjcmV0
  # echo -n 'your-api-key' | base64
  API_KEY: eW91ci1hcGkta2V5

---
# Persistent Volume Claim for database storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: myapp
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: do-block-storage
  resources:
    requests:
      storage: 10Gi

---
# PostgreSQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:15-alpine
        env:
        - name: POSTGRES_DB
          value: "myapp"
        - name: POSTGRES_USER
          value: "myappuser"
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: myapp-secrets
              key: DB_PASSWORD
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
      volumes:
      - name: postgres-storage
        persistentVolumeClaim:
          claimName: postgres-pvc

---
# Redis Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "250m"

---
# Application Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: your-registry/myapp:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 3000
        envFrom:
        - configMapRef:
            name: myapp-config
        - secretRef:
            name: myapp-secrets
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

---
# Services
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
  namespace: myapp
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
  type: ClusterIP

---
apiVersion: v1
kind: Service
metadata:
  name: redis-service
  namespace: myapp
spec:
  selector:
    app: redis
  ports:
  - port: 6379
    targetPort: 6379
  type: ClusterIP

---
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
  namespace: myapp
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

---
# Ingress for external access
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: myapp
  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:
    - yourdomain.com
    secretName: myapp-tls
  rules:
  - host: yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80

---
# Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
  namespace: myapp
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  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

---
# Network Policy for security
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: myapp-netpol
  namespace: myapp
spec:
  podSelector:
    matchLabels:
      app: myapp
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 3000
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - podSelector:
        matchLabels:
          app: redis
    ports:
    - protocol: TCP
      port: 6379

---
# PodDisruptionBudget for high availability
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myapp-pdb
  namespace: myapp
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: myapp

# Additional Kubernetes manifests for DigitalOcean

# Load Balancer Service (if you need DO load balancer)
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-lb
  namespace: myapp
  annotations:
    service.beta.kubernetes.io/do-loadbalancer-name: "myapp-loadbalancer"
    service.beta.kubernetes.io/do-loadbalancer-protocol: "http"
    service.beta.kubernetes.io/do-loadbalancer-port-forwarding-rules: "443:http"
    service.beta.kubernetes.io/do-loadbalancer-http-ports: "80"
    service.beta.kubernetes.io/do-loadbalancer-algorithm: "least_connections"
spec:
  type: LoadBalancer
  selector:
    app: myapp
  ports:
  - name: http
    port: 80
    targetPort: 3000
  - name: https
    port: 443
    targetPort: 3000

# doctl commands for cluster management
"""
# Create DigitalOcean Kubernetes cluster
doctl kubernetes cluster create myapp-cluster \
    --region nyc3 \
    --version 1.28.2-do.1 \
    --node-pool "name=worker-pool;size=s-2vcpu-4gb;count=3" \
    --auto-upgrade \
    --enable-cluster-autoscaling \
    --min-nodes=1 \
    --max-nodes=5

# Get cluster credentials
doctl kubernetes cluster kubeconfig save myapp-cluster

# Create block storage for persistent volumes
doctl volume create postgres-storage --region nyc3 --size 10Gi

# Create domain for DNS management
doctl compute domain create yourdomain.com

# Create DNS records
doctl compute domain records create yourdomain.com --type A --name www --data 192.168.1.1

# Upload container registry
doctl registry repository create myapp

# Tag and push image
docker tag myapp:latest registry.digitalocean.com/your-username/myapp:latest
docker push registry.digitalocean.com/your-username/myapp:latest
"""

💻 Spaces Object-Speicher javascript

🟡 intermediate ⭐⭐⭐

Dateien in DigitalOcean Spaces hochladen, herunterladen und verwalten

⏱️ 25 min 🏷️ digitalocean, spaces, storage, s3, files
Prerequisites: DigitalOcean account, Spaces bucket, Node.js knowledge
// DigitalOcean Spaces File Storage
// JavaScript/Node.js - spaces-manager.js

const AWS = require('aws-sdk');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

class SpacesManager {
    constructor(accessKey, secretKey, region = 'nyc3', bucketName) {
        // DigitalOcean Spaces uses S3-compatible API
        this.s3 = new AWS.S3({
            accessKeyId: accessKey,
            secretAccessKey: secretKey,
            region: region,
            endpoint: new AWS.Endpoint(`https://${region}.digitaloceanspaces.com`)
        });

        this.bucketName = bucketName;
        this.region = region;
    }

    async uploadFile(filePath, keyPrefix = '', metadata = {}) {
        /** Upload a file to Spaces */
        try {
            const fileName = path.basename(filePath);
            const key = keyPrefix ? `${keyPrefix}/${fileName}` : fileName;

            // Read file
            const fileContent = fs.readFileSync(filePath);

            // Calculate file hash
            const fileHash = crypto.createHash('md5').update(fileContent).digest('hex');

            // Set content type based on file extension
            const contentTypes = {
                '.jpg': 'image/jpeg',
                '.jpeg': 'image/jpeg',
                '.png': 'image/png',
                '.gif': 'image/gif',
                '.pdf': 'application/pdf',
                '.txt': 'text/plain',
                '.json': 'application/json',
                '.html': 'text/html',
                '.css': 'text/css',
                '.js': 'application/javascript',
                '.zip': 'application/zip',
                '.mp4': 'video/mp4',
                '.mp3': 'audio/mpeg'
            };

            const ext = path.extname(fileName).toLowerCase();
            const contentType = contentTypes[ext] || 'application/octet-stream';

            const params = {
                Bucket: this.bucketName,
                Key: key,
                Body: fileContent,
                ContentType: contentType,
                ACL: 'public-read', // Make file publicly accessible
                Metadata: {
                    'original-name': fileName,
                    'upload-time': new Date().toISOString(),
                    'file-hash': fileHash,
                    ...metadata
                }
            };

            const result = await this.s3.upload(params).promise();

            console.log(`✅ File uploaded successfully:`);
            console.log(`   Key: ${result.Key}`);
            console.log(`   URL: ${result.Location}`);
            console.log(`   Size: ${fs.statSync(filePath).size} bytes`);
            console.log(`   Hash: ${fileHash}`);

            return {
                key: result.Key,
                url: result.Location,
                size: result.ContentLength,
                hash: fileHash
            };

        } catch (error) {
            console.error(`❌ Error uploading file: ${error.message}`);
            throw error;
        }
    }

    async downloadFile(key, localPath) {
        /** Download a file from Spaces */
        try {
            const params = {
                Bucket: this.bucketName,
                Key: key
            };

            const result = await this.s3.getObject(params).promise();

            // Ensure directory exists
            const dir = path.dirname(localPath);
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true });
            }

            // Write file
            fs.writeFileSync(localPath, result.Body);

            console.log(`✅ File downloaded successfully:`);
            console.log(`   Key: ${key}`);
            console.log(`   Path: ${localPath}`);
            console.log(`   Size: ${result.ContentLength} bytes`);

            return {
                key: key,
                path: localPath,
                size: result.ContentLength,
                metadata: result.Metadata
            };

        } catch (error) {
            console.error(`❌ Error downloading file: ${error.message}`);
            throw error;
        }
    }

    async listFiles(prefix = '', maxKeys = 1000) {
        /** List files in Spaces */
        try {
            const params = {
                Bucket: this.bucketName,
                Prefix: prefix,
                MaxKeys: maxKeys
            };

            const result = await this.s3.listObjectsV2(params).promise();

            console.log(`📋 Found ${result.Contents.length} file(s) in Spaces:`);
            console.log("-" * 80);

            const files = result.Contents.map(obj => {
                const fileSize = this.formatFileSize(obj.Size);
                const lastModified = new Date(obj.LastModified).toLocaleString();

                console.log(`📄 ${obj.Key}`);
                console.log(`   Size: ${fileSize}`);
                console.log(`   Modified: ${lastModified}`);
                console.log(`   URL: https://${this.bucketName}.${this.region}.digitaloceanspaces.com/${obj.Key}`);
                console.log();

                return {
                    key: obj.Key,
                    size: obj.Size,
                    lastModified: obj.LastModified,
                    url: `https://${this.bucketName}.${this.region}.digitaloceanspaces.com/${obj.Key}`,
                    etag: obj.ETag
                };
            });

            if (result.IsTruncated) {
                console.log(`ℹ️  More files available. Use continuation token for next page.`);
            }

            return files;

        } catch (error) {
            console.error(`❌ Error listing files: ${error.message}`);
            throw error;
        }
    }

    async deleteFile(key) {
        /** Delete a file from Spaces */
        try {
            const params = {
                Bucket: this.bucketName,
                Key: key
            };

            await this.s3.deleteObject(params).promise();

            console.log(`🗑️  File deleted successfully: ${key}`);

            return true;

        } catch (error) {
            console.error(`❌ Error deleting file: ${error.message}`);
            throw error;
        }
    }

    async generatePresignedUrl(key, expiresIn = 3600) {
        /** Generate a presigned URL for temporary access */
        try {
            const params = {
                Bucket: this.bucketName,
                Key: key,
                Expires: expiresIn
            };

            const url = await this.s3.getSignedUrlPromise('getObject', params);

            console.log(`🔗 Presigned URL generated:`);
            console.log(`   Key: ${key}`);
            console.log(`   URL: ${url}`);
            console.log(`   Expires in: ${expiresIn} seconds`);

            return url;

        } catch (error) {
            console.error(`❌ Error generating presigned URL: ${error.message}`);
            throw error;
        }
    }

    async createFolder(folderPath) {
        /** Create a folder (actually just a placeholder object) */
        try {
            const key = folderPath.endsWith('/') ? folderPath : `${folderPath}/`;

            const params = {
                Bucket: this.bucketName,
                Key: key,
                Body: '',
                ContentType: 'application/x-directory'
            };

            await this.s3.putObject(params).promise();

            console.log(`📁 Folder created: ${key}`);

            return key;

        } catch (error) {
            console.error(`❌ Error creating folder: ${error.message}`);
            throw error;
        }
    }

    async getFileInfo(key) {
        /** Get detailed information about a file */
        try {
            const params = {
                Bucket: this.bucketName,
                Key: key
            };

            const result = await this.s3.headObject(params).promise();

            console.log(`ℹ️  File Information for: ${key}`);
            console.log("-" * 50);
            console.log(`Size: ${this.formatFileSize(result.ContentLength)}`);
            console.log(`Last Modified: ${new Date(result.LastModified).toLocaleString()}`);
            console.log(`Content Type: ${result.ContentType}`);
            console.log(`ETag: ${result.ETag}`);
            console.log(`URL: https://${this.bucketName}.${this.region}.digitaloceanspaces.com/${key}`);

            if (result.Metadata) {
                console.log(`Metadata: ${JSON.stringify(result.Metadata, null, 2)}`);
            }

            return {
                key: key,
                size: result.ContentLength,
                lastModified: result.LastModified,
                contentType: result.ContentType,
                etag: result.ETag,
                metadata: result.Metadata
            };

        } catch (error) {
            console.error(`❌ Error getting file info: ${error.message}`);
            throw error;
        }
    }

    formatFileSize(bytes) {
        /** Format file size in human readable format */
        const units = ['B', 'KB', 'MB', 'GB', 'TB'];
        let size = bytes;
        let unitIndex = 0;

        while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
        }

        return `${size.toFixed(2)} ${units[unitIndex]}`;
    }

    async syncFolder(localFolder, spacesPrefix = '', excludePatterns = []) {
        /** Sync a local folder to Spaces */
        try {
            console.log(`🔄 Starting sync from ${localFolder} to Spaces prefix ${spacesPrefix}...`);

            let uploadedCount = 0;
            let skippedCount = 0;
            let errorCount = 0;

            // Recursively walk through local folder
            const walkDir = (dir, prefix = '') => {
                const files = fs.readdirSync(dir);

                files.forEach(file => {
                    const filePath = path.join(dir, file);
                    const stats = fs.statSync(filePath);

                    // Check if file should be excluded
                    if (excludePatterns.some(pattern => file.match(pattern))) {
                        console.log(`⏭️  Skipping excluded file: ${filePath}`);
                        skippedCount++;
                        return;
                    }

                    if (stats.isDirectory()) {
                        // Recurse into subdirectory
                        const subPrefix = prefix ? `${prefix}/${file}` : file;
                        walkDir(filePath, subPrefix);
                    } else {
                        // Upload file
                        const keyPrefix = spacesPrefix ? `${spacesPrefix}/${prefix}` : prefix;

                        try {
                            this.uploadFile(filePath, keyPrefix, {
                                'local-path': filePath,
                                'sync-time': new Date().toISOString()
                            });
                            uploadedCount++;
                        } catch (error) {
                            console.error(`❌ Error uploading ${filePath}: ${error.message}`);
                            errorCount++;
                        }
                    }
                });
            };

            walkDir(localFolder);

            console.log(`\n✅ Sync completed:`);
            console.log(`   Uploaded: ${uploadedCount} files`);
            console.log(`   Skipped: ${skippedCount} files`);
            console.log(`   Errors: ${errorCount} files`);

            return {
                uploaded: uploadedCount,
                skipped: skippedCount,
                errors: errorCount
            };

        } catch (error) {
            console.error(`❌ Error syncing folder: ${error.message}`);
            throw error;
        }
    }
}

// Example usage
async function main() {
    // Load environment variables
    require('dotenv').config();

    // Initialize Spaces manager
    const spacesManager = new SpacesManager(
        process.env.SPACES_ACCESS_KEY,
        process.env.SPACES_SECRET_KEY,
        process.env.SPACES_REGION || 'nyc3',
        process.env.SPACES_BUCKET_NAME
    );

    try {
        // Example 1: Upload a file
        console.log("📤 Uploading example file...");
        await spacesManager.uploadFile('./example.txt', 'documents');

        // Example 2: List files
        console.log("\n📋 Listing files...");
        await spacesManager.listFiles();

        // Example 3: Create folder
        console.log("\n📁 Creating folder...");
        await spacesManager.createFolder('images/2024');

        // Example 4: Generate presigned URL
        console.log("\n🔗 Generating presigned URL...");
        await spacesManager.generatePresignedUrl('documents/example.txt', 3600);

        // Example 5: Sync folder (if folder exists)
        if (fs.existsSync('./public')) {
            console.log("\n🔄 Syncing public folder...");
            await spacesManager.syncFolder('./public', 'assets', [
                /.DS_Store/,
                /Thumbs.db/,
                /.git/
            ]);
        }

    } catch (error) {
        console.error('❌ Error in main:', error);
    }
}

// Export the class
module.exports = SpacesManager;

// Run example if called directly
if (require.main === module) {
    main();
}

// package.json dependencies
/*
{
  "dependencies": {
    "aws-sdk": "^2.1500.0",
    "dotenv": "^16.3.1"
  }
}
*/

💻 App Platform-Bereitstellung yaml

🔴 complex ⭐⭐⭐⭐

Containerisierte und statische Anwendungen auf DigitalOcean App Platform bereitstellen

⏱️ 35 min 🏷️ digitalocean, app platform, deployment, containers, paas
Prerequisites: DigitalOcean account, App Platform enabled, Docker knowledge, YAML configuration
# DigitalOcean App Platform Deployment
# app.yaml - Complete application deployment configuration

# Production configuration
name: my-production-app
region: nyc3
services:
- name: web
  # Source code location
  source_dir: /
  # GitHub repository
  github:
    repo: your-username/your-app
    branch: main
    deploy_on_push: true

  # Docker image configuration
  image:
    registry_type: DOCR
    repository: your-username/myapp
    tag: latest

  # Build configuration
  build_command: |
    npm install --production
    npm run build

  # Run command
  run_command: npm start

  # HTTP port
  http_port: 3000

  # Instance count (auto-scaling)
  instance_count: 2
  instance_size_slug: professional-xs

  # Environment variables
  env:
  - key: NODE_ENV
    value: production
  - key: PORT
    value: "3000"
  - key: DATABASE_URL
    value: ${db.DATABASE_URL}
  - key: REDIS_URL
    value: ${redis.REDIS_URL}
  - key: JWT_SECRET
    value: ${_self.JWT_SECRET}

  # Health check
  health_check:
    http_path: /health
    port: 3000
    initial_delay_seconds: 30
    period_seconds: 10
    timeout_seconds: 5
    success_threshold: 3
    failure_threshold: 5

  # Resource limits
  resource_limits:
    memory: 512Mi
    cpu: 500m

  # Deployment strategy
  deployment_strategy:
    type: RollingUpdate
    max_unavailable: 1
    max_surge: 1

# Static site service
- name: docs
  source_dir: ./docs
  github:
    repo: your-username/docs
    branch: gh-pages
  build_command: |
    npm install
    npm run build
  output_dir: _site
  http_port: 80
  routes:
  - path: /docs
    preserve_path_prefix: true
  resource_limits:
    memory: 128Mi
    cpu: 100m

# Workers/Cron jobs
jobs:
- name: daily-backup
  source_dir: ./scripts
  image:
    registry_type: DOCKER_HUB
    repository: your-username/backup-worker
    tag: latest
  run_command: node backup.js
  schedule: "0 2 * * *"  # Daily at 2 AM
  kind: CRON
  env:
  - key: DATABASE_URL
    value: ${db.DATABASE_URL}
  - key: SPACES_ACCESS_KEY
    value: ${_self.SPACES_ACCESS_KEY}
  - key: SPACES_SECRET_KEY
    value: ${_self.SPACES_SECRET_KEY}
  resource_limits:
    memory: 256Mi
    cpu: 250m

# Databases
databases:
- name: db
  engine: PG
  version: "15"
  size: db-s-1vcpu-2gb
  # Connection details (automatically injected as environment variables)
  # DATABASE_URL
  # DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD
  # BACKUP_URL for restore

# Redis cache
- name: redis
  engine: REDIS
  version: "7"
  size: redis-s-1vcpu-512mb
  # Connection details (automatically injected as environment variables)
  # REDIS_URL
  # REDIS_HOST, REDIS_PORT, REDIS_PASSWORD

# Domains
domains:
- domain: yourdomain.com
  type: PRIMARY
  zone: yourdomain.com
  wildcard: true

# Monitoring and alerts
alerts:
- rule: DEPLOYMENT_FAILED
  value: "1"
  operator: GREATER_THAN
  notification_emails:
  - [email protected]
- rule: DOMAIN_FAILED
  value: "1"
  operator: GREATER_THAN
  notification_emails:
  - [email protected]
- rule: HIGH_ERROR_RATE
  value: "90"
  operator: GREATER_THAN
  notification_emails:
  - [email protected]
  comparison: PERCENTAGE

# Development environment variant
# app.dev.yaml
---
name: my-app-dev
region: nyc3
services:
- name: web
  source_dir: /
  github:
    repo: your-username/your-app
    branch: develop
    deploy_on_push: true
  build_command: npm install && npm run build
  run_command: npm run dev
  http_port: 3000
  instance_count: 1
  instance_size_slug: basic-xxs
  env:
  - key: NODE_ENV
    value: development
  resource_limits:
    memory: 256Mi
    cpu: 100m

# Staging environment variant
# app.staging.yaml
---
name: my-app-staging
region: nyc3
services:
- name: web
  source_dir: /
  github:
    repo: your-username/your-app
    branch: staging
    deploy_on_push: true
  build_command: npm ci --production && npm run build
  run_command: npm start
  http_port: 3000
  instance_count: 1
  instance_size_slug: basic-xs
  env:
  - key: NODE_ENV
    value: staging
  - key: DATABASE_URL
    value: ${db-staging.DATABASE_URL}
  resource_limits:
    memory: 512Mi
    cpu: 250m

# Multi-service application (microservices)
# app-microservices.yaml
---
name: my-microservices-app
region: nyc3
services:
# API Gateway
- name: api-gateway
  source_dir: ./gateway
  github:
    repo: your-username/microservices
    branch: main
  build_command: |
    cd gateway
    npm install --production
    npm run build
  run_command: |
    cd gateway
    npm start
  http_port: 8080
  instance_count: 2
  instance_size_slug: basic-xs
  routes:
  - path: /
    preserve_path_prefix: false
  env:
  - key: AUTH_SERVICE_URL
    value: http://auth-service:3001
  - key: USER_SERVICE_URL
    value: http://user-service:3002
  - key: PRODUCT_SERVICE_URL
    value: http://product-service:3003

# Auth Service
- name: auth-service
  source_dir: ./auth
  github:
    repo: your-username/microservices
    branch: main
  build_command: |
    cd auth
    npm install --production
    npm run build
  run_command: |
    cd auth
    npm start
  http_port: 3001
  instance_count: 1
  instance_size_slug: basic-xxs
  internal_ports:
  - 3001

# User Service
- name: user-service
  source_dir: ./user
  github:
    repo: your-username/microservices
    branch: main
  build_command: |
    cd user
    npm install --production
    npm run build
  run_command: |
    cd user
    npm start
  http_port: 3002
  instance_count: 1
  instance_size_slug: basic-xxs
  internal_ports:
  - 3002
  env:
  - key: DATABASE_URL
    value: ${user-db.DATABASE_URL}

# Product Service
- name: product-service
  source_dir: ./product
  github:
    repo: your-username/microservices
    branch: main
  build_command: |
    cd product
    npm install --production
    npm run build
  run_command: |
    cd product
    npm start
  http_port: 3003
  instance_count: 2
  instance_size_slug: basic-xs
  internal_ports:
  - 3003
  env:
  - key: DATABASE_URL
    value: ${product-db.DATABASE_URL}
  - key: REDIS_URL
    value: ${cache.REDIS_URL}

# Databases for microservices
databases:
- name: user-db
  engine: PG
  version: "15"
  size: db-s-1vcpu-1gb
- name: product-db
  engine: PG
  version: "15"
  size: db-s-2vcpu-4gb

# Shared cache
- name: cache
  engine: REDIS
  version: "7"
  size: redis-s-1vcpu-1gb

# CLI commands for App Platform
"""
# Install doctl
curl -sSL https://dl.digitalocean.com/doctl/install.sh | sh

# Authenticate
doctl auth init

# Create app from spec
doctl apps create --spec app.yaml

# Update app
doctl apps update <app-id> --spec app.yaml

# List apps
doctl apps list

# Get app details
doctl apps get <app-id>

# List deployments
doctl apps list-deployments <app-id>

# Create database
doctl databases create mydb --engine pg --version 15 --size db-s-1vcpu-2gb --region nyc3

# Create Redis
doctl databases create mycache --engine redis --version 7 --size redis-s-1vcpu-1gb --region nyc3

# Scale app
doctl apps update <app-id> --spec spec/production.yaml

# View logs
doctl apps logs <app-id> --follow

# Delete app
doctl apps delete <app-id>
"""