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 AssignmentKey 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
| Resource | Minimum | Recommended | Notes |
|---|---|---|---|
| CPU | 2 cores | 4+ cores | 0.5-1 core per environment |
| RAM | 4 GB | 8+ GB | 1-2 GB per environment |
| Disk | 80 GB | 160+ GB SSD | Includes Docker images, databases |
| OS | Ubuntu 20.04+ | Ubuntu 22.04 LTS | Debian, RHEL, Rocky also supported |
| Docker | 20.10+ | Latest stable | Auto-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 --reloadCloud 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 Family | Versions | Support Tier | Package Manager |
|---|---|---|---|
| Ubuntu | 20.04, 22.04, 24.04 | Tier 1 (Full) | apt |
| Debian | 11 (Bullseye), 12 (Bookworm) | Tier 1 (Full) | apt |
| Rocky Linux | 8, 9 | Tier 2 (Partial) | dnf |
| AlmaLinux | 8, 9 | Tier 2 (Partial) | dnf |
| CentOS | 8, 9 | Tier 2 (Partial) | dnf/yum |
| Alpine | 3.17+ | Tier 3 (Experimental) | apk |
| Arch | Latest | Tier 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: 22Limitations:
- 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.pubDeploy 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.10Connection Host Types
OEC.SH supports multiple address types for maximum flexibility:
| Type | When to Use | Example |
|---|---|---|
| IPv4 | Standard internet connections | 203.0.113.10 |
| IPv6 | Modern networks with IPv6 support | 2001:db8::1 |
| FQDN | Domain-based connections (DNS required) | server.example.com |
| Hostname | Internal networks or VPN | production-1 |
| AUTO | Platform detects best option | Priority: FQDN > IPv4 > IPv6 > Hostname |
Add Server via Dashboard
-
Navigate: Settings → Servers → "Add Server"
-
Basic Info:
- Name:
production-server-1(friendly identifier) - Provider: Custom (BYOS), DigitalOcean, AWS, Hetzner, Azure
- Region:
Frankfurtor any label
- Name:
-
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)
- IPv4 Address:
-
SSH Authentication:
- Username:
root(orubuntu,admin, etc.) - Auth Method: SSH Key (recommended) or Password
- Private Key: Paste your SSH private key content
- Passphrase: Only if key is encrypted
- Username:
-
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)
-
Resources (auto-detected after connection):
- CPU Cores: 4
- Memory: 8 GB
- Disk: 160 GB
- Max Environments: 8 (recommended capacity)
-
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-connectionWhat it does:
- Establishes SSH connection using stored credentials
- Detects OS type, version, and architecture
- Checks Docker installation and version
- Detects CPU, RAM, and disk resources
- Determines sudo requirements (root vs non-root user)
- 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}/preflightReturns Server-Sent Events (SSE) stream with real-time progress.
Check Categories:
| Check | Description | Auto-Fix Available |
|---|---|---|
| SSH Connectivity | SSH connection and authentication | ❌ |
| Root/Sudo Access | Privilege escalation for Docker | ✅ (configure sudo) |
| OS Compatibility | Linux distribution and version | ❌ |
| Docker Installation | Docker Engine installed | ✅ (install Docker) |
| Docker Running | Docker daemon active | ✅ (start Docker) |
| Docker Permissions | User can run Docker commands | ✅ (add to docker group) |
| Disk Space | Minimum 20GB free | ❌ |
| Network Connectivity | Internet access for images | ❌ |
| Ports Available | 80, 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:
- Generate new SSH key pair
- Deploy new public key to server (keep old key active)
- Update OEC.SH with new private key
- Test connection succeeds
- 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}/secureWhat it does:
- Generates Ed25519 SSH key pair
- Connects via current password authentication
- Deploys public key to
~/.ssh/authorized_keys - Verifies key-based auth works
- Updates server record to use SSH key
- Clears password from database for security
- 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}/summaryReturns 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}/metricsReturns 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}/urlReturns 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}/metricsMetrics 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 statelast_health_check: ISO timestamp of last checkdocker_status: Docker daemon statenetdata_streaming_enabled: Whether monitoring is active
Server Health
Connection Status
Status Indicators:
| Status | Meaning | Action Required |
|---|---|---|
| 🟢 Healthy | SSH connected, Docker running | None |
| 🟡 Degraded | Warnings (high disk, old Docker) | Monitor |
| 🔴 Unhealthy | Critical issues (no disk space) | Immediate |
| ⚫ Unknown | Never checked or >24h since check | Test connection |
| 🔒 Auth Failed | SSH credentials invalid | Update credentials |
| 🚫 Unreachable | Network issues or firewall | Check network/firewall |
Capacity Planning
Current Usage:
GET /api/v1/vms/{vm_id}/statsResponse:
{
"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 allocatedOrganization.total_ram_quota_mb: Total RAM in MBOrganization.total_disk_quota_gb: Total disk in GB
Active Environment Filtering:
- Only
is_active = Trueenvironments 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:
- Active Environments: Must be zero
- Running Containers: Must be stopped
- 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:
- Checks for active environments (fails if any exist)
- Sets
is_active = Falsein database - Sets
status = "deleted" - Releases quota resources
- Triggers audit log entry
- 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 lineSecurity 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/0Hetzner 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/0Server-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 verboseFirewall-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-alliptables (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 savePermissions
Server management uses granular RBAC permissions:
| Permission | Action | Description |
|---|---|---|
org.servers.list | List Servers | View organization's servers |
org.servers.view | View Server | View server details |
org.servers.create | Add Server | Register new servers |
org.servers.update | Edit Server | Modify server configuration |
org.servers.delete | Delete Server | Remove servers |
org.servers.test | Test Connection | Test SSH connectivity |
org.servers.preflight | Run Preflight | Run deployment checks |
org.servers.secure | Secure Server | One-click security upgrade |
org.monitoring.view | View Metrics | Access Netdata monitoring |
Default Role Mappings:
| Role | Permissions |
|---|---|
| Org Owner | All server permissions |
| Org Admin | All except delete |
| Org Member | List, view, view metrics |
| Project Admin | List, view (organization servers only) |
| Project Member | List, 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 organizationstatus(optional): Filter by status (pending, running, stopped, error)skip(default: 0): Pagination offsetlimit(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_archdocker_versionnetdata_streaming_enabled,netdata_machine_guidlast_health_checkqualification_status,last_preflight_check
Get Server Environments
GET /api/v1/vms/{vm_id}/environmentsResponse:
[
{
"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}/statsResponse:
{
"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}/startStop Server
POST /api/v1/vms/{vm_id}/stopReboot Server
POST /api/v1/vms/{vm_id}/rebootTroubleshooting
SSH Connection Issues
Problem: Authentication failed
Solutions:
- Verify SSH credentials are correct
- Test SSH manually:
ssh -i key.pem user@host - Check if password auth is disabled on server
- Verify key format is correct (PEM format required)
- Check if key is encrypted and passphrase is provided
Problem: Connection timed out
Solutions:
- Verify server IP/hostname is correct
- Check firewall allows port 22
- Test network connectivity:
ping server-ip - Check SSH port:
nc -zv server-ip 22 - Verify cloud security groups allow SSH
Problem: Permission denied (publickey)
Solutions:
- Verify public key is deployed to server
- Check
~/.ssh/authorized_keyspermissions (600) - Check
~/.sshdirectory permissions (700) - Verify key ownership:
chown user:user ~/.ssh/authorized_keys - 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 psConnectivity Issues
Problem: Network connectivity test failed
Solutions:
- Check internet access:
curl -I https://www.google.com - Test DNS resolution:
nslookup docker.io - Check firewall outbound rules (must allow 80, 443)
- Verify no proxy configuration issues
- Test Docker registry access:
docker pull hello-world
Problem: Cannot pull Docker images
Solutions:
- Check Docker Hub rate limits (use authenticated pulls)
- Verify network allows HTTPS to registries
- Test registry access:
curl -I https://registry-1.docker.io - Check disk space:
df -h - Try manual pull:
docker pull odoo:17.0
Port Conflicts
Problem: Port 80/443 already in use
Solutions:
- Check what's using ports:
sudo lsof -i :80 -i :443 - Stop conflicting services:
sudo systemctl stop apache2 # or nginx sudo systemctl disable apache2 - Verify ports are free:
sudo netstat -tulpn | grep -E ':80|:443' - Re-run preflight checks
Monitoring Issues
Problem: Netdata not streaming metrics
Solutions:
- Check Netdata is installed:
systemctl status netdata - Verify API key is configured:
cat /etc/netdata/stream.conf - Check parent URL is reachable:
curl -I https://netdata.oec.sh - Review Netdata logs:
sudo journalctl -u netdata -n 50 - Restart Netdata:
sudo systemctl restart netdata
Problem: Container metrics not showing
Solutions:
- Verify Docker integration:
docker info | grep cgroup - Check Netdata Docker plugin:
netdatacli reload-claiming-state - Ensure containers are running:
docker ps - Check cgroup v2 compatibility (required for modern Docker)
Performance Issues
Problem: Server running slow with many environments
Solutions:
- Check resource allocation vs usage:
GET /api/v1/vms/{vm_id}/stats - Review per-container metrics to find resource hogs
- Consider vertical scaling (more CPU/RAM)
- Consider horizontal scaling (add another server)
- Review environment resource limits
- Check for disk I/O bottlenecks:
iostat -x 1
Problem: Out of disk space
Solutions:
- Clean Docker resources:
docker system prune -a --volumes --force - Remove old images:
docker image prune -a - Check environment disk usage:
docker exec container_name du -sh /var/lib/odoo - Increase disk size (cloud provider console)
- 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 sshdSystem 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.timerFail2Ban (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 fail2banNetwork 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 -pDocker 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 dockerContainer 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 dockerReview 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_DELETEDRelated Documentation
- Environment Management - Deploy Odoo to servers
- Monitoring - Netdata monitoring details
- Backups - Backup strategies for server environments
- Storage Configuration - Configure storage providers
- DNS Management - Configure domains for environments
Need Help? Join our Discord Community (opens in a new tab) or contact support at support@oec.sh