Features
Servers
Manage Servers

Server Management

Overview

OEC.SH implements a Bring Your Own Server (BYOS) model, giving you complete control over your infrastructure. Connect any Linux server—from cloud providers like DigitalOcean, AWS, Hetzner, or your own dedicated hardware—and deploy Odoo environments across your fleet.

BYOS Benefits

  • Cost Control: Use your existing infrastructure or negotiate better pricing with providers
  • Flexibility: Deploy across multiple providers and regions for redundancy
  • Multi-Project Economics: Run 3-10 Odoo environments per server (50% cheaper than single-project hosting)
  • Data Sovereignty: Keep data in your preferred jurisdiction
  • No Vendor Lock-in: Switch providers or bring servers in-house anytime

Architecture

Organization → Servers (1:N) → Environments (1:N)
            └→ Projects → Environments → Server Assignment

Key Points:

  • Servers belong to organizations
  • Environments (not projects) are deployed to servers
  • One project can have multiple environments across different servers
  • Supports 3-10 environments per server depending on resources

Server Requirements

Minimum Specifications

ResourceMinimumRecommendedNotes
CPU2 cores4+ cores0.5-1 core per environment
RAM4 GB8+ GB1-2 GB per environment
Disk80 GB160+ GB SSDIncludes Docker images, databases
OSUbuntu 20.04+Ubuntu 22.04 LTSDebian, RHEL, Rocky also supported
Docker20.10+Latest stableAuto-installed if missing

Network Requirements

Required Ports (must be accessible from OEC.SH platform):

  • SSH (22): Server management and deployments
  • HTTP (80): Web traffic (redirects to HTTPS)
  • HTTPS (443): Secure web traffic via Traefik

Firewall Configuration:

# UFW (Ubuntu/Debian)
sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP
sudo ufw allow 443/tcp   # HTTPS
sudo ufw enable
 
# firewalld (RHEL/Rocky/CentOS)
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Cloud Provider Security Groups:

  • DigitalOcean: Allow inbound 22, 80, 443
  • AWS: Security Group rules for 0.0.0.0/0 on ports 22, 80, 443
  • Hetzner: Cloud Firewall with SSH, HTTP, HTTPS rules

Supported Operating Systems

OS FamilyVersionsSupport TierPackage Manager
Ubuntu20.04, 22.04, 24.04Tier 1 (Full)apt
Debian11 (Bullseye), 12 (Bookworm)Tier 1 (Full)apt
Rocky Linux8, 9Tier 2 (Partial)dnf
AlmaLinux8, 9Tier 2 (Partial)dnf
CentOS8, 9Tier 2 (Partial)dnf/yum
Alpine3.17+Tier 3 (Experimental)apk
ArchLatestTier 3 (Experimental)pacman

Init Systems: systemd (preferred), OpenRC, SysVinit

Add Server

SSH Authentication Methods

OEC.SH supports two authentication methods with a progressive security model:

1. Password Authentication (Quick Start)

Start with password auth for rapid onboarding, then upgrade to SSH keys later.

# Example: Fresh DigitalOcean droplet with root password
Server: 203.0.113.10
Username: root
Password: your-secure-password
Port: 22

Limitations:

  • Less secure than key-based auth
  • May not work if provider disables password auth (AWS, GCP)
  • Recommended for initial setup only

2. SSH Key Authentication (Recommended)

More secure and required for production deployments.

Generate SSH Key Pair:

# Ed25519 (recommended - modern, secure, fast)
ssh-keygen -t ed25519 -C "oecsh-server-name" -f ~/.ssh/oecsh_server_key
 
# RSA (compatible with older systems)
ssh-keygen -t rsa -b 4096 -C "oecsh-server-name" -f ~/.ssh/oecsh_server_key
 
# View private key (paste into OEC.SH)
cat ~/.ssh/oecsh_server_key
 
# View public key (deploy to server)
cat ~/.ssh/oecsh_server_key.pub

Deploy Public Key to Server:

# Method 1: Direct copy
ssh-copy-id -i ~/.ssh/oecsh_server_key.pub root@203.0.113.10
 
# Method 2: Manual deployment
ssh root@203.0.113.10
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "ssh-ed25519 AAAAC3Nz... oecsh-server" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
exit
 
# Verify key works
ssh -i ~/.ssh/oecsh_server_key root@203.0.113.10

Connection Host Types

OEC.SH supports multiple address types for maximum flexibility:

TypeWhen to UseExample
IPv4Standard internet connections203.0.113.10
IPv6Modern networks with IPv6 support2001:db8::1
FQDNDomain-based connections (DNS required)server.example.com
HostnameInternal networks or VPNproduction-1
AUTOPlatform detects best optionPriority: FQDN > IPv4 > IPv6 > Hostname

Add Server via Dashboard

  1. Navigate: Settings → Servers → "Add Server"

  2. Basic Info:

    • Name: production-server-1 (friendly identifier)
    • Provider: Custom (BYOS), DigitalOcean, AWS, Hetzner, Azure
    • Region: Frankfurt or any label
  3. Connection Details:

    • IPv4 Address: 203.0.113.10 (at least one address required)
    • IPv6 Address: 2001:db8::1 (optional)
    • FQDN: server.example.com (optional)
    • Connection Type: AUTO (or specific type)
    • SSH Port: 22 (default)
  4. SSH Authentication:

    • Username: root (or ubuntu, admin, etc.)
    • Auth Method: SSH Key (recommended) or Password
    • Private Key: Paste your SSH private key content
    • Passphrase: Only if key is encrypted
  5. Sudo Mode (for non-root users):

    • AUTO: Detects if sudo is needed
    • NONE: User is root or has direct permissions
    • SUDO: Always use sudo (for AWS ubuntu, GCP users)
  6. Resources (auto-detected after connection):

    • CPU Cores: 4
    • Memory: 8 GB
    • Disk: 160 GB
    • Max Environments: 8 (recommended capacity)
  7. Test Connection: Verify SSH credentials work before saving

Add Server via API

curl -X POST https://app.oec.sh/api/v1/vms \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "organization_id": "org-uuid",
    "name": "production-server-1",
    "provider": "custom",
    "region": "Frankfurt",
    "ipv4_address": "203.0.113.10",
    "connection_host_type": "auto",
    "ssh_port": 22,
    "ssh_username": "root",
    "ssh_auth_method": "ssh_key",
    "ssh_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----",
    "ssh_sudo_mode": "auto",
    "cpu_cores": 4,
    "memory_gb": 8,
    "disk_gb": 160,
    "max_projects": 8
  }'

Response:

{
  "id": "vm-uuid",
  "name": "production-server-1",
  "organization_id": "org-uuid",
  "status": "running",
  "health_status": "unknown",
  "connection_host": "203.0.113.10",
  "ssh_auth_method": "ssh_key",
  "created_at": "2025-01-15T10:30:00Z"
}

Server Configuration

Test Connection

Verify SSH credentials and detect server capabilities:

POST /api/v1/vms/{vm_id}/test-connection

What it does:

  1. Establishes SSH connection using stored credentials
  2. Detects OS type, version, and architecture
  3. Checks Docker installation and version
  4. Detects CPU, RAM, and disk resources
  5. Determines sudo requirements (root vs non-root user)
  6. Updates server record with detected information

Response:

{
  "vm_id": "vm-uuid",
  "status": "success",
  "message": "Connection successful",
  "detected": {
    "hostname": "ubuntu-s-4vcpu-8gb-fra1-01",
    "os_type": "Ubuntu",
    "os_version": "22.04",
    "cpu_cores": 4,
    "memory_gb": 8,
    "disk_gb": 160,
    "docker_status": "running",
    "docker_version": "24.0.7",
    "is_root": false,
    "sudo_available": true,
    "ssh_sudo_mode": "sudo"
  }
}

Preflight Checks

Run comprehensive deployment readiness checks:

POST /api/v1/vms/{vm_id}/preflight

Returns Server-Sent Events (SSE) stream with real-time progress.

Check Categories:

CheckDescriptionAuto-Fix Available
SSH ConnectivitySSH connection and authentication
Root/Sudo AccessPrivilege escalation for Docker✅ (configure sudo)
OS CompatibilityLinux distribution and version
Docker InstallationDocker Engine installed✅ (install Docker)
Docker RunningDocker daemon active✅ (start Docker)
Docker PermissionsUser can run Docker commands✅ (add to docker group)
Disk SpaceMinimum 20GB free
Network ConnectivityInternet access for images
Ports Available80, 443 not in use

Qualification Status:

  • Qualified: All checks passed, ready for deployment
  • Needs Setup: Fixable issues (Docker not installed, etc.)
  • Unqualified: Critical failures requiring manual intervention
  • Stale: >7 days since last check
  • Expired: >30 days since last check, requires re-validation

Example Stream:

event: status
data: {"step": "connecting", "message": "Establishing SSH connection..."}

event: os_info
data: {"name": "Ubuntu", "version": "22.04", "arch": "x86_64"}

event: check
data: {"name": "docker_installed", "passed": false, "fixable": true}

event: complete
data: {
  "status": "needs_setup",
  "passed": 7,
  "failed": 2,
  "warnings": 1
}

Update Server Credentials

Update SSH Password

PUT /api/v1/vms/{vm_id}/ssh-password
Content-Type: application/json
 
{
  "ssh_password": "new-secure-password",
  "test_connection": true
}

Security Notes:

  • Password is encrypted before storage using AES-256-GCM
  • Changes auth method from SSH_KEY to PASSWORD
  • Test connection recommended to avoid lockout

Update SSH Key

PUT /api/v1/vms/{vm_id}/ssh-key
Content-Type: application/json
 
{
  "ssh_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
  "ssh_key_passphrase": "optional-passphrase",
  "test_connection": true
}

Key Rotation Best Practices:

  1. Generate new SSH key pair
  2. Deploy new public key to server (keep old key active)
  3. Update OEC.SH with new private key
  4. Test connection succeeds
  5. Remove old public key from server

One-Click Secure (Progressive Security)

Upgrade from password auth to SSH key auth automatically:

POST /api/v1/vms/{vm_id}/secure

What it does:

  1. Generates Ed25519 SSH key pair
  2. Connects via current password authentication
  3. Deploys public key to ~/.ssh/authorized_keys
  4. Verifies key-based auth works
  5. Updates server record to use SSH key
  6. Clears password from database for security
  7. Optionally disables password auth on server

Request Body (optional):

{
  "disable_password_auth": false
}

Response:

{
  "message": "Server successfully secured with SSH key authentication",
  "vm_id": "vm-uuid",
  "previous_auth_method": "password",
  "new_auth_method": "ssh_key",
  "key_verified": true,
  "password_cleared": true,
  "server_password_auth_disabled": false,
  "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----",
  "public_key": "ssh-ed25519 AAAAC3Nz... paasportal-server@host",
  "key_type": "ed25519"
}

IMPORTANT: Save the private key from the response—it's only shown once!

Server Monitoring

OEC.SH uses Netdata (opens in a new tab) for real-time infrastructure monitoring with a parent-child streaming architecture.

Architecture

┌──────────────────┐
│  OEC.SH Platform │
│   (Netdata Parent)│◄─── Streams metrics from child agents
└──────────────────┘

         │ Stream metrics (1s intervals)

    ┌────┴────┬────────┬────────┐
    │         │        │        │
┌───▼────┐ ┌─▼───┐ ┌─▼───┐ ┌──▼────┐
│ Server1│ │Serv2│ │Serv3│ │Server4│
│(Netdata│ │(Net)│ │(Net)│ │(Netdata)
└────────┘ └─────┘ └─────┘ └───────┘

Benefits:

  • Real-time: 1-second metric resolution
  • Centralized: All server metrics in one dashboard
  • Lightweight: ~1% CPU overhead per server
  • Zero Config: Automatically detects containers and services
  • Historical: Metrics retained for 7 days (parent) + 1 hour (child)

Metrics Hierarchy

Level 1: Summary (for server lists):

GET /api/v1/monitoring/servers/{server_id}/summary

Returns quick metrics for cards:

  • CPU usage %
  • Memory usage %
  • Disk usage %
  • Network throughput
  • Container count
  • Health status

Level 2: Full Metrics (for server detail page):

GET /api/v1/monitoring/servers/{server_id}/metrics

Returns comprehensive metrics:

  • System metrics (CPU, RAM, disk, network)
  • Docker metrics (container CPU/RAM, image cache)
  • Per-container metrics (all running containers)
  • Load averages
  • Disk I/O

Level 3: Netdata Dashboard (for deep analysis):

GET /api/v1/monitoring/servers/{server_id}/url

Returns authenticated URL to full Netdata dashboard with 2000+ charts.

Container Metrics

Per-container resource monitoring for all environments:

GET /api/v1/monitoring/containers/{container_id}/metrics

Metrics Available:

  • CPU usage (%)
  • Memory usage (MB)
  • Memory limit (MB)
  • Network RX/TX (bytes/sec)
  • Block I/O (read/write ops)
  • Container status (running, stopped, etc.)

Example Response:

{
  "container_id": "odoo_prod_123",
  "container_name": "env-uuid_odoo",
  "metrics": {
    "cpu": {
      "usage_percent": 12.5,
      "throttled_periods": 0
    },
    "memory": {
      "usage_bytes": 1073741824,
      "limit_bytes": 2147483648,
      "usage_percent": 50.0
    },
    "network": {
      "rx_bytes": 1048576,
      "tx_bytes": 2097152,
      "rx_packets": 1024,
      "tx_packets": 2048
    }
  },
  "timestamp": "2025-01-15T10:35:00Z"
}

Health Monitoring

Health Check Frequency: Every 2 minutes (ARQ cron job)

Health Status:

  • healthy: All checks passed
  • degraded: Warnings but operational
  • unhealthy: Critical issues detected
  • unknown: Can't connect to server
  • auth_failed: SSH authentication failed
  • unreachable: Network connectivity issues

View Health Status:

GET /api/v1/vms/{vm_id}

Returns server details including:

  • health_status: Current health state
  • last_health_check: ISO timestamp of last check
  • docker_status: Docker daemon state
  • netdata_streaming_enabled: Whether monitoring is active

Server Health

Connection Status

Status Indicators:

StatusMeaningAction Required
🟢 HealthySSH connected, Docker runningNone
🟡 DegradedWarnings (high disk, old Docker)Monitor
🔴 UnhealthyCritical issues (no disk space)Immediate
UnknownNever checked or >24h since checkTest connection
🔒 Auth FailedSSH credentials invalidUpdate credentials
🚫 UnreachableNetwork issues or firewallCheck network/firewall

Capacity Planning

Current Usage:

GET /api/v1/vms/{vm_id}/stats

Response:

{
  "vm_id": "vm-uuid",
  "status": "running",
  "environment_count": 6,
  "max_environments": 8,
  "utilization_percent": 75.0,
  "cpu_cores": 4,
  "memory_gb": 8,
  "disk_gb": 160,
  "health_status": "healthy",
  "docker_status": "running"
}

Capacity Alerts:

  • 🟡 Warning (80%): Consider adding server capacity soon
  • 🔴 Critical (95%): Approaching max capacity, add servers
  • ⚠️ Over-provisioned (>100%): Physical limits exceeded

Resource Allocation:

  • CPU: 0.5-1 core per environment (burstable)
  • RAM: 1-2 GB per environment (includes DB, Redis, Odoo)
  • Disk: Variable (10-50 GB per environment with databases)

Quota Integration

Server resources count toward organization quotas:

Quota Fields:

  • Organization.total_cpu_quota: Total CPU cores allocated
  • Organization.total_ram_quota_mb: Total RAM in MB
  • Organization.total_disk_quota_gb: Total disk in GB

Active Environment Filtering:

  • Only is_active = True environments count toward quota
  • Destroying environments releases quota immediately
  • Replica environments consume additional resources (30% CPU/RAM, 100% disk)

Quota Check Before Server Creation:

quota_service = QuotaService(db)
quota_check = await quota_service.can_add_server(organization_id)
 
if not quota_check.allowed:
    raise HTTPException(403, f"Quota exceeded: {quota_check.reason}")

Remove Server

Safety Checks

Before removing a server, OEC.SH performs safety checks:

  1. Active Environments: Must be zero
  2. Running Containers: Must be stopped
  3. Organization Membership: User must have delete permission

Error Response (if environments exist):

{
  "detail": "Cannot delete server with 3 active environment(s). Reassign or delete them first."
}

Reassign Environments

Move environments to a different server before deleting:

# Method 1: Via Environment Edit
PUT /api/v1/projects/{project_id}/environments/{env_id}
Content-Type: application/json
 
{
  "vm_id": "new-server-uuid"
}
 
# Method 2: Bulk Reassignment (future feature)
POST /api/v1/vms/{old_vm_id}/migrate
Content-Type: application/json
 
{
  "target_vm_id": "new-server-uuid",
  "environment_ids": ["env-1", "env-2"]
}

Delete Server

DELETE /api/v1/vms/{vm_id}

What happens:

  1. Checks for active environments (fails if any exist)
  2. Sets is_active = False in database
  3. Sets status = "deleted"
  4. Releases quota resources
  5. Triggers audit log entry
  6. Sends SSE event to connected clients

Response:

{
  "message": "Server deleted successfully"
}

Cleanup Considerations:

  • Server is soft-deleted (database record retained for audit)
  • SSH keys remain in database (encrypted) for recovery
  • Netdata streaming stops automatically
  • Physical server NOT affected (BYOS model)

SSH Key Management

Centralized SSH Keys (Optional)

Create organization-wide SSH key pairs for reuse:

POST /api/v1/organizations/{org_id}/ssh-keys
Content-Type: application/json
 
{
  "name": "production-servers",
  "description": "Key for all production servers",
  "key_type": "ed25519"
}

Response:

{
  "id": "key-uuid",
  "name": "production-servers",
  "public_key": "ssh-ed25519 AAAAC3Nz...",
  "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
  "fingerprint": "SHA256:abc123...",
  "created_at": "2025-01-15T10:00:00Z"
}

Deploy to Multiple Servers:

# Reference centralized key when adding server
POST /api/v1/vms
{
  "ssh_key_pair_id": "key-uuid",
  ...
}

Key Rotation

Recommended Schedule: Every 90 days for production servers

Rotation Process:

# 1. Generate new key
ssh-keygen -t ed25519 -f ~/.ssh/oecsh_new_key
 
# 2. Deploy new public key (keep old key active)
ssh root@server "cat >> ~/.ssh/authorized_keys" < ~/.ssh/oecsh_new_key.pub
 
# 3. Update OEC.SH with new private key
PUT /api/v1/vms/{vm_id}/ssh-key
{
  "ssh_private_key": "new-key-content",
  "test_connection": true
}
 
# 4. Verify new key works
ssh -i ~/.ssh/oecsh_new_key root@server
 
# 5. Remove old public key from server
ssh root@server "nano ~/.ssh/authorized_keys"  # Delete old key line

Security Best Practices

Key Generation:

  • Use Ed25519: Modern, secure, fast (ssh-keygen -t ed25519)
  • Use RSA 4096: If Ed25519 not supported (ssh-keygen -t rsa -b 4096)
  • Avoid RSA 2048: Too weak for production
  • Avoid DSA: Deprecated and insecure

Key Storage:

  • Encrypt at Rest: OEC.SH uses AES-256-GCM for all credentials
  • Separate Keys: Different keys per environment type (prod/staging)
  • Backup Keys: Store securely in password manager or vault
  • Don't Commit: Never commit private keys to Git

Key Access:

  • Principle of Least Privilege: Only grant access as needed
  • Audit Logs: Track who creates/modifies servers
  • Regular Rotation: 90-day rotation for production
  • Revoke on Departure: Remove access when team members leave

Firewall Configuration

Cloud Provider Examples

DigitalOcean

# Via doctl CLI
doctl compute firewall create \
  --name oecsh-servers \
  --inbound-rules "protocol:tcp,ports:22,sources:addresses:0.0.0.0/0 protocol:tcp,ports:80,sources:addresses:0.0.0.0/0 protocol:tcp,ports:443,sources:addresses:0.0.0.0/0"
 
# Via Dashboard:
# Networking → Firewalls → Create Firewall
# Add rules: SSH (22), HTTP (80), HTTPS (443)

AWS Security Groups

# Via AWS CLI
aws ec2 authorize-security-group-ingress \
  --group-id sg-xxxxx \
  --protocol tcp --port 22 --cidr 0.0.0.0/0
 
aws ec2 authorize-security-group-ingress \
  --group-id sg-xxxxx \
  --protocol tcp --port 80 --cidr 0.0.0.0/0
 
aws ec2 authorize-security-group-ingress \
  --group-id sg-xxxxx \
  --protocol tcp --port 443 --cidr 0.0.0.0/0

Hetzner Cloud Firewall

# Via hcloud CLI
hcloud firewall create \
  --name oecsh-servers \
  --rule direction=in,protocol=tcp,port=22,source-ips=0.0.0.0/0 \
  --rule direction=in,protocol=tcp,port=80,source-ips=0.0.0.0/0 \
  --rule direction=in,protocol=tcp,port=443,source-ips=0.0.0.0/0

Server-Level Firewall (UFW)

# Reset firewall (careful!)
sudo ufw --force reset
 
# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
 
# Allow SSH, HTTP, HTTPS
sudo ufw allow 22/tcp comment 'SSH access'
sudo ufw allow 80/tcp comment 'HTTP traffic'
sudo ufw allow 443/tcp comment 'HTTPS traffic'
 
# Optional: Allow Docker registry (if using private registry)
sudo ufw allow 5000/tcp comment 'Docker registry'
 
# Enable firewall
sudo ufw enable
 
# Check status
sudo ufw status verbose

Firewall-cmd (RHEL/Rocky/CentOS)

# Allow services
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
 
# Or by port
sudo firewall-cmd --permanent --add-port=22/tcp
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
 
# Reload to apply
sudo firewall-cmd --reload
 
# Check status
sudo firewall-cmd --list-all

iptables (Advanced)

# Flush existing rules
sudo iptables -F
 
# Default policies
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
 
# Allow loopback
sudo iptables -A INPUT -i lo -j ACCEPT
 
# Allow established connections
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
 
# Allow SSH, HTTP, HTTPS
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
 
# Save rules (Debian/Ubuntu)
sudo netfilter-persistent save
 
# Save rules (RHEL/CentOS)
sudo service iptables save

Permissions

Server management uses granular RBAC permissions:

PermissionActionDescription
org.servers.listList ServersView organization's servers
org.servers.viewView ServerView server details
org.servers.createAdd ServerRegister new servers
org.servers.updateEdit ServerModify server configuration
org.servers.deleteDelete ServerRemove servers
org.servers.testTest ConnectionTest SSH connectivity
org.servers.preflightRun PreflightRun deployment checks
org.servers.secureSecure ServerOne-click security upgrade
org.monitoring.viewView MetricsAccess Netdata monitoring

Default Role Mappings:

RolePermissions
Org OwnerAll server permissions
Org AdminAll except delete
Org MemberList, view, view metrics
Project AdminList, view (organization servers only)
Project MemberList, view (organization servers only)

API Reference

List Servers

GET /api/v1/vms?organization_id={org_id}&status={status}

Query Parameters:

  • organization_id (optional): Filter by organization
  • status (optional): Filter by status (pending, running, stopped, error)
  • skip (default: 0): Pagination offset
  • limit (default: 100): Results per page

Response:

[
  {
    "id": "vm-uuid",
    "name": "production-server-1",
    "organization_id": "org-uuid",
    "organization_name": "Acme Corp",
    "provider": "custom",
    "region": "Frankfurt",
    "status": "running",
    "connection_host": "203.0.113.10",
    "ssh_port": 22,
    "ssh_username": "root",
    "ssh_auth_method": "ssh_key",
    "cpu_cores": 4,
    "memory_gb": 8,
    "disk_gb": 160,
    "environment_count": 6,
    "running_environment_count": 5,
    "health_status": "healthy",
    "docker_status": "running",
    "created_at": "2025-01-10T08:00:00Z"
  }
]

Get Server Details

GET /api/v1/vms/{vm_id}

Response: Same as list item, with additional fields:

  • os_type, os_version, os_arch
  • docker_version
  • netdata_streaming_enabled, netdata_machine_guid
  • last_health_check
  • qualification_status, last_preflight_check

Get Server Environments

GET /api/v1/vms/{vm_id}/environments

Response:

[
  {
    "id": "env-uuid",
    "name": "production",
    "environment_type": "production",
    "status": "running",
    "domain": "shop.example.com",
    "port": 8069,
    "container_id": "odoo_prod_abc123",
    "cpu_cores": 1.0,
    "ram_mb": 2048,
    "disk_gb": 40,
    "project": {
      "id": "project-uuid",
      "name": "E-commerce Shop",
      "slug": "ecommerce-shop",
      "odoo_version": "17.0"
    }
  }
]

Get Server Stats

GET /api/v1/vms/{vm_id}/stats

Response:

{
  "vm_id": "vm-uuid",
  "status": "running",
  "size": "medium",
  "environment_count": 6,
  "max_environments": 8,
  "utilization_percent": 75.0,
  "cpu_cores": 4,
  "memory_gb": 8,
  "disk_gb": 160,
  "health_status": "healthy",
  "docker_status": "running",
  "cpu_usage_percent": 45.2,
  "memory_usage_percent": 62.8,
  "disk_usage_percent": 48.5
}

Update Server

PATCH /api/v1/vms/{vm_id}
Content-Type: application/json
 
{
  "name": "production-server-1-updated",
  "cpu_cores": 8,
  "memory_gb": 16,
  "max_projects": 12
}

Server Actions

Start Server

POST /api/v1/vms/{vm_id}/start

Stop Server

POST /api/v1/vms/{vm_id}/stop

Reboot Server

POST /api/v1/vms/{vm_id}/reboot

Troubleshooting

SSH Connection Issues

Problem: Authentication failed

Solutions:

  1. Verify SSH credentials are correct
  2. Test SSH manually: ssh -i key.pem user@host
  3. Check if password auth is disabled on server
  4. Verify key format is correct (PEM format required)
  5. Check if key is encrypted and passphrase is provided

Problem: Connection timed out

Solutions:

  1. Verify server IP/hostname is correct
  2. Check firewall allows port 22
  3. Test network connectivity: ping server-ip
  4. Check SSH port: nc -zv server-ip 22
  5. Verify cloud security groups allow SSH

Problem: Permission denied (publickey)

Solutions:

  1. Verify public key is deployed to server
  2. Check ~/.ssh/authorized_keys permissions (600)
  3. Check ~/.ssh directory permissions (700)
  4. Verify key ownership: chown user:user ~/.ssh/authorized_keys
  5. Check sshd logs: sudo journalctl -u ssh

Docker Issues

Problem: Docker not installed

Solution: Run preflight checks with auto-fix:

POST /api/v1/vms/{vm_id}/preflight
# Click "Fix All" in UI or use fix endpoint
POST /api/v1/vms/{vm_id}/preflight/fix
{
  "actions": ["install_docker"]
}

Problem: Docker daemon not running

Solution:

# Via preflight auto-fix
POST /api/v1/vms/{vm_id}/preflight/fix
{
  "actions": ["start_docker"]
}
 
# Or manually
ssh root@server "sudo systemctl start docker && sudo systemctl enable docker"

Problem: Permission denied while trying to connect to Docker

Solution:

# Add user to docker group
sudo usermod -aG docker $USER
 
# Restart SSH session
exit
ssh user@server
 
# Verify
docker ps

Connectivity Issues

Problem: Network connectivity test failed

Solutions:

  1. Check internet access: curl -I https://www.google.com
  2. Test DNS resolution: nslookup docker.io
  3. Check firewall outbound rules (must allow 80, 443)
  4. Verify no proxy configuration issues
  5. Test Docker registry access: docker pull hello-world

Problem: Cannot pull Docker images

Solutions:

  1. Check Docker Hub rate limits (use authenticated pulls)
  2. Verify network allows HTTPS to registries
  3. Test registry access: curl -I https://registry-1.docker.io
  4. Check disk space: df -h
  5. Try manual pull: docker pull odoo:17.0

Port Conflicts

Problem: Port 80/443 already in use

Solutions:

  1. Check what's using ports: sudo lsof -i :80 -i :443
  2. Stop conflicting services:
    sudo systemctl stop apache2  # or nginx
    sudo systemctl disable apache2
  3. Verify ports are free: sudo netstat -tulpn | grep -E ':80|:443'
  4. Re-run preflight checks

Monitoring Issues

Problem: Netdata not streaming metrics

Solutions:

  1. Check Netdata is installed: systemctl status netdata
  2. Verify API key is configured: cat /etc/netdata/stream.conf
  3. Check parent URL is reachable: curl -I https://netdata.oec.sh
  4. Review Netdata logs: sudo journalctl -u netdata -n 50
  5. Restart Netdata: sudo systemctl restart netdata

Problem: Container metrics not showing

Solutions:

  1. Verify Docker integration: docker info | grep cgroup
  2. Check Netdata Docker plugin: netdatacli reload-claiming-state
  3. Ensure containers are running: docker ps
  4. Check cgroup v2 compatibility (required for modern Docker)

Performance Issues

Problem: Server running slow with many environments

Solutions:

  1. Check resource allocation vs usage:
    GET /api/v1/vms/{vm_id}/stats
  2. Review per-container metrics to find resource hogs
  3. Consider vertical scaling (more CPU/RAM)
  4. Consider horizontal scaling (add another server)
  5. Review environment resource limits
  6. Check for disk I/O bottlenecks: iostat -x 1

Problem: Out of disk space

Solutions:

  1. Clean Docker resources:
    docker system prune -a --volumes --force
  2. Remove old images: docker image prune -a
  3. Check environment disk usage:
    docker exec container_name du -sh /var/lib/odoo
  4. Increase disk size (cloud provider console)
  5. Move large files to object storage

Security Best Practices

Server Hardening

SSH Configuration (/etc/ssh/sshd_config):

# Disable password authentication (after SSH keys deployed)
PasswordAuthentication no
ChallengeResponseAuthentication no
 
# Disable root login (use sudo instead)
PermitRootLogin no
 
# Use SSH protocol 2 only
Protocol 2
 
# Limit authentication attempts
MaxAuthTries 3
 
# Disconnect idle sessions
ClientAliveInterval 300
ClientAliveCountMax 2
 
# Restart SSH to apply
sudo systemctl restart sshd

System Updates:

# Ubuntu/Debian (automated)
sudo apt update && sudo apt upgrade -y
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
 
# RHEL/Rocky/CentOS
sudo dnf update -y
sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic.timer

Fail2Ban (brute-force protection):

# Install
sudo apt install fail2ban  # Debian/Ubuntu
sudo dnf install fail2ban  # RHEL/Rocky
 
# Configure SSH protection
sudo tee /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
EOF
 
# Start and enable
sudo systemctl enable --now fail2ban

Network Security

Restrict SSH Access (optional, if you have static IPs):

# UFW: Allow SSH only from specific IPs
sudo ufw delete allow 22/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22
 
# firewall-cmd: Rich rule for SSH
sudo firewall-cmd --permanent --add-rich-rule='
  rule family="ipv4"
  source address="203.0.113.0/24"
  port protocol="tcp" port="22" accept'

Disable IPv6 (if not used):

# Add to /etc/sysctl.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
 
# Apply
sudo sysctl -p

Docker Security

Docker Daemon Security:

# Limit who can run Docker
sudo usermod -aG docker oecsh-user  # Only specific users
 
# Enable live restore (survive daemon restarts)
sudo tee -a /etc/docker/daemon.json <<EOF
{
  "live-restore": true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF
 
sudo systemctl restart docker

Container Security:

  • OEC.SH runs containers with limited capabilities
  • No privileged containers
  • Read-only root filesystem where possible
  • Resource limits enforced (CPU, memory)

Audit and Compliance

Enable Audit Logging:

# Install auditd
sudo apt install auditd  # Ubuntu/Debian
sudo dnf install audit   # RHEL/Rocky
 
# Enable and start
sudo systemctl enable --now auditd
 
# View Docker events
sudo ausearch -k docker

Review OEC.SH Audit Logs:

GET /api/v1/audit?resource_type=server&action=SERVER_CREATED
GET /api/v1/audit?resource_type=server&action=SERVER_UPDATED
GET /api/v1/audit?resource_type=server&action=SERVER_DELETED

Related Documentation


Need Help? Join our Discord Community (opens in a new tab) or contact support at support@oec.sh